SpringBoot集成Dynamic-Datasource:从配置到实战的多数据源管理
1. 为什么需要多数据源管理在实际开发中单数据源往往无法满足复杂业务需求。比如电商系统需要同时连接订单库和用户库或者需要实现读写分离来提升性能。我之前做过一个内容管理平台就遇到了需要同时操作主库和日志库的情况。这时候如果还死磕单数据源就像用一把钥匙想开所有门既不现实也不安全。baomidou的dynamic-datasource组件就像个智能钥匙串能根据场景自动切换对应的钥匙。它通过注解方式实现数据源切换对代码侵入性极小。我实测下来从单数据源改造到支持多数据源核心业务代码几乎不用改动只需要在配置文件和DAO层稍作调整。2. 快速集成到SpringBoot项目2.1 添加Maven依赖首先在pom.xml中加入starter依赖建议用最新稳定版。我当前项目用的是4.1.3版本dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version4.1.3/version /dependency注意不要和mybatis-plus的版本冲突。有次我升级mybatis-plus到3.5.6时发现不兼容回退到3.5.3才正常。建议在引入前先检查版本兼容性矩阵。2.2 配置多数据源参数在application.yml中配置主从数据源。这里有个坑要注意从SpringBoot 2.4开始配置格式有变化老项目迁移时需要调整缩进spring: datasource: dynamic: primary: master # 默认数据源 strict: false # 是否严格匹配数据源 datasource: master: url: jdbc:mysql://localhost:3306/main_db username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave_1: url: jdbc:mysql://192.168.1.100:3306/log_db username: log_user password: log_123 driver-class-name: com.mysql.cj.jdbc.Driver建议把密码等敏感信息放到单独的配置中心。我在生产环境就遇到过配置泄露的问题后来改用Vault管理密码就安全多了。3. 注解驱动的数据源切换3.1 DS注解使用技巧在DAO层或Service方法上添加DS注解即可切换数据源。比如日志记录走从库Repository DS(slave_1) public interface LogRepository extends JpaRepositoryLogEntity, Long { // 自动使用slave_1数据源 }也可以细化到方法级别。我在审计服务中就这么用Service public class AuditService { DS(master) public void writeAudit(Audit audit) { // 写操作走主库 } DS(slave_1) public PageAudit queryAudits(Pageable pageable) { // 读操作走从库 } }3.2 动态切换的底层原理组件通过DynamicRoutingDataSource继承Spring的AbstractRoutingDataSource内部维护着数据源映射表。当执行数据库操作时查找当前线程是否绑定了数据源通过DS注解指定没有注解则使用primary指定的默认数据源从dataSourceMap中获取真实DataSource对象创建数据库连接执行SQL我通过断点调试发现每次切换都会先检查数据源是否存在。如果stricttrue时找不到对应数据源会直接报错false则降级到默认数据源。4. 实战中的进阶用法4.1 读写分离实现方案配置主从数据源后配合DS注解可以轻松实现读写分离Service public class UserService { Autowired private UserMapper userMapper; DS(master) public void createUser(User user) { userMapper.insert(user); // 写操作 } DS(slave_1) public User getUser(Long id) { return userMapper.selectById(id); // 读操作 } }但要注意主从同步延迟问题。有次用户注册后立即查询由于主从延迟导致查不到数据。后来我们在需要强一致性的场景改用主库查询DS(master) public User getFreshUser(Long id) { // 强制走主库查询 }4.2 多租户数据隔离通过动态数据源可以实现SaaS系统的租户隔离。我在物业管理系统中的做法是每个租户有独立schema登录时根据租户ID动态注册数据源通过AOP自动切换数据源Aspect Component public class TenantDataSourceAspect { Around(annotation(tenantScope)) public Object switchDataSource(ProceedingJoinPoint pjp) throws Throwable { String tenantId TenantContext.getCurrentTenant(); DynamicDataSourceContextHolder.push(tenantId); try { return pjp.proceed(); } finally { DynamicDataSourceContextHolder.poll(); } } }4.3 事务处理的注意事项多数据源下的事务需要特别小心。默认情况下没有Transactional注解时每个DAO方法独立事务有Transactional时整个方法使用同一个数据源首次访问的数据源我在订单服务中踩过坑Transactional public void createOrder(Order order) { orderDao.insert(order); // 使用master数据源 logDao.addLog(order); // 也需要master但实际用了slave }解决方案是添加DS注解指定主数据源DS(master) Transactional public void createOrder(Order order) { // 现在所有操作都走master }5. 性能优化与问题排查5.1 连接池配置技巧每个数据源可以独立配置连接池参数。我通常会给主库分配更多连接master: url: jdbc:mysql://localhost:3306/main_db hikari: maximum-pool-size: 20 minimum-idle: 5 slave_1: url: jdbc:mysql://192.168.1.100:3306/log_db hikari: maximum-pool-size: 10 minimum-idle: 2监控发现从库QPS较低时适当减少连接数可以节省资源。5.2 常见问题解决方案问题1启动时报找不到数据源检查项配置缩进是否正确数据源名称是否与DS注解匹配数据库服务是否可达问题2事务不生效检查项是否在同一个类内调用带Transactional的方法是否在DS注解的类上同时使用Transactional问题3性能突然下降检查项是否忘记清理DynamicDataSourceContextHolder连接池是否耗尽是否有跨数据源关联查询6. 扩展与替代方案除了dynamic-datasource还可以考虑ShardingSphere更适合分库分表场景Spring AbstractRoutingDataSource自己实现更灵活的控制MyBatis插件机制通过拦截器实现数据源路由但经过对比测试对于大多数SpringBoot项目来说dynamic-datasource在易用性和功能完整性上都是最佳选择。特别是它完美兼容MyBatis-Plus减少了大量样板代码。