mybatis 的数据源切换是构建高可用、高性能数据库应用的关键技术。本文将从原理架构、核心代码实现、用法详解、初级进阶以及优势劣势等多个维度,结合具体代码示例,深入剖析这一技术。
一、原理架构
mybatis 的数据源管理核心在于 datasource 接口的抽象与路由。在单数据源场景下,配置直接指向一个具体的数据库连接池。而在多数据源场景下,我们需要引入一个"路由层",根据业务规则动态选择真正的数据源。
1.1 核心架构组件
下图展示了多数据源切换的整体架构,包括上下文管理、路由数据源以及实际的数据源工厂:
核心原理说明:
- configuration/environment:mybatis 的全局配置,传统方式下这里只能配置一个 environment。
- dynamicdatasource:这是实现切换的关键。在 spring 生态中,我们通常继承
abstractroutingdatasource。它内部维护了一个map<object, object>,存储了 key(如 “master”, “slave”)到真实datasource的映射。 - datasourcecontextholder:一个 threadlocal 工具类,用于在当前线程中存储数据源的 key,确保数据源选择在同一个线程内是透传的。
二、核心工作流程与代码实现
数据源切换并不是发生在 sql 解析阶段,而是发生在获取数据库连接(connection)的时刻。
2.1 工作流程时序图
下图展示了从 service 层调用到获取数据库连接的完整时序:

2.2 关键代码实现
要实现上述流程,我们需要编写三个核心部分:上下文管理器、动态数据源类和配置类。
上下文管理器 (datasourcecontextholder)
使用 threadlocal 保证线程隔离。
public class datasourcecontextholder {
private static final threadlocal<string> context_holder = new threadlocal<>();
/**
* 设置当前线程的数据源key
*/
public static void setdatasourcekey(string key) {
context_holder.set(key);
}
/**
* 获取当前线程的数据源key
*/
public static string getdatasourcekey() {
return context_holder.get();
}
/**
* 清除当前线程的数据源key(非常重要,防止内存泄漏和污染)
*/
public static void cleardatasourcekey() {
context_holder.remove();
}
}
动态数据源
继承 spring 的 abstractroutingdatasource,实现路由逻辑。
import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource;
public class dynamicdatasource extends abstractroutingdatasource {
@override
protected object determinecurrentlookupkey() {
// 直接从上下文中获取key
return datasourcecontextholder.getdatasourcekey();
}
}
三、动态数据源实现架构与配置
下图展示了我们在代码中需要构建的类体系及其依赖关系:

3.1 spring boot 配置示例
我们需要配置多个真实的数据源 bean,并将它们注入到 dynamicdatasource 中。
@configuration
public class datasourceconfig {
// 1. 配置主数据源
@bean
@configurationproperties(prefix = "spring.datasource.master")
public datasource masterdatasource() {
return datasourcebuilder.create().build();
}
// 2. 配置从数据源
@bean
@configurationproperties(prefix = "spring.datasource.slave")
public datasource slavedatasource() {
return datasourcebuilder.create().build();
}
// 3. 配置动态数据源(primary)
@bean
@primary
public dynamicdatasource dynamicdatasource(
@qualifier("masterdatasource") datasource masterdatasource,
@qualifier("slavedatasource") datasource slavedatasource) {
map<object, object> targetdatasources = new hashmap<>();
targetdatasources.put("master", masterdatasource);
targetdatasources.put("slave", slavedatasource);
dynamicdatasource dynamicdatasource = new dynamicdatasource();
// 设置目标数据源映射
dynamicdatasource.settargetdatasources(targetdatasources);
// 设置默认数据源(通常为主库)
dynamicdatasource.setdefaulttargetdatasource(masterdatasource);
return dynamicdatasource;
}
}
四、数据源切换策略与用法
如何决定使用哪个数据源?常见的策略包括注解驱动、方法名约定和手动切换。
下图梳理了常见的路由策略:

