使用Spring AOP和自定义注解实现多数据源动态切换,下面直接上代码
自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DS {
String value() default "db1";
}
定义动态数据源
定义动态数据源,继承AbstractRoutingDataSource抽象类,并重写determineCurrentLookupKey()方法。AbstractRoutingDataSource这个类是实现多数据源的关键,
作用是动态切换数据源。在该类中有一个targetDataSources map集合,其中key表示每个数据源的名字,value为每个数据源。然后根据determineCurrentLookupKey()
这个方法获取当前数据源在map中的key值,然后determineTargetDataSource()方法中动态获取当前数据源。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDB();
}
}
配置多数据源
数据源信息在Apollo中配置,此处不再罗列
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.first")
public DataSource firstDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(firstDatasource());
Map<Object, Object> dsMap = new HashMap<>(2);
dsMap.put("db1", firstDatasource());
dsMap.put("db2", secondDataSource());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
利用ThreadLocal确保线程安全
public class DataSourceContextHolder {
/**
* 默认数据源
*/
public static final String DEFAULT_DS = "db1";
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
log.debug("切换到{}数据源", dbType);
CONTEXT_HOLDER.set(dbType);
}
// 获取数据源名
public static String getDB() {
return CONTEXT_HOLDER.get();
}
// 清除数据源名
public static void clearDB() {
CONTEXT_HOLDER.remove();
}
}
定义数据源切面类,动态切换数据源
@Aspect
@Component
public class DynamicDataSourceAspect {
@Before("execution(public * com.**.service.impl..*(..)) && @annotation(ds)")
public void beforeSwitchDS(JoinPoint point, DS ds){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
@After("@annotation(ds)")
public void afterSwitchDS(JoinPoint point, DS ds){
DataSourceContextHolder.clearDB();
}
}
service层方法添加自定义注解
@DS
public Long getId1(Long id){}
@DS("db2")
public Long getId2(Long id){}
至此,就实现了多数据源的动态切换。