Skip to content

MyBatis-Plus 多数据源

  MyBatis-Plus 通过 dynamic-datasource-spring-boot-starter 提供多数据源支持,使用 @DS 注解即可实现动态数据源切换。

一、引入依赖

xml
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>4.2.0</version>
</dependency>

二、配置多数据源

yaml
spring:
  datasource:
    dynamic:
      primary: master  # 默认数据源
      strict: false    # 严格模式(找不到数据源时是否抛异常)
      datasource:
        # 主库
        master:
          url: jdbc:mysql://localhost:3306/db_master?useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        # 从库1
        slave1:
          url: jdbc:mysql://localhost:3307/db_slave1?useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        # 从库2
        slave2:
          url: jdbc:mysql://localhost:3308/db_slave2?useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        # Oracle
        oracle:
          url: jdbc:oracle:thin:@localhost:1521:ORCL
          username: system
          password: 123456
          driver-class-name: oracle.jdbc.OracleDriver

三、@DS 注解

java
// 类级别:整个类所有方法默认使用指定数据源
@Service
@DS("slave1")
public class UserService extends ServiceImpl<UserMapper, User> {
    // 默认查询从库
}

// 方法级别:覆盖类级别配置
@Service
public class UserService extends ServiceImpl<UserMapper, User> {

    // 读操作 → 从库
    @DS("slave1")
    public User getById(Long id) {
        return super.getById(id);
    }

    @DS("slave2")
    public List<User> list() {
        return super.list();
    }

    // 写操作 → 主库
    @DS("master")
    public boolean save(User user) {
        return super.save(user);
    }
}

四、读写分离

4.1 手动指定

java
@Service
public class UserService extends ServiceImpl<UserMapper, User> {

    @DS("master")
    public boolean createUser(User user) {
        return save(user);
    }

    @DS("slave1")
    public User getUserById(Long id) {
        return getById(id);
    }
}

4.2 AOP 自动切换

java
@Aspect
@Component
public class DataSourceAspect {

    /**
     * 读方法走从库,写方法走主库
     */
    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        DynamicDataSourceContextHolder.push("master");
        try {
            return point.proceed();
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }

    @Before("execution(* com.example.service.*.get*(..)) " +
            "|| execution(* com.example.service.*.select*(..)) " +
            "|| execution(* com.example.service.*.list*(..)) " +
            "|| execution(* com.example.service.*.find*(..)) " +
            "|| execution(* com.example.service.*.query*(..))")
    public void setReadDataSource() {
        DynamicDataSourceContextHolder.push("slave1");
    }

    @After("execution(* com.example.service.*.get*(..)) " +
            "|| execution(* com.example.service.*.select*(..)) " +
            "|| execution(* com.example.service.*.list*(..)) " +
            "|| execution(* com.example.service.*.find*(..)) " +
            "|| execution(* com.example.service.*.query*(..))")
    public void clearDataSource() {
        DynamicDataSourceContextHolder.poll();
    }
}

五、多数据源事务

java
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;      // 主库
    @Autowired
    private InventoryMapper inventoryMapper; // 从库

    // 注意:@DS 不能和 @Transactional 一起在同一个方法上
    // 多数据源事务需要分布式事务方案(如 Seata)

    @DS("master")
    public void createOrder(Order order) {
        orderMapper.insert(order);
    }

    @DS("slave1")
    public List<Order> queryOrders() {
        return orderMapper.selectList(null);
    }
}

注意: 多数据源使用本地事务时,@Transactional 只能管理默认数据源的事务。跨数据源操作需要分布式事务。

六、动态添加数据源

java
@Component
public class DynamicDataSourceManager {

    @Autowired
    private DataSource dataSource;

    /**
     * 运行时动态添加数据源
     */
    public void addDataSource(String name, String url, String username, String password) {
        DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;

        DataSourceProperty property = new DataSourceProperty();
        property.setUrl(url);
        property.setUsername(username);
        property.setPassword(password);
        property.setDriverClassName("com.mysql.cj.jdbc.Driver");

        DataSource newDataSource = new DynamicDataSourceCreator().createDataSource(property);
        ds.addDataSource(name, newDataSource);
    }
}

七、多数据源原理

dynamic-datasource 原理:

  ① 启动时加载所有数据源配置
     → 创建 DynamicRoutingDataSource(路由数据源)

  ② @DS 注解解析
     → 通过 AOP 拦截,在执行方法前设置数据源

  ③ 数据源切换
     → DynamicDataSourceContextHolder 使用 ThreadLocal 存储当前数据源名
     → DynamicRoutingDataSource 根据名称获取对应的 DataSource

  ④ 方法执行完毕后清除
     → 恢复默认数据源

  数据源切换流程:
    @DS("slave") → AOP 拦截 → ThreadLocal.push("slave")
    → 获取 DataSource → 执行 SQL → ThreadLocal.poll()

八、速记总结

多数据源三步走:
  ① 加依赖:dynamic-datasource-spring-boot-starter
  ② 配数据源:spring.datasource.dynamic.datasource.xxx
  ③ 加注解:@DS("数据源名") 在类或方法上

读写分离:
  主库 → @DS("master")
  从库 → @DS("slave")

速记口诀:
  动态数据源配置过,@DS 注解指定库,
  类上默认方法覆盖,主库写从库读。

九、面试要点

问题答案要点
多数据源怎么实现?dynamic-datasource + @DS 注解
@DS 注解可以加在哪?类上(默认)或方法上(覆盖)
多数据源事务怎么办?本地事务只能管一个,跨库需要分布式事务
原理是什么?AOP 拦截 + ThreadLocal 存数据源名 + 路由数据源