目录
一、为什么需要多数据源?
二、5大主流实现方案对比
三、方案实现详解
方案1:手动配置多DataSource(基础版)
方案2:AbstractRoutingDataSource(动态路由)
方案3:MyBatis-Plus多数据源(推荐)
方案4:JPA多数据源配置
方案5:ShardingSphere(企业级方案)
四、事务管理解决方案
1. 分布式事务(XA协议)
2. BASE柔性事务
五、性能优化策略
1. 连接池配置优化
2. 读写分离权重配置
3. 多数据源监控
六、生产环境避坑指南
1. 连接泄露检测
2. 多数据源启动顺序
3. 跨库关联查询
七、方案选型决策树
八、总结与最佳实践
在复杂业务场景下,多数据源成为刚需:
方案 | 核心思路 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
手动配置多DataSource | 显式创建多个DataSource Bean | 实现简单,直观可控 | 事务管理复杂,需手动切换 | 简单场景(2-3个数据源) |
AbstractRoutingDataSource | 动态路由数据源 | 灵活切换,无代码侵入 | 需解决事务传播问题 | 动态数据源场景(多租户) |
MyBatis-Plus多数据源 | 注解驱动切换 | 开箱即用,集成度高 | 依赖特定框架 | MyBatis技术栈项目 |
JPA多数据源 | 实体管理器工厂分离 | 符合JPA标准 | 配置繁琐,性能开销 | 纯JPA项目 |
ShardingSphere | 分布式数据库中间件 | 功能强大,支持分库分表 | 学习曲线陡峭 | 大型分布式系统 |
实现步骤:
@Configuration
public class DataSourceConfig {
// 主数据源
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
// 从数据源
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
}
// 使用示例
@Service
public class UserService {
private final JdbcTemplate masterJdbcTemplate;
private final JdbcTemplate slaveJdbcTemplate;
public UserService(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
this.masterJdbcTemplate = new JdbcTemplate(master);
this.slaveJdbcTemplate = new JdbcTemplate(slave);
}
public void updateUser(User user) {
// 写操作使用主库
masterJdbcTemplate.update("UPDATE user SET name=? WHERE id=?",
user.getName(), user.getId());
}
public User getUser(long id) {
// 读操作使用从库
return slaveJdbcTemplate.queryForObject(
"SELECT * FROM user WHERE id=?",
new BeanPropertyRowMapper<>(User.class),
id);
}
}
事务管理挑战:
@Transactional // 默认只能使用主数据源
public void crossDbOperation() {
// 操作主库
masterRepo.save(entity);
// 操作从库(将使用错误的数据源!)
slaveRepo.query(...);
}
核心实现:
public class DynamicDataSource extends AbstractRoutingDataSource {
// 线程安全的数据源上下文
private static final ThreadLocal CONTEXT =
new ThreadLocal<>();
public static void setDataSource(String ds) {
CONTEXT.set(ds);
}
public static void clear() {
CONTEXT.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return CONTEXT.get();
}
}
// 配置类
@Bean
public DataSource dynamicDataSource(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
Map
使用AOP自动切换:
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(readOnly)")
public void setReadOnly(ReadOnly readOnly) {
DynamicDataSource.setDataSource("slave");
}
@After("@annotation(readOnly)")
public void clearDataSource(ReadOnly readOnly) {
DynamicDataSource.clear();
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {}
// 业务层使用
@Service
public class UserService {
@ReadOnly
public User getUser(long id) {
// 自动使用从库
return userMapper.selectById(id);
}
}
依赖引入:
com.baomidou
dynamic-datasource-spring-boot-starter
3.6.1
配置示例:
spring:
datasource:
dynamic:
primary: master
strict: true
datasource:
master:
url: jdbc:mysql://master:3306/db
username: root
password: master_pwd
driver-class-name: com.mysql.cj.jdbc.Driver
slave1:
url: jdbc:mysql://slave1:3306/db
username: read_user
password: read_pwd
slave2:
url: jdbc:mysql://slave2:3306/db
注解驱动切换:
@Service
public class OrderService {
// 默认使用主数据源
public void createOrder(Order order) {
orderMapper.insert(order);
}
@DS("slave1") // 指定数据源
public Order getOrder(long id) {
return orderMapper.selectById(id);
}
@DS("slave2") // 使用另一个从库
public List listOrders() {
return orderMapper.selectList(null);
}
}
实体管理器分离:
@Configuration
@EnableJpaRepositories(
basePackages = "com.example.repository.master",
entityManagerFactoryRef = "masterEntityManager",
transactionManagerRef = "masterTransactionManager"
)
public class MasterConfig {
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean masterEntityManager(
EntityManagerFactoryBuilder builder,
@Qualifier("masterDataSource") DataSource ds) {
return builder
.dataSource(ds)
.packages("com.example.entity.master")
.persistenceUnit("master")
.build();
}
@Primary
@Bean
public PlatformTransactionManager masterTransactionManager(
@Qualifier("masterEntityManager") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
// 从数据源配置类似(省略@Primary注解)
分库分表示例:
spring:
shardingsphere:
datasource:
names: ds0, ds1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://db0:3306/db
username: root
ds1:
type: com.zaxxer.hikari.HikariDataSource
jdbc-url: jdbc:mysql://db1:3306/db
rules:
sharding:
tables:
orders:
actualDataNodes: ds$->{0..1}.orders_$->{0..15}
tableStrategy:
standard:
shardingColumn: order_id
shardingAlgorithmName: table-inline
keyGenerateStrategy:
column: order_id
keyGeneratorName: snowflake
shardingAlgorithms:
table-inline:
type: INLINE
props:
algorithm-expression: orders_$->{order_id % 16}
@Bean
public JtaTransactionManager transactionManager(
UserTransaction userTransaction,
TransactionManager txManager) {
return new JtaTransactionManager(userTransaction, txManager);
}
@Bean
public UserTransaction userTransaction() throws SystemException {
UserTransactionImpl userTx = new UserTransactionImpl();
userTx.setTransactionTimeout(300);
return userTx;
}
@Bean
public TransactionManager txManager() {
return new TransactionManagerImpl();
}
// 使用注解
@Transactional // 支持跨数据源事务
public void crossDatabaseOperation() {
masterRepo.save(entity1);
slaveRepo.save(entity2); // 两个操作要么全成功,要么全回滚
}
// 使用Seata实现
@GlobalTransactional // Seata全局事务注解
public void placeOrder(Order order) {
// 1. 扣减库存(库存服务)
stockService.deduct(order.getProductId(), order.getCount());
// 2. 创建订单(订单服务)
orderService.create(order);
// 3. 扣减余额(账户服务)
accountService.debit(order.getUserId(), order.getAmount());
}
spring:
datasource:
dynamic:
datasource:
master:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 3000
idle-timeout: 600000
max-lifetime: 1800000
slave:
hikari:
maximum-pool-size: 30 # 读库可配置更大连接池
spring:
datasource:
dynamic:
datasource:
master:
weight: 10 # 写权重
slave1:
weight: 30 # 读权重
slave2:
weight: 30
@Bean
public MeterRegistryCustomizer metrics() {
return registry -> {
// 监控主库连接池
HikariDataSource masterDs = ...;
new HikariDataSourceMetrics(
masterDs,
"datasource.master",
Tags.empty()
).bindTo(registry);
// 监控从库连接池
HikariDataSource slaveDs = ...;
new HikariDataSourceMetrics(
slaveDs,
"datasource.slave",
Tags.empty()
).bindTo(registry);
};
}
// 在DynamicDataSource中增加监控
public class MonitoredDataSource extends AbstractDataSource {
private final DataSource targetDataSource;
@Override
public Connection getConnection() throws SQLException {
Connection conn = targetDataSource.getConnection();
// 记录连接获取堆栈
ConnectionTracker.track(conn, new Exception("Connection opened at:"));
return conn;
}
private static class ConnectionTracker {
static void track(Connection conn, Exception openTrace) {
// 存储打开堆栈信息
}
static void checkLeaks() {
// 定时检查未关闭连接
}
}
}
@Bean
@DependsOn("dynamicDataSource") // 确保先初始化数据源
public SqlSessionFactory sqlSessionFactory(
DynamicDataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
return factory.getObject();
}
解决方案:
使用内存关联(小数据量)
建立数据同步管道(CDC工具)
使用分布式查询引擎(Presto/Drill)
核心结论:
️ 中小项目:MyBatis-Plus多数据源 > 手动配置
动态路由场景:AbstractRoutingDataSource
分布式系统:ShardingSphere
最佳实践:
连接池分离:每个数据源独立配置连接池
读写分离:写操作路由到主库,读操作负载均衡到从库
事务边界:避免跨数据源事务,必要时使用Seata
监控告警:对每个数据源建立健康检查
失败转移:实现从库故障自动切换
黄金法则:
在满足业务需求的前提下,选择最简洁的实现方案,避免过度设计