程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • mybatis

    • mybatis 概述
    • MyBatis 整合Spring Boot
    • MyBatis 开发环境搭建
    • MyBatis 入门案例
    • MyBatis 常用注解
    • MyBatis数据库类型与实体类型对应关系
    • MyBatis 动态参数传递与模糊查询
    • MyBatis 查询原理与字段映射
    • MyBatis 的resultType 和 resultMap
    • MyBatis 增删改操作
    • MyBatis 单表查询
    • MyBatis 多表查询
    • MyBatis 动态 SQL
    • MyBatis 分页插件
    • MyBatis 一级和二级缓存
    • MyBatis 逆向工程
    • MyBatis 多数据源配置
      • 1. 手动配置多数据源
        • 1.1 配置多个数据源
        • 1.2 为每个数据源配置 MyBatis
        • 1.3 使用手动配置的多数据源
      • 2. 动态数据源实现读写分离
        • 2.1 创建动态数据源的核心组件
        • 2.2 定义数据源切换注解
        • 2.3 配置动态数据源
        • 2.4 实现数据源切换的 AOP
        • 2.5 使用动态数据源
      • 3. 使用第三方库实现多数据源和读写分离
        • 3.1 Baomidou Dynamic Datasource
        • 3.2 使用 ShardingSphere 实现读写分离
      • 4. 多数据源注解使用
        • 4.1 自定义实现中的注解
        • 4.2 Baomidou Dynamic Datasource 注解详解
        • 4.3 ShardingSphere 中的数据源控制
        • 4.4 注解使用的最佳实践
      • 5. 多数据源方案选择
    • MyBatis 自定义拦截器
  • MyBatis
  • mybatis
scholar
2025-03-16
目录

MyBatis 多数据源配置

# MyBatis 多数据源配置

# 1. 手动配置多数据源

手动配置是最基础的多数据源实现方式,通过显式定义多个数据源并针对不同的业务场景选择合适的数据源。

# 1.1 配置多个数据源

首先,在 application.yml 中定义多个数据源的配置:

spring:
  datasource:
    master:  # 主数据源配置
      url: jdbc:mysql://master-host:3306/db_name?useSSL=false
      username: root
      password: password
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave:  # 从数据源配置
      url: jdbc:mysql://slave-host:3306/db_name?useSSL=false
      username: root
      password: password
      driver-class-name: com.mysql.cj.jdbc.Driver
1
2
3
4
5
6
7
8
9
10
11
12

然后,创建数据源配置类:

@Configuration  // 标记为 Spring 配置类
public class DataSourceConfig {
    
    @Bean
    @Primary  // 标记为主要的数据源,默认情况下使用此数据源
    @ConfigurationProperties(prefix = "spring.datasource.master")  // 绑定 application.yml 中的配置
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();  // 创建主数据源
    }
    
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")  // 绑定从数据源配置
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();  // 创建从数据源
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 1.2 为每个数据源配置 MyBatis

为了让 MyBatis 能够使用这些数据源,需要为每个数据源配置 SqlSessionFactory 和 SqlSessionTemplate:

@Configuration  // 标记为配置类
@MapperScan(basePackages = "com.example.master.mapper", sqlSessionTemplateRef = "masterSqlSessionTemplate")  // 指定主数据源的 Mapper 路径
public class MasterDataSourceConfig {
    
    @Autowired
    private DataSource masterDataSource;  // 注入主数据源
    
    @Bean
    public SqlSessionFactory masterSqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();  // 创建 SqlSessionFactoryBean
        factoryBean.setDataSource(masterDataSource);  // 设置数据源
        
