微信优化

weixinyouhua

MyBatis多数据源怎么配置?,多数据源配置教程

2026-08-29 19:10:48

MyBatis配置多个数据源的核心答案是:从根源上杜绝多数据源混乱,本质是让框架按业务场景精确路由到对应数据库,而非在代码中硬编码连接。这件事在Java服务端开发中属于高频刚需,无论是读写分离、分库分表,还是对接多个异构业务库,都必须掌握。

mybatis多数据源配置的核心原理与设计思路

理解多数据源的原理,比照搬配置更重要,通俗地说,MyBatis本身不直接管理数据源,它依赖SqlSessionFactory去操作数据库,多个数据源,就意味着要构建多个SqlSessionFactory,每个对应一个独立的数据源。

为什么不能在一个SqlSessionFactory里塞两个数据源

不少新手朋友踩过这个坑,试图在yml里配置两个连接,但只建了一个SqlSessionFactory,结果就是框架只认一个DataSource Bean,另一个配置形同虚设,Spring容器在注入时会因为存在两个DataSource类型的Bean而直接启动报错expected single matching bean but found 2,行业共识是,一个SqlSessionFactory绑定一个数据源,配置多个数据源的本质就是维护一组SqlSessionFactory,或者通过动态路由机制在运行时切换上下文。

方案选型对比:静态多工厂与动态路由

多数据源实现方案主要有两种,适合不同的业务场景。

方案 实现方式 适用场景 缺点
静态多工厂 手工配置多个SqlSessionFactory,通过包名或注解区分 数据源固定、业务边界清晰的项目 扩展性一般,代码侵入稍高
动态路由 基于Spring的AbstractRoutingDataSource实现 读写分离、多租户、Saas应用 事务处理复杂,需额外防护

动态路由是目前的主流方式,它利用一个determineCurrentLookupKey()方法返回路由key,框架根据key自动找到对应的数据源,但要注意,动态路由解决的是连接切换,不解决事务边界,你可能需要深入理解Spring的事务同步机制,才能让路由上下文的传播行为符合预期这里的核心是数据源路由信息通常在服务启动时就需要初始化好,若是在事务运行期间动态改变路由key,容易出现连接错乱。

mybatis配置多个数据源的实操步骤与标准流程

采用动态路由方案时,你需要完成三个关键模块:数据源配置、路由决策器、为MyBatis装配Mapper扫描策略,以Spring Boot + MyBatis为例,按以下流程走基本不会出错。

第一步:定义数据源连接信息(yml配置)

spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/main_db
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      jdbc-url: jdbc:mysql://localhost:3306/analysis_db
      username: reader
      password: 654321
      driver-class-name: com.mysql.cj.jdbc.Driver

需要留意的是,多数据源配置时不能使用Spring Boot默认的spring.datasource.url通用写法,因为DataSourceProperties会尝试绑定同一个前缀,导致两个数据源拿到相同的连接信息,必须使用自定义前缀分别绑定。

第二步:编写DataSourceConfig核心配置类

这里必须拆开读,你需要构建两对组件:

  • 主数据源的DataSource、SqlSessionFactory、SqlSessionTemplate
  • 从数据源的DataSource、SqlSessionFactory、SqlSessionTemplate

MyBatis多数据源怎么配置?,多数据源配置教程

@Configuration public class DataSourceConfig { @Bean(name = "primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "routingDataSource") public DataSource routingDataSource() { RoutingDataSource routing = new RoutingDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("primary", primaryDataSource()); targetDataSources.put("secondary", secondaryDataSource()); routing.setDefaultTargetDataSource(primaryDataSource()); routing.setTargetDataSources(targetDataSources); return routing; } }

工程的合理划分层次上,建议再单独写一个MyBatisConfig,只装配MyBatis需要的那份SqlSessionFactory。

第三步:实现动态路由决策器

这部分是核心连接逻辑。路由决策器的作用,是从当前业务上下文中获取一个标识,决定本次请求走主库还是从库,这一步骤直接关系到线上数据流向是否错乱。

@Slf4j
public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceKey = DataSourceContextHolder.getDataSource();
        log.debug("当前数据源路由key为: {}", dataSourceKey);
        return dataSourceKey;
    }
}

