告别数据孤岛:MyBatis 3多数据源整合实战指南
在企业级应用开发中,你是否经常面临需要同时操作多个数据库的困境?订单数据存在MySQL、用户信息存储在PostgreSQL、商品库存又放在Oracle——这种数据分散存储的场景正在成为常态。本文将通过3个实战方案,手把手教你用MyBatis 3实现多数据源无缝整合,解决跨库事务、连接管理和动态路由难题,让数据流动像水一样自然。
多数据源整合的核心挑战
多数据源架构下,应用需要解决三个关键问题:如何管理不同数据库的连接配置、如何动态选择目标数据源、以及如何保证跨库操作的数据一致性。MyBatis作为优秀的ORM框架,通过灵活的数据源接口设计为这些问题提供了基础解决方案。
MyBatis定义了标准的数据源工厂接口DataSourceFactory.java,所有数据源实现都必须遵循这个规范:
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
框架默认提供了三种实现:
- UnpooledDataSourceFactory.java:非池化数据源
- PooledDataSourceFactory.java:带连接池的数据源
- JndiDataSourceFactory.java:JNDI数据源
方案一:静态多数据源配置
最简单直接的实现方式是为每个数据源创建独立的SqlSessionFactory实例。这种方式适合数据源数量固定且不会频繁变化的场景,实现步骤如下:
- 配置多个数据源属性
在MyBatis配置文件中定义多个数据源环境:
<environments default="db1">
<!-- 主数据库 -->
<environment id="db1">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${db1.driver}"/>
<property name="url" value="${db1.url}"/>
<property name="username" value="${db1.username}"/>
<property name="password" value="${db1.password}"/>
</dataSource>
</environment>
<!-- 从数据库 -->
<environment id="db2">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${db2.driver}"/>
<property name="url" value="${db2.url}"/>
<property name="username" value="${db2.username}"/>
<property name="password" value="${db2.password}"/>
</dataSource>
</environment>
</environments>
- 创建多个SqlSessionFactory
通过指定环境ID构建不同的SqlSessionFactory:
// 主库SqlSessionFactory
SqlSessionFactory db1SessionFactory = new SqlSessionFactoryBuilder()
.build(reader, "db1");
// 从库SqlSessionFactory
SqlSessionFactory db2SessionFactory = new SqlSessionFactoryBuilder()
.build(reader, "db2");
- 使用对应SqlSession操作数据
// 操作主库
try (SqlSession session = db1SessionFactory.openSession()) {
UserMapper userMapper = session.getMapper(UserMapper.class);
userMapper.insert(user);
}
// 操作从库
try (SqlSession session = db2SessionFactory.openSession()) {
OrderMapper orderMapper = session.getMapper(OrderMapper.class);
orderMapper.selectById(orderId);
}
这种方式的优势是实现简单、易于理解,缺点是当数据源数量增加时,配置和维护成本会线性增长。MyBatis的DefaultSqlSessionFactory.java会根据环境配置创建对应的Executor实例,从而实现对不同数据源的操作隔离。
方案二:动态数据源路由
当需要在运行时根据业务逻辑动态选择数据源时,可以实现自定义的动态数据源路由。这种方式通过AOP和ThreadLocal技术实现数据源上下文切换,核心思路是:
- 实现动态数据源类
创建一个继承AbstractRoutingDataSource的类,重写determineCurrentLookupKey方法:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceKey();
}
}
- 创建数据源上下文持有者
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceKey(String key) {
CONTEXT_HOLDER.set(key);
}
public static String getDataSourceKey() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceKey() {
CONTEXT_HOLDER.remove();
}
}
- 配置数据源路由
<bean id="dynamicDataSource" class="com.example.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="db1" value-ref="dataSource1"/>
<entry key="db2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean>
- 使用AOP实现自动切换
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(dataSource)")
public void beforeSwitchDataSource(DataSource dataSource) {
DataSourceContextHolder.setDataSourceKey(dataSource.value());
}
@After("@annotation(dataSource)")
public void afterSwitchDataSource(DataSource dataSource) {
DataSourceContextHolder.clearDataSourceKey();
}
}
这种方案的核心在于通过MyBatis的SqlSessionFactory.java接口实现对数据源的统一管理,再结合Spring的事务管理机制,可以有效解决大多数动态数据源场景需求。SqlSessionManager.java提供了对SqlSession的线程安全管理,可以作为实现动态数据源路由的参考。
方案三:分布式事务处理
在多数据源环境下保证事务一致性是一个复杂问题。MyBatis本身不提供分布式事务支持,但可以与第三方事务管理器集成,如Atomikos、Bitronix等JTA实现。
集成JTA事务的基本步骤:
- 添加JTA依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
- 配置分布式数据源
@Bean
public DataSource dataSource1() {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setUniqueResourceName("db1");
ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
Properties props = new Properties();
props.setProperty("url", "jdbc:mysql://localhost:3306/db1");
props.setProperty("user", "root");
props.setProperty("password", "password");
ds.setXaProperties(props);
return ds;
}
- 配置JTA事务管理器
@Bean
public JtaTransactionManager transactionManager() {
return new JtaTransactionManager();
}
- 使用@Transactional注解
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 操作多个数据源的代码
orderMapper.insert(order);
inventoryMapper.decrease(order.getProductId(), order.getQuantity());
}
}
MyBatis的Transaction.java接口定义了事务操作的标准,而JdbcTransaction.java实现了基于JDBC的事务管理。在分布式场景下,这些本地事务会被JTA事务管理器协调,实现两阶段提交协议。
性能优化与最佳实践
无论采用哪种多数据源方案,都需要注意以下性能优化点:
- 合理配置连接池参数
对于PooledDataSourceFactory.java,关键参数包括:
- poolMaximumActiveConnections:最大活动连接数
- poolMaximumIdleConnections:最大空闲连接数
- poolMaximumCheckoutTime:连接检出超时时间
- 使用读写分离策略
将查询操作路由到从库,写操作路由到主库:
@DataSource("master")
void insertUser(User user);
@DataSource("slave")
User selectUserById(Long id);
- 缓存策略设计
多数据源环境下,二级缓存需要按数据源隔离:
<cache type="org.apache.ibatis.cache.impl.PerpetualCache" />
<cache-ref namespace="com.example.db1.UserMapper"/>
- 监控与日志
通过MyBatis的日志插件记录数据源切换过程:
<setting name="logImpl" value="STDOUT_LOGGING"/>
MyBatis的LoggingPlugin.java提供了详细的SQL执行日志,可以帮助追踪数据源使用情况。
总结与选型建议
三种多数据源方案各有适用场景:
| 方案类型 | 实现复杂度 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|---|
| 静态多数据源 | ★☆☆☆☆ | 数据源数量固定 | 简单直观,易于调试 | 配置冗余,不支持动态切换 |
| 动态数据源路由 | ★★★☆☆ | 需要按条件动态切换 | 灵活度高,使用方便 | 实现复杂,需处理线程安全 |
| 分布式事务 | ★★★★★ | 强一致性要求的跨库操作 | 保证数据一致性 | 性能开销大,配置复杂 |
在实际项目中,可以根据业务复杂度和一致性要求选择合适的方案。大多数业务场景下,动态数据源路由能够满足需求,而对于金融交易等核心业务,则需要引入分布式事务机制。
MyBatis通过SqlSessionManager.java提供了线程安全的SqlSession管理,结合本文介绍的多数据源方案,可以构建强大而灵活的数据访问层。随着微服务架构的普及,多数据源整合能力将成为后端开发工程师的必备技能。
最后,建议通过官方文档configuration.md深入了解MyBatis的配置选项,以及通过SqlSessionFactoryBuilder.java的源码学习MyBatis的初始化流程,这将帮助你更好地理解数据源配置的底层实现。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