        // 配置 Mapper XML 文件位置
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/master/*.xml"));  // 指定 XML 文件路径
        
        return factoryBean.getObject();  // 返回 SqlSessionFactory 实例
    }
    
    @Bean
    public SqlSessionTemplate masterSqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(masterSqlSessionFactory());  // 创建 SqlSessionTemplate
    }
    
    @Bean
    public PlatformTransactionManager masterTransactionManager() {
        return new DataSourceTransactionManager(masterDataSource);  // 创建事务管理器
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

同样地,为从数据源创建配置类:

@Configuration  // 标记为配置类
@MapperScan(basePackages = "com.example.slave.mapper", sqlSessionTemplateRef = "slaveSqlSessionTemplate")  // 指定从数据源的 Mapper 路径
public class SlaveDataSourceConfig {
    
    @Autowired
    private DataSource slaveDataSource;  // 注入从数据源
    
    @Bean
    public SqlSessionFactory slaveSqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();  // 创建 SqlSessionFactoryBean
        factoryBean.setDataSource(slaveDataSource);  // 设置数据源
        
        // 配置 Mapper XML 文件位置
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/slave/*.xml"));  // 指定 XML 文件路径
        
        return factoryBean.getObject();  // 返回 SqlSessionFactory 实例
    }
    
    @Bean
    public SqlSessionTemplate slaveSqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(slaveSqlSessionFactory());  // 创建 SqlSessionTemplate
    }
    
    @Bean
    public PlatformTransactionManager slaveTransactionManager() {
        return new DataSourceTransactionManager(slaveDataSource);  // 创建事务管理器
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 1.3 使用手动配置的多数据源

使用手动配置的多数据源时,需要通过不同的 Mapper 接口访问不同的数据源:

@Service  // 标记为服务类
public class UserService {
    
    @Autowired
    private MasterUserMapper masterUserMapper;  // 注入主数据源的 Mapper
    
    @Autowired
    private SlaveUserMapper slaveUserMapper;  // 注入从数据源的 Mapper
    
    public void createUser(User user) {
        masterUserMapper.insert(user);  // 写操作使用主数据源
    }
    
    public List<User> getAllUsers() {
        return slaveUserMapper.findAll();  // 读操作使用从数据源
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 2. 动态数据源实现读写分离

手动配置多数据源的方式需要为每个操作显式指定使用哪个数据源,较为繁琐。动态数据源通过 AOP 实现自动数据源切换,更加灵活。

# 2.1 创建动态数据源的核心组件

首先,创建一个动态数据源持有者,用于保存当前线程使用的数据源:

public class DynamicDataSourceHolder {
    public static final String MASTER = "master";  // 主库标识
    public static final String SLAVE = "slave";    // 从库标识
    
    // 使用 ThreadLocal 确保线程安全
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();  // 存储当前线程的数据源类型
    
    // 设置当前线程的数据源
    public static void setDataSourceType(String dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);  // 设置数据源类型
    }
    
    // 获取当前线程的数据源,默认返回主库
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get() == null ? MASTER : CONTEXT_HOLDER.get();  // 获取数据源类型
    }
    
    // 清除当前线程的数据源类型
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();  // 移除当前线程的数据源类型
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

接下来,创建继承自 Spring 的 AbstractRoutingDataSource 的动态数据源类:

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDataSourceType();  // 返回当前线程的数据源类型
    }
}
1
2
3
4
5
6

# 2.2 定义数据源切换注解

创建自定义注解用于标记应该使用哪个数据源:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Master {
    // 可以添加属性,例如指定具体的主库
    String value() default "";
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Slave {
    // 可以添加属性,例如指定具体的从库
    String value() default "";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这些注解需要通过 AOP 拦截并处理,如前面动态数据源实现中的 DataSourceAspect。

# 2.3 配置动态数据源

创建动态数据源配置类:

@Configuration  // 标记为配置类
@EnableTransactionManagement  // 启用事务管理
public class DynamicDataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.master")  // 绑定主数据源配置
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();  // 创建主数据源
    }
    
    @Bean
    @ConfigurationProperties("spring.datasource.slave")  // 绑定从数据源配置
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();  // 创建从数据源
    }
    
    @Bean
    @Primary  // 设置为主要的数据源 Bean
    public DataSource dynamicDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DynamicDataSourceHolder.MASTER, masterDataSource());  // 添加主数据源
        targetDataSources.put(DynamicDataSourceHolder.SLAVE, slaveDataSource());    // 添加从数据源
        
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setTargetDataSources(targetDataSources);  // 设置目标数据源集合
        dataSource.setDefaultTargetDataSource(masterDataSource());  // 设置默认数据源
        
        return dataSource;  // 返回动态数据源
    }
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource);  // 设置数据源
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/**/*.xml"));  // 设置 Mapper XML 文件位置
        
        return sqlSessionFactoryBean.getObject();  // 返回 SqlSessionFactory
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 2.4 实现数据源切换的 AOP

创建切面类,根据方法上的注解或命名规则切换数据源:

@Aspect  // 标记为切面类
@Component  // 注册为 Spring Bean
@Order(1)  // 设置切面优先级,确保在事务切面之前执行
public class DataSourceAspect {
    
    private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);
    
    // 定义切点:使用 @Master 注解的方法或类
    @Pointcut("@annotation(com.example.annotation.Master) || @within(com.example.annotation.Master)")
    public void masterPointcut() {}
    
    // 定义切点:使用 @Slave 注解的方法或类
    @Pointcut("@annotation(com.example.annotation.Slave) || @within(com.example.annotation.Slave)")
    public void slavePointcut() {}
    