DataSourceContextHolder是一个ThreadLocal封装,在进入Service方法之前,手动设置需要使用的数据源主键,用完后必须清理,否则线程池复用时,后续请求会拿到别人的数据源上下文这种错误在线上排查时特别磨人。稳妥的写法是在@Around或自定义AOP中try-finally保证清理。

第四步:配置Mapper扫描与SqlSessionFactory关联

现代的Spring Boot集成环境中,除了自定义动态数据源配置,还同时需要避免包扫描冲突,你的Mapper接口分布在不同的包下,必须手动指定@MapperScan的SqlSessionTemplateRef,否则MyBatis会尝试使用默认唯一的SqlSessionFactory,注入时因为多个SqlSessionFactory存在而报错。

@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionTemplateRef = "primarySqlSessionTemplate")
public class PrimaryMyBatisConfig {
    // 构建primary SqlSessionFactory
}
@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary", sqlSessionTemplateRef = "secondarySqlSessionTemplate")
public class SecondaryMyBatisConfig {
    // 构建secondary SqlSessionFactory
}

这一步是很多项目启动报错的直接原因,建议在构建SqlSessionFactory时显式指定mapper-locations,防止找不到XML文件。

mybatis配置多个数据源时的事务管理与失效分析

多数据源场景中,事务是重灾区。默认的Spring声明式事务依赖当前线程持有的数据库连接,一旦跨越两个数据源,DataSourceTransactionManager只能管一个连接,如果你在主库事务中调用了从库Mapper,从库的操作将脱离事务控制,出现半成功状态。

本地事务方案:避免跨源业务编排

最务实的做法是避免在同一个事务中跨数据源操作,业内专家指出,多数据源环境下,将事务边界控制在单个数据源操作范围内,是降低复杂度的根本手段,如果业务必须同时写两个库,需要引入分布式事务(如Seata、DTM)或采用本地消息表+最终一致性方案,分布式事务的代价很大,能绕则绕。

MyBatis多数据源怎么配置?,多数据源配置教程

动态路由与事务传播的冲突问题

一个常见的误区:在事务开启后,通过AOP修改ThreadLocal中的路由key,期望切换数据源,Spring事务管理器会在事务开始时提前获取并绑定连接,后续切换路由key也不会重取连接,结果是事务内数据源切换失效,解决办法:

  • 事务方法内部不要调用切换逻辑,把数据源切换放在事务边界之前完成
  • 使用编程式事务TransactionTemplate,在每次操作前手动指定路由key

多数据源下MyBatis二级缓存注意事项

多SqlSessionFactory意味着二级缓存独立,如果一个Mapper同时操作不同数据源,缓存结果可能错乱,二级缓存默认namespace级别,如果多个SqlSessionFactory的mapperLocations覆盖了同一个Mapper接口,缓存会失控,典型踩坑场景:主库和从库都建了同名的订单Mapper,二级缓存开启后,主库更新订单,从库却读到了旧缓存数据,建议在多数据源环境下,全局关闭二级缓存,或对每个SqlSessionFactory单独配置cache-enabled=false。

mybatis多数据源配置的性能优化与查询瓶颈排查

配置完成后,性能问题会逐渐暴露,多数据源带来的不仅是连接数翻倍,还有连接池竞争和SQL路由开销。

连接池参数调整思路

通常每个数据源都会独立配置连接池(HikariCP默认值按单库设计),多数据源时,总连接数等于各数据源连接数之和,数据库端max_connections此时容易成为瓶颈,例如主库和从库各配20个连接,但数据库服务端最大连接数只有50,此时高并发下必然报“Too many connections”,建议:

  • 按业务调用比例分配连接池大小,核心链路给大池,低频分析给小池
  • 在test环境用JMeter压测,找到连接池和数据库上限的匹配值
  • 设置合理的connection-timeout,避免线程无限等待

数据库连接亲和性对稳定性影响分析

当动态路由频繁切换主从连接时,每切换一次,连接就从连接池中取出一个不同数据源的连接,长时间的连接切换加剧了连接池的分片碎片化,导致连接的冷启动增加,若主从数据源的连接在物理上分布在不同机房,这种切换还可能显著放大网络延迟的抖动多数情况下,单次查询的额外损耗并不大,但在高QPS场景,系统压力会被放大,两个思路:

  • 在AOP切面中加入路由缓存的ThreadLocal复用机制,同一请求周期尽量复用同一路由
  • 对真实环境做压测对比单数据源与多数据源的吞吐差异,设定合理的容量预警水位

