实现动态数据源配置
0. 排除数据源的自动装配
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
1. yml配置多个数据源信息
spring:
datasource:
master:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.124.200:3306/monitor
username: root
password: root
salve:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.124.201:3306/monitor
username: root
password: root
2. 使用ThreadLocal,保存数据源名称供下游业务使用
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDataSource(String dbName) {
contextHolder.set(dbName);
}
// 获取数据源名
public static String getDataSource() {
return (contextHolder.get());
}
// 清除数据源名
public static void clearDataSource() {
contextHolder.remove();
}
}
3. 定义数据源类,继承AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
4. 定义配置类读取数据源信息,设置数据源对象属性
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource master(){
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slave(){
return DataSourceBuilder.create().build();
}
@Primary
@Bean
public DynamicDataSource dataSource(DataSource master, DataSource slave) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", master);
targetDataSources.put("slave", slave);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
5. 定义自定义数据源注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DS {
String value() default "master";
}
6. 定义切面,设置数据源名到ThreadLocal中
@Component
@Aspect
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.iteng.annotation.DS)")
public void dynamicDataSourcePointCut() {
}
@Around("dynamicDataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dataSourceKey;
// 类上的注解
Class<?> aClass = joinPoint.getTarget().getClass();
DS annotation = aClass.getAnnotation(DS.class);
// 方法上的注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
DS annotationMethod = signature.getMethod().getAnnotation(DS.class);
if (Objects.nonNull(annotationMethod)) {
dataSourceKey = annotationMethod.value();
} else {
dataSourceKey = annotation.value();
}
// 设置数据源
DataSourceContextHolder.setDataSource(dataSourceKey);
try {
return joinPoint.proceed();
}finally {
DataSourceContextHolder.clearDataSource();
}
}
}
7. 使用动态数据源
@DS(value = "salve")
@GetMapping("/get")
public R getName(){
// 数据库操作
}
@DS
@PostMapping("/save")
public R sageName(){
// 数据库操作
}
流程:
- getName() 方法在执行之前,先执行对应的切面类,将“
salve
”这个数据源名称保存带ThreadLocal中 - 然后执行getName() 方法,当遇到数据库操作时,首先要与数据库进行连接。
- MyBatis 会先找
DataSource
的实现类,因为 DataSource 有getConnection()
方法。 - 继承了 AbstractRoutingDataSource 的
DynamicDataSource
会被找到。 - 然后执行 DynamicDataSource 中的
determineTargetDataSource
方法,即确定AbstractRoutingDataSource
中 Map 对应的Key,然后获取对应的数据源对象。 - 进行数据库连接。