    // 在 @Master 注解的方法执行前,切换到主库
    @Before("masterPointcut()")
    public void beforeMaster() {
        log.debug("Switch to MASTER database");
        DynamicDataSourceHolder.setDataSourceType(DynamicDataSourceHolder.MASTER);  // 设置为主库数据源
    }
    
    // 在 @Slave 注解的方法执行前,切换到从库
    @Before("slavePointcut()")
    public void beforeSlave() {
        log.debug("Switch to SLAVE database");
        DynamicDataSourceHolder.setDataSourceType(DynamicDataSourceHolder.SLAVE);  // 设置为从库
    }
    
    // 方法执行后清除数据源标识
    @After("masterPointcut() || slavePointcut()")
    public void after() {
        DynamicDataSourceHolder.clearDataSourceType();  // 清除数据源标识
    }
    
    // 对未添加注解的方法,根据方法名判断使用哪个数据源
    @Before("execution(* com.example.service.*.*(..)) && !masterPointcut() && !slavePointcut()")
    public void beforeDefault(JoinPoint point) {
        String methodName = point.getSignature().getName();  // 获取方法名
        // 查询方法使用从库,其他方法使用主库
        if (methodName.startsWith("select") || methodName.startsWith("get") || 
            methodName.startsWith("find") || methodName.startsWith("query") ||
            methodName.startsWith("list") || methodName.startsWith("count")) {
            log.debug("Switch to SLAVE database - Method name: {}", methodName);
            DynamicDataSourceHolder.setDataSourceType(DynamicDataSourceHolder.SLAVE);  // 设置为从库
        } else {
            log.debug("Switch to MASTER database - Method name: {}", methodName);
            DynamicDataSourceHolder.setDataSourceType(DynamicDataSourceHolder.MASTER);  // 设置为主库
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 2.5 使用动态数据源

在服务类中使用动态数据源:

@Service  // 标记为服务类
public class UserService {
    @Autowired
    private UserMapper userMapper;  // 注入统一的 Mapper
    
    @Master  // 指定使用主库
    public void createUser(User user) {
        userMapper.insert(user);  // 写操作使用主库
    }
    
    @Slave  // 指定使用从库
    public List<User> getAllUsers() {
        return userMapper.findAll();  // 读操作使用从库
    }
    
    // 未添加注解,会根据方法名决定使用哪个数据源
    public User findById(Long id) {
        return userMapper.findById(id);  // 方法名以 find 开头,自动路由到从库
    }
    
    // 未添加注解,会根据方法名决定使用哪个数据源
    public void updateUser(User user) {
        userMapper.update(user);  // 方法名以 update 开头,自动路由到主库
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 3. 使用第三方库实现多数据源和读写分离

# 3.1 Baomidou Dynamic Datasource

Baomidou Dynamic Datasource 是一个专为 MyBatis 设计的动态数据源切换框架,提供了更简单的配置和使用方式。

引入依赖

根据 Spring Boot 版本选择合适的依赖:

<!-- Spring Boot 2.x -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.2</version>  <!-- 使用最新版本 -->
</dependency>

<!-- Spring Boot 3.x (JDK 17+) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
    <version>3.5.2</version>  <!-- 使用最新版本 -->
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13

配置数据源

在 application.yml 中配置多数据源:

spring:
  datasource:
    dynamic:
      primary: master  # 设置默认数据源
      strict: false    # 是否严格匹配数据源,false 表示找不到数据源时使用默认数据源
      datasource:
        master:  # 主库配置
          url: jdbc:mysql://localhost:3306/master_db
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1:  # 从库1配置
          url: jdbc:mysql://localhost:3307/slave_db
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_2:  # 从库2配置
          url: jdbc:mysql://localhost:3308/slave_db
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

使用 @DS 注解切换数据源

Baomidou Dynamic Datasource 提供了 @DS 注解来指定使用哪个数据源:

@Service  // 标记为服务类
public class UserService {
    
    @DS("master")  // 使用主库数据源
    public void createUser(User user) {
        userMapper.insert(user);  // 写操作使用主库
    }
    
    @DS("slave_1")  // 指定使用从库1
    public List<User> getAllUsers() {
        return userMapper.findAll();  // 读操作使用从库1
    }
    
    @DS("slave_2")  // 指定使用从库2
    public User getUserById(Long id) {
        return userMapper.findById(id);  // 使用从库2查询
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

使用 @Master 和 @Slave 注解

Baomidou Dynamic Datasource 还提供了预定义的 @Master 和 @Slave 注解,这些注解是 @DS 的特定形式:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DS("master")  // 底层使用 @DS("master")
public @interface Master {
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DS("slave")  // 底层使用 @DS("slave")
public @interface Slave {
}
1
2
3
4
5
6
7
8
9
10
11
12
13

使用这些注解:

@Service  // 标记为服务类
public class UserService {
    
    @Master  // 使用主库数据源
    public void createUser(User user) {
        userMapper.insert(user);  // 写操作使用主库
    }
    
    @Slave  // 使用从库数据源,会自动在从库组中负载均衡
    public List<User> getAllUsers() {
        return userMapper.findAll();  // 读操作使用从库
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

配置数据源组和负载均衡

可以将多个从库配置为一个组,并设置负载均衡策略:

spring:
  datasource:
    dynamic:
      primary: master
      datasource:
        master: # 主库
          url: jdbc:mysql://localhost:3306/master_db
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_1: # 从库1
          url: jdbc:mysql://localhost:3307/slave_db
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave_2: # 从库2
          url: jdbc:mysql://localhost:3308/slave_db
          username: root
          password: password
          driver-class-name: com.mysql.cj.jdbc.Driver
      # 配置从库组
      strategy: com.baomidou.dynamic.datasource.strategy.LoadBalanceDynamicDataSourceStrategy  # 负载均衡策略
      # 设置数据源组
      ds-group:
        slave: # slave 组包含 slave_1 和 slave_2
          - slave_1
          - slave_2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

然后可以使用 @DS("slave") 或 @Slave 注解选择从库组,框架会自动在从库之间进行负载均衡。

# 3.2 使用 ShardingSphere 实现读写分离

Apache ShardingSphere 是一个功能更加全面的数据库中间件,除了读写分离外,还支持分库分表、数据加密等高级功能。

引入依赖

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>5.3.2</version>  <!-- 使用最新版本 -->
</dependency>
1
2
3
4
5

配置读写分离

在 application.yml 中配置 ShardingSphere 的读写分离:

spring:
  shardingsphere:
    mode:
      type: Standalone  # 运行模式:单机模式
      repository:
        type: JDBC
    datasource:
      names: master,slave1,slave2  # 数据源名称
      # 主库配置
      master:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/master_db
        username: root
        password: password
      # 从库1配置
      slave1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3307/slave_db
        username: root
        password: password
      # 从库2配置
      slave2:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3308/slave_db
        username: root
        password: password
    # 规则配置
    rules:
      # 读写分离规则
      readwrite-splitting:
        data-sources:
          readwrite_ds:  # 读写分离数据源名称
            type: Static  # 静态读写分离
            props:
              write-data-source-name: master  # 写数据源名称
              read-data-source-names: slave1,slave2  # 读库数据源名称列表
            load-balancer-name: round_robin  # 负载均衡算法名称
        # 负载均衡算法配置
        load-balancers:
          round_robin:  # 负载均衡算法名称
            type: ROUND_ROBIN  # 轮询算法
    # 属性配置
    props:
      sql-show: true  # 显示SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

使用 ShardingSphere 实现的读写分离

ShardingSphere 会根据 SQL 类型自动路由,无需添加额外注解:

@Service  // 标记为服务类
public class UserService {
    @Autowired
    private UserMapper userMapper;  // 注入 Mapper
    
    // 插入操作自动路由到主库
    public void createUser(User user) {
        userMapper.insert(user);  // 自动路由到主库
    }
    
    // 查询操作自动路由到从库
    public List<User> getAllUsers() {
        return userMapper.findAll();  // 自动路由到从库
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

强制使用主库

有时需要确保查询操作也使用主库(例如,需要强一致性的场景),可以使用 ShardingSphere 的 API:

@Service  // 标记为服务类
public class UserService {
    
    public User getLatestUserFromMaster(Long id) {
        // 设置强制使用主库
        HintManager.getInstance().setWriteRouteOnly();
        try {
            return userMapper.findById(id);  // 强制使用主库查询
        } finally {
            // 清除路由设置
            HintManager.clear();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

也可以封装为注解:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ForceMaster {
}

@Aspect
@Component
public class ShardingSphereAspect {
    @Around("@annotation(ForceMaster)")
    public Object aroundForceMaster(ProceedingJoinPoint joinPoint) throws Throwable {
        HintManager.getInstance().setWriteRouteOnly();  // 设置强制主库
        try {
            return joinPoint.proceed();  // 执行方法
        } finally {
            HintManager.clear();  // 清除设置
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4. 多数据源注解使用

在多数据源架构中,注解是实现数据源切换的关键。不同框架提供了不同的注解实现。

# 4.1 自定义实现中的注解

在自定义动态数据源实现中,通常会定义如下注解:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Master {
    // 可以添加属性,例如指定具体的主库
    String value() default "";
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Slave {
    // 可以添加属性,例如指定具体的从库
    String value() default "";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这些注解需要通过 AOP 拦截并处理,如前面动态数据源实现中的 DataSourceAspect。

# 4.2 Baomidou Dynamic Datasource 注解详解

Baomidou Dynamic Datasource 提供了更丰富的注解支持:

  1. @DS 注解:基础注解,用于指定数据源

    @DS("master")  // 使用主库
    public void updateUser(User user) { ... }
    
    @DS("slave_1")  // 使用特定从库
    public List<User> getAllUsers() { ... }
    
    @DS("slave")  // 使用从库组(负载均衡)
    public User getById(Long id) { ... }
    
    1
    2
    3
    4
    5
    6
    7
    8
  2. @Master 注解:指定使用主库的语法糖

    @Master  // 等同于 @DS("master")
    public void createOrder(Order order) { ... }
    
    1
    2
  3. @Slave 注解:指定使用从库的语法糖

    @Slave  // 等同于 @DS("slave")
    public List<Product> listProducts() { ... }
    
    1
    2
  4. 自定义数据源注解:可以创建更多语义化的注解

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @DS("sharding")  // 关联到分片数据源
    public @interface Sharding {
    }
    
    // 使用自定义注解
    @Sharding
    public List<Order> getOrdersBySharding() { ... }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# 4.3 ShardingSphere 中的数据源控制

ShardingSphere 主要通过 API 而非注解控制路由:

// 强制使用主库
HintManager.getInstance().setWriteRouteOnly();
try {
    return userMapper.findById(id);  // 强制使用主库查询
} finally {
    HintManager.clear();  // 清除设置
}
1
2
3
4
5
6
7

但可以封装为自定义注解:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ForceMaster {
}

@Aspect
@Component
public class ShardingSphereAspect {
    @Around("@annotation(ForceMaster)")
    public Object aroundForceMaster(ProceedingJoinPoint joinPoint) throws Throwable {
        HintManager.getInstance().setWriteRouteOnly();  // 设置强制主库
        try {
            return joinPoint.proceed();  // 执行方法
        } finally {
            HintManager.clear();  // 清除设置
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4.4 注解使用的最佳实践

  1. 明确的命名约定:使用 @Master/@Slave 或 @ReadWrite/@ReadOnly 这样语义明确的注解。
  2. 合理的默认行为:对未标注注解的方法,根据方法名称(如以 get/find/select/query 开头的方法使用从库)自动选择数据源。
  3. 注解的层次:可以在类级别设置默认数据源,在方法级别覆盖特定行为。
  4. 事务协同:确保与 Spring 的 @Transactional 注解协同工作,通常数据源切换应该在事务开始前完成。
  5. 监控与日志:为数据源切换添加日志,方便排查问题。

# 5. 多数据源方案选择

根据项目规模和需求选择合适的多数据源方案:

  1. 手动配置多数据源

    • 优点:直观、完全控制、适合简单场景
    • 缺点:代码冗余、难以维护、不够灵活
    • 适用场景:简单项目,数据源较少且业务逻辑简单
  2. 自定义动态数据源

    • 优点:灵活性高、可定制性强
    • 缺点:实现复杂、需要自行维护
    • 适用场景:对定制化要求高的中型项目
  3. Baomidou Dynamic Datasource

    • 优点:配置简单、注解丰富、功能完善
    • 缺点:与 MyBatis 绑定较紧
    • 适用场景:大多数 MyBatis 项目,特别是需要读写分离的场景
  4. ShardingSphere

    • 优点:功能全面、支持分库分表、性能优越
    • 缺点:配置较复杂、学习曲线陡峭
    • 适用场景:需要分库分表、读写分离等复杂功能的大型项目

选择建议:

  • 对于简单项目:优先考虑 Baomidou Dynamic Datasource
  • 对于复杂项目:选择 ShardingSphere
  • 需要高度定制化:自行实现动态数据源
  • 快速原型验证:手动配置多数据源

最终,应根据项目的具体需求、团队技术栈和长期维护成本综合考虑选择合适的多数据源方案。

编辑此页 (opens new window)
上次更新: 2025/03/16, 13:51:03
MyBatis 逆向工程
MyBatis 自定义拦截器

← MyBatis 逆向工程 MyBatis 自定义拦截器→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式