在项目中有时会需要根据情况来使用不同的数据源
spring:
datasource:
master:
password: root
url: jdbc:mysql://localhost:3306/evid_yunyan?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
username: root
slave:
password: root
url: jdbc:mysql://localhost:3306/evid_qingzhen?characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
username: root
// 创建使用的数据源类型,之后根据这个数据源类型切换数据源
public enum DataSourceType {
MASTER { // 主库
@Override
public String toString() {
return "MASTER";
}
},
SLAVE { // 从库
@Override
public String toString() {
return "SLAVE";
}
}
}
创建一个数据源切换处理类,有对数据源变量的获取、设置和清空的方法。其中的ThreadLocal用于保存某个线程共享变量
// 创建切换数据源的处理类,主要提供在各个线程中切换线程使用的数据源
public class DynamicDataSourceContext {
// ThreadLocal变量可以将变量线程隔离,对于每个线程这个变量都是独立的
// 把变量线程隔离开,保证在多线程下该变量的值不会被其它线程所改变
private static ThreadLocal<DataSourceType> dbType = new ThreadLocal<DataSourceType>();
public static void setDbType(DataSourceType changeType){
dbType.set(changeType);
System.out.println("数据源修改为" + changeType);
}
public static DataSourceType getDbType(){
return dbType.get();
}
public static void clearDbType(){
dbType.remove();
}
}
四、创建动态数据源对象
动态切换数据源主要依靠AbstractRoutingDataSource
。创建一个AbstractRoutingDataSource
的子类,重写determineCurrentLookupKey
方法,用于决定使用哪一个数据源。这里主要用到AbstractRoutingDataSource
的两个属性defaultTargetDataSource和targetDataSources
。defaultTargetDataSource
默认目标数据源,targetDataSources
(map类型)存放用来切换的数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> DataSources) {
// 将默认使用的放在父类的成员变量上
super.setDefaultTargetDataSource(defaultDataSource);
// 将所有数据源放在父类的MAP上
super.setTargetDataSources(DataSources);
// 必须调用此方法,将targetDataSources的属性写入resolvedDataSources中,实现切换数据源
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
// 该方法是必须实现的,因为该方法控制返回使用哪一个数据源的的key
return DynamicDataSourceContext.getDbType();
}
}
Configuration
public class DataBasesConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean
@Primary // 此注解必须,这样在需要数据源的时候才会经过这个动态路由数据源对象进行数据源的选择
public DataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource){
// 获取两个数据源并放入map中
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put(DataSourceType.MASTER,masterDataSource);
dataSources.put(DataSourceType.SLAVE,slaveDataSource);
// 将这个动态的数据源作为主数据源,这样在代码中使用数据源就会通过这个动态数据源去切换数据源
return new DynamicDataSource(masterDataSource, dataSources);
}
}
设置拦截数据源的注解,可以设置在具体的类上,或者在具体的方法上
// 目标是注解在方法上
@Target({ElementType.METHOD})
// 表示该注解在运行期间都是有效的
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
// 切换数据源的枚举对象,默认为master数据源
DataSourceType value() default DataSourceType.MASTER;
}
通过拦截上面的注解,在其执行之前处理设置当前执行SQL的数据源的信息
Aspect
@Order(1) // 声明执行顺序要是第一个
@Component // 放入Spring中管理
public class DataSourceAspect {
// 只针对DataSource注解的方法作为切入点
@Pointcut("@annotation(com.example.mp.demo1.aop.DataSource)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取这个方法的签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 重签名中获取方法的注解
DataSource annotation = signature.getMethod().getAnnotation(DataSource.class);
// 设置切换数据源的处理类当前的数据源值
DynamicDataSourceContext.setDbType(annotation.value());
try {
// 执行该方法
return joinPoint.proceed();
} finally {
// 清空使用的数据源类型
DynamicDataSourceContext.clearDbType();
}
}
}
@Override
@DataSource(DataSourceType.SLAVE)
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
System.out.println("启动成功");
DynamicDataSourceContext.setDbType(DataSourceType.SLAVE);
System.out.println(testDao.selectCustom());
applicationReadyEvent.getApplicationContext().stop();
}