最近项目中使用多数据源,架构给的多数据源方案是在注册数据源时区分mapper包的位置,在开发过程中将mapper写到对应的包下。这种方式可能更加清晰,但是在开发过程中个人觉得有点麻烦,参考别人的博客,结合自己的理解写了这篇文章,希望对大家有帮助。
实现思路:
- yml 配置数据源基本信息
- 配置类注册数据源信息
- 继承 AbstractRoutingDataSource类动态切换数据源
- 声明注解
- 编织切面
1. yml 配置数据源基本信息
spring:
datasource:
USER: #用户库
type: com.alibaba.druid.pool.DruidDataSource
jdbc-url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
ORDER: #订单库
type: com.alibaba.druid.pool.DruidDataSource
jdbc-url: jdbc:mysql://localhost:3306/order
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
2. 配置类注册数据源信息
/**
* @Description: 注入数据源
*/
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.user")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.order")
public DataSource orderDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource userDataSource, DataSource orderDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.USER.name(), userDataSource);
targetDataSources.put(DataSourceType.ORDER.name(), orderDataSource);
return new DynamicDataSource(userDataSource, targetDataSources);
}
}
3. 继承 AbstractRoutingDataSource类动态切换数据源
/**
* @Description:
* 动态切换数据源主要依靠 AbstractRoutingDataSource。
* 创建一个 AbstractRoutingDataSource 的子类,重写 determineCurrentLookupKey 方法,
* 用于决定使用哪一个数据源。这里主要用到 AbstractRoutingDataSource 的两个属
* 性 defaultTargetDataSource和targetDataSources。defaultTargetDataSource 默认目标数据源,
* targetDataSources(map类型)存放用来切换的数据源。
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
4. 声明注解
/**
* @Description:
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DataSourceType value() default DataSourceType.USER;
}
5. 编织切面
/**
* @Description: 通过拦截 @DataSource 注解,在其执行之前处理设置当前执行SQL的数据源的信息,
* CONTEXT_HOLDER.set(dataSourceType)这里的数据源信息从我们设置的注解上面获取信息,
* 如果没有设置就是用默认的数据源的信息。
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.haowq.anno.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
//通过注解值设置数据源
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
6. 动态数据源上下文
/**
* @Description: 动态数据源切换
*/
public class DynamicDataSourceContextHolder {
/***
* @description: 使用ThreadLocal维护变量,TThreadLocal为每个使用该变量的县城提供独立的变量副本,
* 所以每一个线程都可以独立改变自己的副本,而不会影响其他线程所对应的副本
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/***
* @description: 设置数据源变量
*/
public static void setDataSourceType(String dataSourceType) {
System.out.println("切换到 "+ dataSourceType+"数据源");
CONTEXT_HOLDER.set(dataSourceType);
}
/***
* @description: 获取数据源变量
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}