4-3 多数据源配置
概念解析
何时需要多数据源
| 场景 | 说明 |
|---|---|
| 主从分离 | 读/写分离,提高性能 |
| 分库分表 | 数据量大的表拆分 |
| 多业务隔离 | 不同业务使用不同数据库 |
| 历史数据归档 | 冷热数据分离 |
配置方式
| 方式 | 复杂度 | 适用场景 |
|---|---|---|
| Spring 原生 | 中 | 简单多数据源 |
| Druid 多数据源 | 低 | 大部分场景 |
| Seata AT 模式 | 高 | 分布式事务 |
代码示例
1. 基础多数据源配置
@Configuration
public class DataSourceConfig {
// 主数据源
@Bean
@Primary // 默认数据源
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DruidDataSourceFactory.createDataSource(
new DruidConfig());
}
// 从数据源
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DruidDataSourceFactory.createDataSource(
new DruidConfig());
}
// 主事务管理器
@Bean
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 从事务管理器
@Bean
public PlatformTransactionManager slaveTransactionManager(
@Qualifier("slaveDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
2. 配置示例
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/master_db?useUnicode=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
initial-size: 5
min-idle: 5
max-active: 20
slave:
url: jdbc:mysql://localhost:3306/slave_db?useUnicode=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
initial-size: 5
min-idle: 5
max-active: 20
3. JPA 多数据源配置
@Configuration
@EnableJpaRepositories(
basePackages = "com.example.demo.repository.master",
entityManagerFactoryRef = "masterEntityManagerFactory",
transactionManagerRef = "masterTransactionManager"
)
public class MasterJpaConfig {
@Autowired
@Qualifier("primaryDataSource")
private DataSource primaryDataSource;
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean
masterEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(primaryDataSource)
.packages("com.example.demo.entity.master")
.persistenceUnit("master")
.build();
}
@Bean
@Primary
public JpaTransactionManager masterTransactionManager(
EntityManagerFactory masterEntityManagerFactory) {
return new JpaTransactionManager(masterEntityManagerFactory);
}
}
@Configuration
@EnableJpaRepositories(
basePackages = "com.example.demo.repository.slave",
entityManagerFactoryRef = "slaveEntityManagerFactory",
transactionManagerRef = "slaveTransactionManager"
)
public class SlaveJpaConfig {
@Autowired
@Qualifier("slaveDataSource")
private DataSource slaveDataSource;
@Bean
public LocalContainerEntityManagerFactoryBean
slaveEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(slaveDataSource)
.packages("com.example.demo.entity.slave")
.persistenceUnit("slave")
.build();
}
@Bean
public JpaTransactionManager slaveTransactionManager(
EntityManagerFactory slaveEntityManagerFactory) {
return new JpaTransactionManager(slaveEntityManagerFactory);
}
}
4. MyBatis 多数据源配置
@Configuration
public class MyBatisConfig {
// 主库 SqlSessionFactory
@Bean
@Primary
public SqlSessionFactory masterSqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/master/**/*.xml"));
return factory.getObject();
}
// 从库 SqlSessionFactory
@Bean
public SqlSessionFactory slaveSqlSessionFactory(
@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/slave/**/*.xml"));
return factory.getObject();
}
}
5. 动态数据源切换
// 动态数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> DATA_SOURCE_KEY =
ThreadLocal.withInitial(() -> "master");
public static void setDataSource(String dataSource) {
DATA_SOURCE_KEY.set(dataSource);
}
public static String getDataSource() {
return DATA_SOURCE_KEY.get();
}
public static void clear() {
DATA_SOURCE_KEY.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return DATA_SOURCE_KEY.get();
}
}
// 数据源切换切面
@Aspect
@Component
public class DataSourceSwitchAspect {
@Pointcut("execution(* com.example.demo.mapper.master.*.*(..))")
public void masterPointcut() {}
@Pointcut("execution(* com.example.demo.mapper.slave.*.*(..))")
public void slavePointcut() {}
@Before("masterPointcut()")
public void switchToMaster() {
DynamicDataSource.setDataSource("master");
}
@Before("slavePointcut()")
public void switchToSlave() {
DynamicDataSource.setDataSource("slave");
}
}
6. 读写分离路由
@Service
public class RoutingDataSourceService {
@Autowired
private DynamicDataSource dynamicDataSource;
// 读操作切换到从库
public User findById(Long id) {
DynamicDataSource.setDataSource("slave");
try {
return userMapper.findById(id);
} finally {
DynamicDataSource.clear();
}
}
// 写操作使用主库
@Transactional(transactionManager = "masterTransactionManager")
public void save(User user) {
// 默认主库
userMapper.insert(user);
}
// 显式指定主库
@Transactional(transactionManager = "masterTransactionManager")
public void update(User user) {
DynamicDataSource.setDataSource("master");
try {
userMapper.update(user);
} finally {
DynamicDataSource.clear();
}
}
}
常见坑点
⚠️ 坑 1:@Primary 未指定
// ❌ 缺少 @Primary
@Bean
public DataSource slaveDataSource() {
return DruidDataSourceFactory.createDataSource(config);
}
// ✅ 添加 @Primary 标记默认数据源
@Bean
@Primary
public DataSource primaryDataSource() {
return DruidDataSourceFactory.createDataSource(config);
}
⚠️ 坑 2:事务管理器未匹配
// ❌ 事务管理器与数据源不匹配
@Transactional(transactionManager = "slaveTransactionManager")
public void save(User user) {
// 实际使用 masterDataSource,但事务管理器是 slave
}
// ✅ 确保匹配
@Transactional(transactionManager = "primaryTransactionManager")
public void save(User user) {
// 使用 primaryDataSource
}
⚠️ 坑 3:跨数据源事务
// ❌ 跨数据源无法用本地事务
@Transactional
public void distributedOperation() {
userMapper.insert(user); // 主库
orderMapper.insert(order); // 从库 - 不会回滚!
}
// ✅ 使用分布式事务(Seata)
@GlobalTransactional
public void distributedOperation() {
userMapper.insert(user); // 主库
orderMapper.insert(order); // 从库
}
面试题
Q1:如何实现读写分离?
参考答案:
- 配置多数据源:主库(写)+ 从库(读)
- 数据源路由:根据操作类型自动切换
- 实现方式:
- AOP 切面:根据 Mapper 所在包切换
- 注解:自定义
@ReadOnly注解 - 动态路由:继承
AbstractRoutingDataSource
Q2:Spring 如何管理多数据源事务?
参考答案:
每个数据源需要独立的 TransactionManager:
@Bean
public PlatformTransactionManager masterTxManager(
@Qualifier("primaryDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
@Bean
public PlatformTransactionManager slaveTxManager(
@Qualifier("slaveDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
使用事务时指定:
@Transactional(transactionManager = "masterTxManager")
public void writeOperation() { }
Q3:多数据源下的分布式事务?
参考答案:
| 方案 | 代表 | 说明 |
|---|---|---|
| XA | Atomikos | 两阶段提交,性能差 |
| TCC | Seata | Try-Confirm-Cancel |
| 最终一致性 | 本地消息表 | 不保证实时一致 |
Seata AT 模式:
@GlobalTransactional
public void purchase(Order order) {
// 扣库存 - branch 1
inventoryMapper.decrease(order.getProductId(), order.getQuantity());
// 扣余额 - branch 2
accountMapper.decrease(order.getUserId(), order.getAmount());
}