多数据源:多 SqlSessionFactory 与动态数据源切换
一个应用要同时连主库与报表库,或做读写分离时,就面对"多数据源"问题。MyBatis 多数据源有两种主流组织方式:每库一套 SqlSessionFactory(隔离彻底)与运行时动态切换(一个入口按需路由)。本节分别给出思路与关键配置。
思路一:每库一套 SqlSessionFactory
原则:一个 DataSource 对应一个 SqlSessionFactory,Mapper 按包分区互不串库。Spring 配置示意(细节以 mybatis-spring 官方文档为准):
@Bean(name = "masterDs")
public DataSource masterDs() { return buildDataSource("jdbc:mysql://host/db1"); }
@Bean(name = "masterSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDs") DataSource ds)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/master/*.xml"));
return bean.getObject();
}
Mapper 扫描时显式绑定各自的 factory:
@Configuration
@MapperScan(basePackages = "com.demo.mapper.master",
sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterConfig { }
事务也要分开:每个数据源配独立的 DataSourceTransactionManager,@Transactional(transactionManager = "masterTm") 指明用哪个。常见坑:漏配 sqlSessionFactoryRef,两个包共用一个 factory,连接互相拿错。
思路二:动态数据源切换
Mapper 不分包、想按方法/请求切换库时,用 AbstractRoutingDataSource 运行时选库:
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> KEY = new ThreadLocal<>();
public static void setKey(String k) { KEY.set(k); }
public static void clear() { KEY.remove(); }
@Override
protected Object determineCurrentLookupKey() {
return KEY.get(); // 每个线程记住当前要连哪个库
}
}
用前 setKey、用完 clear,配 AOP 切面统一收放最稳。生产上更省事的是用成熟组件:MyBatis-Plus 生态的 dynamic-datasource starter 提供 @DS("slave") 注解切换,读写分离、事务边界都已封装(以官方文档为准)。
对比与注意
| 方式 | 优点 | 缺点 |
|---|---|---|
| 多 SqlSessionFactory | 隔离清晰、各自独立配置 | 配置多、每库一套 Mapper |
| 动态路由(@DS) | 一套代码、切换灵活 | 依赖 ThreadLocal 与 AOP,注意清理 |
三个共性问题:事务内切换不生效(连接在事务开始时已绑定);AOP 切面顺序要先于事务;跨库事务需要分布式事务方案,普通多数据源解决不了。
多数据源没有银弹:库少、职责清晰就"每库一套 factory"硬隔离;读写分离、频繁切库就用动态路由。动手前先把连接、事务、Mapper 三条线的归属理清楚。