多数据源:多 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 三条线的归属理清楚。

笔记加载中…