Appearance
MyBatis-Plus 多数据源
MyBatis-Plus 通过 dynamic-datasource-spring-boot-starter 提供多数据源支持,使用 @DS 注解即可实现动态数据源切换。
一、引入依赖
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>4.2.0</version>
</dependency>二、配置多数据源
yaml
spring:
datasource:
dynamic:
primary: master # 默认数据源
strict: false # 严格模式(找不到数据源时是否抛异常)
datasource:
# 主库
master:
url: jdbc:mysql://localhost:3306/db_master?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库1
slave1:
url: jdbc:mysql://localhost:3307/db_slave1?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 从库2
slave2:
url: jdbc:mysql://localhost:3308/db_slave2?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Oracle
oracle:
url: jdbc:oracle:thin:@localhost:1521:ORCL
username: system
password: 123456
driver-class-name: oracle.jdbc.OracleDriver三、@DS 注解
java
// 类级别:整个类所有方法默认使用指定数据源
@Service
@DS("slave1")
public class UserService extends ServiceImpl<UserMapper, User> {
// 默认查询从库
}
// 方法级别:覆盖类级别配置
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
// 读操作 → 从库
@DS("slave1")
public User getById(Long id) {
return super.getById(id);
}
@DS("slave2")
public List<User> list() {
return super.list();
}
// 写操作 → 主库
@DS("master")
public boolean save(User user) {
return super.save(user);
}
}四、读写分离
4.1 手动指定
java
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
@DS("master")
public boolean createUser(User user) {
return save(user);
}
@DS("slave1")
public User getUserById(Long id) {
return getById(id);
}
}4.2 AOP 自动切换
java
@Aspect
@Component
public class DataSourceAspect {
/**
* 读方法走从库,写方法走主库
*/
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object around(ProceedingJoinPoint point) throws Throwable {
DynamicDataSourceContextHolder.push("master");
try {
return point.proceed();
} finally {
DynamicDataSourceContextHolder.poll();
}
}
@Before("execution(* com.example.service.*.get*(..)) " +
"|| execution(* com.example.service.*.select*(..)) " +
"|| execution(* com.example.service.*.list*(..)) " +
"|| execution(* com.example.service.*.find*(..)) " +
"|| execution(* com.example.service.*.query*(..))")
public void setReadDataSource() {
DynamicDataSourceContextHolder.push("slave1");
}
@After("execution(* com.example.service.*.get*(..)) " +
"|| execution(* com.example.service.*.select*(..)) " +
"|| execution(* com.example.service.*.list*(..)) " +
"|| execution(* com.example.service.*.find*(..)) " +
"|| execution(* com.example.service.*.query*(..))")
public void clearDataSource() {
DynamicDataSourceContextHolder.poll();
}
}五、多数据源事务
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper; // 主库
@Autowired
private InventoryMapper inventoryMapper; // 从库
// 注意:@DS 不能和 @Transactional 一起在同一个方法上
// 多数据源事务需要分布式事务方案(如 Seata)
@DS("master")
public void createOrder(Order order) {
orderMapper.insert(order);
}
@DS("slave1")
public List<Order> queryOrders() {
return orderMapper.selectList(null);
}
}注意: 多数据源使用本地事务时,
@Transactional只能管理默认数据源的事务。跨数据源操作需要分布式事务。
六、动态添加数据源
java
@Component
public class DynamicDataSourceManager {
@Autowired
private DataSource dataSource;
/**
* 运行时动态添加数据源
*/
public void addDataSource(String name, String url, String username, String password) {
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
DataSourceProperty property = new DataSourceProperty();
property.setUrl(url);
property.setUsername(username);
property.setPassword(password);
property.setDriverClassName("com.mysql.cj.jdbc.Driver");
DataSource newDataSource = new DynamicDataSourceCreator().createDataSource(property);
ds.addDataSource(name, newDataSource);
}
}七、多数据源原理
dynamic-datasource 原理:
① 启动时加载所有数据源配置
→ 创建 DynamicRoutingDataSource(路由数据源)
② @DS 注解解析
→ 通过 AOP 拦截,在执行方法前设置数据源
③ 数据源切换
→ DynamicDataSourceContextHolder 使用 ThreadLocal 存储当前数据源名
→ DynamicRoutingDataSource 根据名称获取对应的 DataSource
④ 方法执行完毕后清除
→ 恢复默认数据源
数据源切换流程:
@DS("slave") → AOP 拦截 → ThreadLocal.push("slave")
→ 获取 DataSource → 执行 SQL → ThreadLocal.poll()八、速记总结
多数据源三步走:
① 加依赖:dynamic-datasource-spring-boot-starter
② 配数据源:spring.datasource.dynamic.datasource.xxx
③ 加注解:@DS("数据源名") 在类或方法上
读写分离:
主库 → @DS("master")
从库 → @DS("slave")
速记口诀:
动态数据源配置过,@DS 注解指定库,
类上默认方法覆盖,主库写从库读。九、面试要点
| 问题 | 答案要点 |
|---|---|
| 多数据源怎么实现? | dynamic-datasource + @DS 注解 |
| @DS 注解可以加在哪? | 类上(默认)或方法上(覆盖) |
| 多数据源事务怎么办? | 本地事务只能管一个,跨库需要分布式事务 |
| 原理是什么? | AOP 拦截 + ThreadLocal 存数据源名 + 路由数据源 |
