在复杂业务系统中,多数据源切换已成为必备技能。本文将深入剖析三种主流实现方案,带你从入门到精通!
一、多数据源应用场景
读写分离:主库负责写操作,从库处理读请求
多租户系统:不同租户使用独立数据库
分库分表:业务数据按规则分散存储
多数据库类型:同时使用MySQL、Oracle等异构数据库
二、3种实现方案对比
方案 | 实现复杂度 | 侵入性 | 维护成本 | 适用场景 |
---|---|---|---|---|
AbstractRoutingDataSource | 中等 | 高 | 高 | 简单读写分离 |
多SqlSessionFactory | 高 | 高 | 高 | 异构数据库 |
dynamic-datasource | 低 | 低 | 低 | 复杂多数据源 |
三、方案一:AbstractRoutingDataSource
实现原理
通过继承Spring的AbstractRoutingDataSource
类,动态路由数据源
实现步骤
1. 添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
</dependencies>
2. 配置多数据源
# application.yml
spring:
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/master_db
username: root
password: root123
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/slave_db
username: root
password: root123
3. 动态数据源配置类
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource dynamicDataSource() {
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. 自定义动态数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceKey();
}
public static class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder =
new ThreadLocal<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
}
5. 自定义注解切换数据源
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "master";
}
6. AOP切面实现动态切换
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(dataSource)")
public void beforeSwitchDataSource(JoinPoint point, DataSource dataSource) {
String dataSourceKey = dataSource.value();
DynamicDataSource.DataSourceContextHolder.setDataSourceKey(dataSourceKey);
}
@After("@annotation(dataSource)")
public void afterSwitchDataSource(JoinPoint point, DataSource dataSource) {
DynamicDataSource.DataSourceContextHolder.clearDataSourceKey();
}
}
7. 业务层使用示例
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 使用主库
@DataSource("master")
public void createUser(User user) {
String sql = "INSERT INTO users(name, email) VALUES(?, ?)";
jdbcTemplate.update(sql, user.getName(), user.getEmail());
}
// 使用从库
@DataSource("slave")
public List<User> getAllUsers() {
String sql = "SELECT * FROM users";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
}
}
方案优缺点
优点:
纯Spring实现,无第三方依赖
灵活控制数据源切换
缺点:
事务管理复杂
需手动处理连接池
切换逻辑侵入业务代码
四、方案二:多SqlSessionFactory
实现原理
为每个数据源创建独立的MyBatis SqlSessionFactory
实现步骤
1. 主数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.master",
sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory masterSqlSessionFactory(
@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/master/*.xml"));
return bean.getObject();
}
@Bean
public DataSourceTransactionManager masterTransactionManager(
@Qualifier("masterDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
2. 从数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.slave",
sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory slaveSqlSessionFactory(
@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/slave/*.xml"));
return bean.getObject();
}
@Bean
public DataSourceTransactionManager slaveTransactionManager(
@Qualifier("slaveDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
3. 业务层使用
@Service
public class OrderService {
// 注入主库Mapper
@Autowired
@Qualifier("masterOrderMapper")
private OrderMapper masterOrderMapper;
// 注入从库Mapper
@Autowired
@Qualifier("slaveOrderMapper")
private OrderMapper slaveOrderMapper;
@Transactional(transactionManager = "masterTransactionManager")
public void createOrder(Order order) {
masterOrderMapper.insert(order);
}
@Transactional(transactionManager = "slaveTransactionManager")
public Order getOrder(Long id) {
return slaveOrderMapper.selectById(id);
}
}
方案优缺点
优点:
各数据源完全隔离
事务管理清晰
支持异构数据库
缺点:
配置复杂,冗余代码多
Mapper需按数据源分包
动态切换不灵活
五、方案三:dynamic-datasource框架
框架优势
零侵入:通过注解实现数据源切换
功能丰富:支持读写分离、分库分表等
简单易用:简化多数据源配置
实现步骤
1. 添加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
2. 配置数据源
spring:
datasource:
dynamic:
primary: master # 默认数据源
strict: false # 是否严格匹配数据源
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/master_db
username: root
password: root123
slave1:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/slave_db1
username: root
password: root123
slave2:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/slave_db2
username: root
password: root123
oracle_db:
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@localhost:1521:orcl
username: system
password: oracle123
3. 使用@DS注解切换数据源
@Service
public class ProductService {
// 默认使用主库
@Autowired
private JdbcTemplate jdbcTemplate;
// 使用主库
@DS("master")
public void createProduct(Product product) {
jdbcTemplate.update("INSERT INTO product(...) VALUES(...)");
}
// 随机使用从库
@DS("slave")
public Product getProduct(Long id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM product WHERE id = ?",
new BeanPropertyRowMapper<>(Product.class), id);
}
// 指定特定从库
@DS("slave1")
public List<Product> getHotProducts() {
return jdbcTemplate.query(
"SELECT * FROM product WHERE hot = 1",
new BeanPropertyRowMapper<>(Product.class));
}
// 使用Oracle数据库
@DS("oracle_db")
public List<Category> getOracleCategories() {
return jdbcTemplate.query(
"SELECT * FROM categories",
new BeanPropertyRowMapper<>(Category.class));
}
}
4. 高级功能:读写分离
spring:
datasource:
dynamic:
primary: master
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://master-host:3306/db
username: root
password: root123
slave_1:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://slave1-host:3306/db
username: root
password: root123
slave_2:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://slave2-host:3306/db
username: root
password: root123
strategy: # 读写分离配置
load-balance: # 负载均衡策略
slave: round_robin # 从库轮询策略
5. 事务管理
@DS("master")
@Transactional
public void placeOrder(Order order) {
// 1. 扣减库存
productService.reduceStock(order.getProductId(), order.getQuantity());
// 2. 创建订单
orderMapper.insert(order);
// 3. 记录日志
logService.logOrder(order);
// 所有操作都在主库事务中执行
}
最佳实践技巧
数据源分组管理:
spring: datasource: dynamic: datasource: master_1: # 配置... master_2: # 配置... slave_1: # 配置... slave_2: # 配置... group: masters: master_1, master_2 slaves: slave_1, slave_2
多租户数据源动态注册:
@Autowired private DynamicRoutingDataSource routingDataSource; public void addTenantDataSource(String tenantId, DataSourceProperty property) { DataSource dataSource = dataSourceCreator.createDataSource(property); routingDataSource.addDataSource(tenantId, dataSource); }
自定义负载均衡策略:
public class RandomStrategy implements LoadBalanceStrategy { @Override public String determineDataSource( List<String> dataSourceNames) { Random random = new Random(); return dataSourceNames.get( random.nextInt(dataSourceNames.size())); } }
六、性能优化建议
连接池配置优化:
spring: datasource: dynamic: datasource: master: # ... hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000
避免频繁切换数据源:
将同一数据源操作集中处理
使用@DSTransactional管理跨库事务
监控数据源状态:
@RestController public class DataSourceMonitor { @Autowired private DynamicRoutingDataSource routingDataSource; @GetMapping("/datasources") public Map<String, DataSource> listDataSources() { return routingDataSource.getDataSources(); } @GetMapping("/datasources/stats") public Map<String, Object> getDataSourceStats() { Map<String, Object> stats = new HashMap<>(); routingDataSource.getDataSources().forEach((key, ds) -> { if(ds instanceof HikariDataSource) { HikariDataSource hikari = (HikariDataSource) ds; HikariPoolMXBean pool = hikari.getHikariPoolMXBean(); stats.put(key, Map.of( "active", pool.getActiveConnections(), "idle", pool.getIdleConnections(), "total", pool.getTotalConnections() )); } }); return stats; } }
七、方案选型建议
中小型项目:优先选用dynamic-datasource,开发效率高
异构数据库系统:选择多SqlSessionFactory方案,隔离性好
需要高度定制:AbstractRoutingDataSource提供最大灵活性
云原生环境:dynamic-datasource + Seata分布式事务
八、常见问题解决方案
数据源切换失效:
检查方法是否被AOP代理
确保@DS注解在public方法上
避免类内部方法调用
跨库事务问题:
// 使用分布式事务 @DS("order") @GlobalTransactional public void createOrder(Order order) { // 操作订单库 orderMapper.insert(order); // 操作库存库 stockService.reduce(order.getProductId(), order.getQuantity()); }
连接泄露检测:
@Bean public DataSource dataSource(DataSourceProperties properties) { HikariDataSource dataSource = properties.initializeDataSourceBuilder() .type(HikariDataSource.class).build(); dataSource.setLeakDetectionThreshold(5000); // 5秒泄露检测 return dataSource; }
九、结语
多数据源管理是现代应用开发的必备技能。通过本文介绍的三种方案:
AbstractRoutingDataSource:Spring原生方案,适合定制化场景
多SqlSessionFactory:适合异构数据库系统
dynamic-datasource:生产环境首选,功能强大易用
最佳实践提示:对于大多数Java项目,推荐使用dynamic-datasource框架,它提供了一站式的解决方案,大大降低了多数据源管理的复杂度。同时结合Spring Cloud Alibaba Seata,可轻松实现分布式事务管理。
扩展阅读:
掌握多数据源切换技术,让你的应用从容应对复杂数据场景!