4.1 策略一:自定义注解 + aop(推荐)
这是最优雅的方式,对业务代码侵入性最小。
第一步:定义注解
@target({elementtype.method, elementtype.type})
@retention(retentionpolicy.runtime)
@documented
public @interface targetdatasource {
string value() default "master"; // 默认主库
}
第二步:定义 aop 切面
@aspect
@component
public class datasourceaspect {
// 拦截带有 @targetdatasource 注解的方法
@before("@annotation(targetdatasource)")
public void before(joinpoint point, targetdatasource targetdatasource) {
string datasourcekey = targetdatasource.value();
if (stringutils.isnotblank(datasourcekey)) {
datasourcecontextholder.setdatasourcekey(datasourcekey);
system.out.println("切换数据源到: " + datasourcekey);
}
}
// 执行完毕后清除上下文
@after("@annotation(targetdatasource)")
public void after(targetdatasource targetdatasource) {
datasourcecontextholder.cleardatasourcekey();
}
}
第三步:业务使用
@service
public class userservice {
@autowired
private usermapper usermapper;
// 写操作走主库
@targetdatasource("master")
public void adduser(user user) {
usermapper.insert(user);
}
// 读操作走从库
@targetdatasource("slave")
public user getuserbyid(long id) {
return usermapper.selectbyid(id);
}
}
4.2 策略二:手动编程式切换
在某些复杂的动态逻辑中(例如根据前端传入的参数决定租户库),可能需要手动切换。
public void processorder(long userid, order order) {
try {
// 1. 根据用户id计算分片key,切换到对应的用户库
string shardkey = "shard_" + (userid % 4);
datasourcecontextholder.setdatasourcekey(shardkey);
// 2. 执行业务操作(此时会路由到指定分片)
ordermapper.insert(order);
} finally {
// 3. 务必在finally中清理,避免影响后续操作
datasourcecontextholder.cleardatasourcekey();
}
}
五、进阶场景:读写分离架构
读写分离是多数据源最经典的应用场景。应用层通过 aop 或拦截器,自动将 select 请求发送至从库,insert/update/delete 发送至主库。下图展示了经典的读写分离架构:

实现思路:可以结合 mybatis 的 plugin(插件)机制,在 sql 执行前解析 sql 语句类型。如果以 select 开头,则调用datasourcecontextholder.setdatasourcekey("slave"),否则设为 master。
六、优势与劣势分析
任何技术方案都有其适用场景。下图详细对比了该技术的优势与劣势:

6.1 劣势详解:事务边界问题
这是多数据源最大的坑。spring 的 @transactional 注解是基于线程绑定的 connection 来管理的。 如果在一个事务方法中切换了数据源,spring 的事务管理器可能无法感知,或者导致事务失效。
错误示例:
@transactional // 开启事务,此时获取了 master 的连接
public void updateandread() {
usermapper.updateuser(user); // 使用 master 连接
// 尝试切换数据源
datasourcecontextholder.setdatasourcekey("slave");
// 问题:事务管理器仍持有 master 的连接,这里可能并不会真的切换,
// 或者抛出异常,因为 connection 已经绑定在事务中了。
usermapper.selectuser(id);
}
解决方案:
- 避免事务内切换:将读写操作拆分到不同的事务方法中。
- 编程式事务:使用
transactiontemplate精确控制事务边界。 - 分布式事务:如果必须跨库操作(如 master 写,slave 读并校验),需要引入 seata 等分布式事务框架,但这通常用于跨库写,读写分离场景下通常允许最终一致性。
七、配置实现全流程
下图描述了从零开始搭建动态数据源的完整配置步骤:

八、进阶应用场景与演进
随着业务复杂度的提升,数据源切换技术也衍生出了多种高级应用场景。
下图展示了多数据源技术在不同阶段的应用场景及演进:

技术演进时间线:

九、总结
mybatis 的数据源切换机制通过 abstractroutingdatasource 和 threadlocal 的巧妙结合,为我们提供了一种轻量级、灵活的数据路由方案。核心要点回顾:
- threadlocal 是灵魂:它保证了在多线程环境下,每个请求的数据源选择互不干扰。
- aop 是翅膀:通过切面将数据源选择逻辑从业务代码中剥离,保持代码整洁。
- 事务是双刃剑:在使用多数据源时,务必清醒地认识事务边界的限制,避免事务失效。
在实际项目中,建议从简单的读写分离开始,逐步根据业务需求引入更复杂的路由策略,并做好充分的监控与容错准备。
以上就是mybatis实现多数据源动态切换的核心方法介绍的详细内容,更多关于mybatis数据源切换的资料请关注代码网其它相关文章!
发表评论