排查和诊断:多数据源慢SQL定位

慢SQL排查比想象中更复杂,因为数据库层面可能分主从,你需要:

  • 在yml中配置mybatis.configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl,观察实际执行的JDBC URL
  • 在数据源包装层打印DataSourceUtils.getConnection(dataSource)获取的Connection对象看它的hashcode与哪个连接池相关
  • 动态路由后,通过HikariDataSource.getPoolName()标识连接归属,快速定位慢SQL落到哪个库

核心数值参考:事务隔离级别影响

多数据源场景下,主库和从库的事务隔离级别可能不一致(主库RC、从库RR),这会导致同一段代码在不同数据源上表现不同,建议在创建数据源时显式统一隔离级别,避免业务逻辑在测试环境中正常,上生产后出现不可重复读的偶发问题,如果运维层面无法统一,最好在代码中指定默认隔离级别。

MyBatis多数据源怎么配置?,多数据源配置教程

mybatis多数据源配置的常见报错与应对

配置结束后,部分报错的排查步骤比较固定,按顺序检查通常能快速定位。

启动报错:Invalid value type for attribute ‘factoryBeanObjectType’

这一般在Spring Boot 2.7+ 与MyBatis Starter适配过程中出现,原因是在多个SqlSessionFactory时,bean定义解析器拿到多个FactoryBean候选,类型判断出错,解决办法:

  • 使用明确的@Primary标记默认SqlSessionFactory
  • 检查mybatis-spring-boot-starter与mybatis-spring的版本兼容性
  • 在@MapperScan中显式指定sqlSessionFactoryRef

运行时报错:Connection is not available, request timed out

连接池被耗尽,重点检查是否在注解事务方法中包含了动态数据源切换逻辑,导致连接长期占用未释放,另一个隐蔽原因是ThreadLocal未清理,导致高并发时路由上下文错乱,所有请求全打到同一个数据源上。

事务回滚失效:异常时数据未回滚

检查以下几点:

  • 是否配置了DataSourceTransactionManager,而非Spring Boot默认的单一事务管理器
  • 若存在多事务管理器,是否在主事务管理器上标注@Primary
  • @Transactional是否作用于public方法
  • 是否捕获了异常但未抛出,导致Spring感知不到回滚信号这是人为疏忽概率较高的一个点

多数据源配置成本评估与落地建议

对于中小规模项目,是否需要mybatis多数据源配置,可以按业务需求判断,基于功能需求而言,如果只是读多写少,先考虑引入读写分离中间件(如MySQL主从复制+ShardingSphere),而不是直接改应用代码,这类中间件对应用透明,减少事务和路由的代码侵入。

只有当业务确实需要访问多个异构数据库(比如MySQL+PostgreSQL),或者分库分表维度明确时,才值得投入时间改造。整体配置成本集中在事务管理和连接池调优上,纯配置本身不超过半天工时。

  • 新人上手建议先从静态多工厂方案开始,理解每个配置项的作用
  • 多次迭代后若配置冗余膨胀,可结合基础配置抽取公共依赖库
  • 为数据源配置模块编写框架级别的自动化测试,确保多数据源报告可追踪

常见问题解答(Q&A)

mybatis多数据源配置时,两个DataSource Bean会导致启动报错吗?

会,Spring容器中存在两个DataSource类型的Bean时,如果没有标注@Primary或命名规则不明确,DataSourceAutoConfiguration会因无法决定主数据源而启动报错,解决办法是手动定义数据源配置类,显式声明@Primary标注主数据源,并通过@MapperScan分别绑定不同的SqlSessionFactory。

mybatis动态数据源事务不生效,怎么排查比较快?

以三大黄金步骤为主:先确认当前操作是否跨越了两个不同数据源;然后检查@Transactional所在的方法是不是被代理调用的入口;最后打印TransactionSynchronizationManager.getResourceMap(),查看当前线程绑定了哪些数据源的连接,最常用的验收思路是:尝试去事务中修改路由key,观察日志中连接对象是否变化,不变则说明事务已提前截取连接,需要通过解耦改写。

相关文章

2024年,SaaS软件行业碰到获客难、增长慢等问题吗?

我们努力让每一次邂逅总能超越期待