java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot MyBatis-Plus多数据源并动态切换

SpringBoot+MyBatis-Plus实现配置多数据源并动态切换

作者:张老师技术栈

企业级项目中经常需要连接多个数据库——读写分离、分库分表、跨库查询,这一篇讲 SpringBoot + MyBatis-Plus 如何配置多数据源并动态切换,需要的朋友可以参考下

引言

企业级项目中经常需要连接多个数据库——读写分离、分库分表、跨库查询。这一篇讲 SpringBoot + MyBatis-Plus 如何配置多数据源并动态切换。

一、什么时候需要多数据源

读写分离:
  写操作 → master 库(主库)
  读操作 → slave 库(从库)
  好处:分摊主库压力
多库业务:
  用户数据 → user_db
  订单数据 → order_db
  好处:数据隔离,防止单库过大
第三方面对接:
  业务库 → 自己的 MySQL
  对接库 → 第三方的数据库

二、配置多数据源

1. 引入依赖

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

2. 配置多数据源

spring:
  datasource:
    dynamic:
      # 默认数据源(必须)
      primary: master
      # 是否启用严格模式(找不到数据源抛异常)
      strict: false
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/seckill_db?useUnicode=true&characterEncoding=utf-8
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
          url: jdbc:mysql://192.168.1.100:3306/seckill_db?useUnicode=true&characterEncoding=utf-8
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        order:
          url: jdbc:mysql://localhost:3306/order_db?useUnicode=true&characterEncoding=utf-8
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

不需要自己写配置类,引入依赖后只需要配置 yml 即可。

三、使用 @DS 切换数据源

1. 在 Service 上使用

@Service
@DS("master")  // 类级别:整个类默认走 master
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Override
    @DS("slave")  // 方法级别:这个方法走 slave
    public List<User> listUsers() {
        return this.list();
    }

    @Override
    @DS("master") // 写操作走 master
    public boolean saveUser(User user) {
        return this.save(user);
    }
}

2. 在 Mapper 上使用

@DS("order")
public interface OrderMapper extends BaseMapper<Order> {
    // 这个 Mapper 的所有操作都走 order 数据源
}

3. 执行原理

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;    // order 库
    @Autowired
    private UserService userService;    // master 库

    public OrderVO getOrderDetail(Long orderId) {
        // 查订单(自动走 order 数据源)
        Order order = orderMapper.selectById(orderId);

        // 查用户(自动切回 master)
        User user = userService.getById(order.getUserId());

        return merge(order, user);
    }
}

四、读写分离实战

@Service
@DS("master")  // 默认写库
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> {

    @DS("slave")  // 查询走从库
    public List<Product> listProducts() {
        return this.list();
    }

    @DS("slave")
    public Product getById(Long id) {
        return this.getById(id);
    }

    // 增删改不走 @DS,默认走 master
}

读写分离的好处:

单库:所有请求打到一个库
      1000 次查询 + 100 次写入 = 1100 次请求

读写分离:写入走主库,查询走从库
          主库:100 次写入
          从库:1000 次查询
          主库压力小 10 倍

五、多库事务

跨数据源的事务需要手动控制:

@Service
public class MultiDbService {

    @Autowired
    @Lazy
    private MultiDbService self;

    /**
     * 跨库操作:用户库 + 订单库
     * 不能用 @Transactional,需要用编程式事务
     */
    public void createUserAndOrder(User user, Order order) {
        // 使用 TransactionTemplate 控制事务
        TransactionTemplate userTx = new TransactionTemplate();
        TransactionTemplate orderTx = new TransactionTemplate();

        userTx.execute(status -> {
            try {
                // 插入用户(master 库)
                self.saveUser(user);
                // 插入订单(order 库)
                self.saveOrder(order);
                return null;
            } catch (Exception e) {
                status.setRollbackOnly();
                throw e;
            }
        });
    }

    @DS("master")
    public void saveUser(User user) {
        // 保存到用户库
    }

    @DS("order")
    public void saveOrder(Order order) {
        // 保存到订单库
    }
}

六、动态数据源切换原理

/**
 * @DS 注解的原理:
 * 通过 AOP 拦截,在执行前把数据源名称设置到 ThreadLocal 中
 * 执行完后清除
 */
@Aspect
@Component
public class DataSourceAspect {

    @Around("@annotation(ds)")
    public Object around(ProceedingJoinPoint pjp, DS ds) throws Throwable {
        // 切换数据源
        DynamicDataSourceContextHolder.push(ds.value());
        try {
            return pjp.proceed();
        } finally {
            // 使用完后清除
            DynamicDataSourceContextHolder.poll();
        }
    }
}

简化使用: 引入 dynamic-datasource 依赖后,AOP 逻辑已经内置,不需要自己写切面。

七、常见问题

1. 事务中切换失败

@Service
public class UserService {

    @Transactional  // 事务内
    public void doSomething() {
        // 此时数据源已经在事务开始时固定了
        // 内部的 @DS 切换不会生效!
    }
}

解决: 避免在事务中切换数据源,或者把不同数据源的操作拆分到不同 Service。

2. 多数据源 + MyBatis-Plus 分页

// MyBatis-Plus 分页插件在多数据源下仍然有效
// 只需要在配置类中配置一次即可
@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

3. 数据源配置加密

spring:
  datasource:
    dynamic:
      datasource:
        master:
          password: ENC(加密后的密码)  # 配合 Jasypt 加密

八、多数据源 vs 分库分表

方案适用场景复杂度
多数据源 @DS不同业务库、读写分离⭐ 简单
分库分表 ShardingSphere单表数据量过大⭐⭐⭐⭐
多数据源 + 分库分表大型项目⭐⭐⭐⭐⭐

建议: 项目初期读写分离用 @DS 完全够用,等数据量上亿后再考虑 ShardingSphere。

到此这篇关于SpringBoot+MyBatis-Plus实现配置多数据源并动态切换的文章就介绍到这了,更多相关SpringBoot MyBatis-Plus多数据源并动态切换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文