概述:2022年第一天,在这祝大家新年快乐,好运连连,事业爱情双丰收。本文主要是通过注解结合aop的方式实现多数据源的动态切换。
一、配置文件
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
#主数据源
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://ip地址:3306/数据库名?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: 用户名
password: 密码
#另一个数据源
slave-one:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://ip地址:3306/数据库名?autoReconnect=true&useUnicode=true&characterEncoding=utf8&useSSL=false
username: 用户名
password: 密码
连接池用的druid,上面配置根据自身更改。
二、去除springboot的数据源自动配置
//主启动类中
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
三、定义注解和切面
//注解
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default (默认数据源名称)DataSourceName.MASTER;
}
//切面
@Aspect
@Component
public class DataSourceAspect implements Ordered {
/**
* 切点: 所有配置 DataSource 注解的方法
*/
@Pointcut("@annotation(注解类全限定名)")
public void dataSourcePointCut() {}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource ds = method.getAnnotation(DataSource.class);
// 动态判断当前方法应用哪个数据源
DynamicDataSource.setDataSource(ds.value());
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
}
}
@Override
public int getOrder() {
return 1;
}
}
四、配置数据源bean
//数据源bean
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "slaveOneDataSource")
@ConfigurationProperties("spring.datasource.druid.slave-one")
public DataSource slaveOneDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveOneDataSource") DataSource slaveOneDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DataSourceName.MASTER, masterDataSource);
targetDataSources.put(DataSourceName.SLAVE_ONE, slaveOneDataSource);
// 还有数据源,在targetDataSources中继续添加
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
//通过AbstractRoutingDataSource实现动态数据源
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
/**
* 配置DataSource, defaultTargetDataSource为主数据库
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
五、测试方法
//数据源名称,也可定义为枚举类
public interface DataSourceName {
String MASTER = "MASTER";
String SLAVE_ONE = "SLAVE_ONE";
}
//使用方式:service的impl加上注解@DataSource(数据源名称)即可
@DataSource(DataSourceName.SLAVE_ONE)
@Override
public Integer saveData(Object object) {
return xxxMapper.saveData(object);
}
注意:
- 调用数据源切换方法的位置不能在同一个类中
- 数据源切换必须在事务开启前
比如我之前遇到的问题:我在service层通过切面方式对update*、save*等更新方法加了事务,然后在其他service中调用了带数据源切换的方法,结果,切换失败了,因为在service中已经开启了事务,确定了数据源,再调用带切换数据源注解的方法已经不会生效了。