3步搞定MyBatis读写分离:Mapper动态数据源配置指南

【免费下载链接】Mapper Mybatis Common Mapper - Easy to use 【免费下载链接】Mapper 项目地址: https://gitcode.com/gh_mirrors/ma/Mapper

你还在为数据库读写分离配置复杂而烦恼吗?还在纠结如何优雅地整合动态数据源到MyBatis项目中?本文将通过3个简单步骤,带你快速实现基于MyBatis Common Mapper的动态数据源配置,轻松应对高并发场景下的数据库负载均衡。

读完本文你将获得:

  • 掌握MyBatis Mapper多数据源配置核心方法
  • 学会通过注解实现读写分离路由策略
  • 获得可直接复用的动态数据源配置模板
  • 了解生产环境下的最佳实践与常见问题

为什么需要读写分离?

在传统的单数据源架构中,所有数据库操作都通过一个数据库连接进行,这在高并发场景下会导致严重的性能瓶颈。读写分离架构通过将查询操作路由到从库,写操作路由到主库,可显著提升系统吞吐量。

MyBatis Common Mapper(以下简称Mapper)作为MyBatis的增强工具,提供了便捷的多数据源集成方案。通过MapperScannerConfigurer类的灵活配置,我们可以轻松实现数据源的动态切换。

核心组件与工作原理

读写分离架构图

mermaid

关键技术组件

  1. 动态数据源路由:通过Spring的AbstractRoutingDataSource实现数据源动态选择
  2. Mapper多数据源支持:利用MapperScannerConfigurer的sqlSessionFactoryBeanName属性指定不同数据源
  3. 注解式事务管理:通过@Transactional注解指定事务使用的数据源

step 1: 配置多数据源

添加依赖

在pom.xml中添加Spring Boot和Mapper的依赖:

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

配置数据源信息

在application.properties中配置主从数据源:

# 主数据源配置
spring.datasource.master.url=jdbc:mysql://localhost:3306/master_db?useSSL=false
spring.datasource.master.username=root
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver

# 从数据源配置
spring.datasource.slave1.url=jdbc:mysql://localhost:3306/slave1_db?useSSL=false
spring.datasource.slave1.username=root
spring.datasource.slave1.password=123456
spring.datasource.slave1.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.slave2.url=jdbc:mysql://localhost:3306/slave2_db?useSSL=false
spring.datasource.slave2.username=root
spring.datasource.slave2.password=123456
spring.datasource.slave2.driver-class-name=com.mysql.cj.jdbc.Driver

# Mapper配置
mapper.mappers[0]=tk.mybatis.mapper.common.Mapper
mapper.notEmpty=true
mapper.style=uppercase

step 2: 实现动态数据源路由

创建数据源配置类

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource master,
                                        @Qualifier("slave1DataSource") DataSource slave1,
                                        @Qualifier("slave2DataSource") DataSource slave2) {
        Map<Object, Object> dataSources = new HashMap<>();
        dataSources.put("master", master);
        dataSources.put("slave1", slave1);
        dataSources.put("slave2", slave2);
        
        DynamicDataSource routingDataSource = new DynamicDataSource();
        routingDataSource.setTargetDataSources(dataSources);
        routingDataSource.setDefaultTargetDataSource(master);
        return routingDataSource;
    }
}

实现动态数据源路由类

public class DynamicDataSource extends AbstractRoutingDataSource {
    
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
    public static void setDataSourceKey(String key) {
        contextHolder.set(key);
    }
    
    public static void clearDataSourceKey() {
        contextHolder.remove();
    }
    
    @Override
    protected Object determineCurrentLookupKey() {
        return contextHolder.get();
    }
}

step 3: 配置Mapper多数据源支持

配置SqlSessionFactory

@Configuration
@MapperScan(basePackages = "com.example.mapper.master", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterMapperConfig {

    @Bean
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("routingDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        // 设置主库Mapper扫描路径
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/master/*.xml"));
        return sessionFactory.getObject();
    }
}
@Configuration
@MapperScan(basePackages = "com.example.mapper.slave", sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveMapperConfig {

    @Bean
    public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("routingDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        // 设置从库Mapper扫描路径
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/slave/*.xml"));
        return sessionFactory.getObject();
    }
}

使用注解实现读写分离

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMasterMapper userMasterMapper;
    
    @Autowired
    private UserSlaveMapper userSlaveMapper;
    
    @Override
    @Transactional
    public void createUser(User user) {
        // 自动路由到主库
        DynamicDataSource.setDataSourceKey("master");
        userMasterMapper.insert(user);
        DynamicDataSource.clearDataSourceKey();
    }
    
    @Override
    public User getUserById(Long id) {
        // 随机选择从库
        String[] slaves = {"slave1", "slave2"};
        DynamicDataSource.setDataSourceKey(slaves[new Random().nextInt(slaves.length)]);
        User user = userSlaveMapper.selectByPrimaryKey(id);
        DynamicDataSource.clearDataSourceKey();
        return user;
    }
}

最佳实践与注意事项

1. 事务管理

在使用读写分离时,需要特别注意事务边界。由于从库是主库的只读副本,写操作必须在主库上执行。可以通过Spring的事务注解指定数据源:

@Transactional(transactionManager = "masterTransactionManager")

2. 数据源切换策略

常见的数据源切换策略包括:

  • 基于注解的手动切换
  • 基于AOP的自动切换
  • 基于负载均衡的从库选择

3. 延迟加载问题

在使用MyBatis的延迟加载功能时,需要确保延迟加载的属性访问也使用正确的数据源。可以通过MapperScannerConfigurerlazyInitialization属性控制延迟加载行为。

4. 监控与运维

建议集成Spring Boot Actuator监控数据源状态:

management.endpoints.web.exposure.include=datasources,health

常见问题解决方案

1. 数据源切换不生效

检查是否正确配置了@MapperScan注解的sqlSessionFactoryRef属性,确保不同包下的Mapper使用不同的SqlSessionFactory。

2. 事务无法回滚

确保在事务方法中正确设置了数据源键,并且事务管理器与SqlSessionFactory对应。

3. 从库数据延迟

对于实时性要求高的业务,可以采用"写主读主"的策略,或者实现从库数据延迟检测机制。

总结与扩展

通过本文介绍的3个步骤,我们实现了基于MyBatis Common Mapper的动态数据源配置,主要包括:

  1. 配置多数据源连接信息
  2. 实现动态数据源路由
  3. 配置Mapper多数据源支持

这种方案的优势在于:

  • 实现简单,无需复杂的第三方依赖
  • 灵活性高,可根据业务需求自定义路由策略
  • 与Spring生态深度整合,支持事务管理

对于更复杂的场景,可以考虑结合dynamic-datasource-spring-boot-starter等成熟框架,进一步简化多数据源配置。

官方文档:README.md Spring集成指南:spring/README.md Spring Boot集成示例:spring-boot-starter/mapper-spring-boot-samples

希望本文能帮助你轻松应对数据库读写分离的挑战,构建高性能的MyBatis应用!如果你有任何问题或建议,欢迎在评论区留言讨论。

【免费下载链接】Mapper Mybatis Common Mapper - Easy to use 【免费下载链接】Mapper 项目地址: https://gitcode.com/gh_mirrors/ma/Mapper

更多推荐