前言
在企业应用中,经常需要使用多个数据源,如读写分离、分库分表等场景。Spring Boot 支持多数据源配置,通过 AbstractRoutingDataSource 可以实现动态数据源切换。
快速开始
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>4.2.0</version>
</dependency>
2. 配置多数据源
spring:
datasource:
dynamic:
primary: master
strict: false
datasource:
master:
url: jdbc:mysql://master:3306/demo
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave_1:
url: jdbc:mysql://slave1:3306/demo
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave_2:
url: jdbc:mysql://slave2:3306/demo
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
3. 使用多数据源
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
/**
* 默认使用主库
*/
public User create(User user) {
return userRepository.save(user);
}
/**
* 使用从库
*/
@DS("slave_1")
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
/**
* 动态切换
*/
public List<User> findAll() {
// 使用从库查询
DynamicDataSourceContextHolder.push("slave_1");
try {
return userRepository.findAll();
} finally {
DynamicDataSourceContextHolder.poll();
}
}
}
自定义多数据源
1. 配置数据源
@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();
}
}
2. 创建路由数据源
@Configuration
public class DataSourceConfig {
@Bean
public DataSource routingDataSource(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave
) {
AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.get();
}
};
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", master);
targetDataSources.put("slave", slave);
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(master);
return routingDataSource;
}
}
3. 创建数据源上下文
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void set(String key) {
CONTEXT.set(key);
}
public static String get() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
4. 创建切面
@Aspect
@Component
@Order(1)
public class DataSourceAspect {
@Around("@annotation(ds)")
public Object around(ProceedingJoinPoint pjp, DS ds) throws Throwable {
String key = ds.value();
DataSourceContextHolder.set(key);
try {
return pjp.proceed();
} finally {
DataSourceContextHolder.clear();
}
}
}
5. 创建注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
String value() default "master";
}
读写分离
1. 配置主从数据源
spring:
datasource:
dynamic:
primary: master
datasource:
master:
url: jdbc:mysql://master:3306/demo
username: root
password: 123456
slave:
url: jdbc:mysql://slave:3306/demo
username: root
password: 123456
2. 使用读写分离
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
/**
* 写操作 - 主库
*/
@DS("master")
public User create(User user) {
return userRepository.save(user);
}
/**
* 读操作 - 从库
*/
@DS("slave")
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
/**
* 自动读写分离
*/
public List<User> findAll() {
// 根据方法名自动选择数据源
return userRepository.findAll();
}
}
3. 自动读写分离切面
@Aspect
@Component
@Order(1)
public class ReadWriteAspect {
@Around("execution(* com.example.demo.service..*.find*(..)) || " +
"execution(* com.example.demo.service..*.get*(..)) || " +
"execution(* com.example.demo.service..*.query*(..)) || " +
"execution(* com.example.demo.service..*.select*(..))")
public Object readAround(ProceedingJoinPoint pjp) throws Throwable {
DataSourceContextHolder.set("slave");
try {
return pjp.proceed();
} finally {
DataSourceContextHolder.clear();
}
}
@Around("execution(* com.example.demo.service..*.save*(..)) || " +
"execution(* com.example.demo.service..*.insert*(..)) || " +
"execution(* com.example.demo.service..*.update*(..)) || " +
"execution(* com.example.demo.service..*.delete*(..))")
public Object writeAround(ProceedingJoinPoint pjp) throws Throwable {
DataSourceContextHolder.set("master");
try {
return pjp.proceed();
} finally {
DataSourceContextHolder.clear();
}
}
}
分库分表
1. 配置多个数据库
spring:
datasource:
dynamic:
datasource:
db_0:
url: jdbc:mysql://db0:3306/demo
username: root
password: 123456
db_1:
url: jdbc:mysql://db1:3306/demo
username: root
password: 123456
db_2:
url: jdbc:mysql://db2:3306/demo
username: root
password: 123456
2. 动态路由
@Service
public class OrderService {
/**
* 根据用户 ID 路由到对应数据库
*/
public Order findById(Long userId, Long orderId) {
// 计算数据库索引
int dbIndex = Math.abs(userId.hashCode() % 3);
String dsKey = "db_" + dbIndex;
DynamicDataSourceContextHolder.push(dsKey);
try {
return orderRepository.findById(orderId).orElse(null);
} finally {
DynamicDataSourceContextHolder.poll();
}
}
}
3. 使用 ShardingSphere
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.4.0</version>
</dependency>
spring:
shardingsphere:
datasource:
names: ds0,ds1
ds0:
url: jdbc:mysql://ds0:3306/demo
username: root
password: 123456
ds1:
url: jdbc:mysql://ds1:3306/demo
username: root
password: 123456
rules:
sharding:
tables:
order:
actual-data-nodes: ds$->{0..1}.order$->{0..1}
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: order-table-algorithm
sharding-algorithms:
order-table-algorithm:
type: CLASS_BASED
props:
strategy: STANDARD
algorithm-class: com.example.demo.sharding.OrderTableShardingAlgorithm
事务管理
1. 单数据源事务
@Service
public class UserService {
@Transactional
@DS("master")
public User create(User user) {
return userRepository.save(user);
}
}
2. 多数据源事务
@Service
public class OrderService {
/**
* 多数据源事务 - 需要 JTA
*/
@Transactional
public void createOrder(Order order) {
// 订单库
orderRepository.save(order);
// 用户库
userService.updatePoints(order.getUserId(), order.getPoints());
}
}
3. 配置 JTA
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
spring:
jta:
enabled: true
atomikos:
datasource:
max-pool-size: 10
min-pool-size: 1
max-lifetime: 60000
borrow-connection-timeout: 30000
最佳实践
1. 数据源隔离
// ✅ 推荐 - 明确指定数据源
@Service
public class UserService {
@DS("master")
public User create(User user) {
return userRepository.save(user);
}
@DS("slave")
public User findById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
// ❌ 不推荐 - 依赖自动切换
@Service
public class UserService {
public User create(User user) {
// 不知道使用哪个数据源
return userRepository.save(user);
}
}
2. 避免跨库 Join
// ✅ 推荐 - 应用层组装
@Service
public class OrderService {
public OrderDetailVO getDetail(Long orderId) {
Order order = orderRepository.findById(orderId).orElse(null);
User user = userRepository.findById(order.getUserId()).orElse(null);
OrderDetailVO vo = new OrderDetailVO();
vo.setOrder(order);
vo.setUser(user);
return vo;
}
}
// ❌ 不推荐 - 跨库 Join
@Query("SELECT o, u FROM Order o JOIN User u ON o.userId = u.id")
OrderDetailVO getDetail(Long orderId);
3. 数据源监控
@Configuration
public class DataSourceMonitorConfig {
@Bean
public DataSource dataSource(DataSource routingDataSource) {
return new ProxyDataSource(routingDataSource) {
@Override
public Connection getConnection() throws SQLException {
Connection connection = super.getConnection();
DataSourceContextHolder.get();
log.info("获取数据源连接:{}", DataSourceContextHolder.get());
return connection;
}
};
}
}
总结
多数据源要点:
- ✅ 配置方式 - dynamic-datasource、自定义
- ✅ 读写分离 - 主从数据源、自动切换
- ✅ 分库分表 - 动态路由、ShardingSphere
- ✅ 事务管理 - 单数据源、JTA 多数据源
- ✅ 最佳实践 - 明确指定、避免跨库 Join
多数据源是处理大数据量、高并发场景的重要手段。