1. 创建一个管理数据源key值的类RoutingDataSourceHolder?
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
?
public class RoutingDataSourceHolder {
? ? private static Logger logger = LogManager.getLogger();
?
? ? private static final ThreadLocal<String> dataSources = new ThreadLocal<>();
?
? ? //一个事务内用同一个数据源
? ? public static void setDataSource(String dataSourceName) {
? ? ? ? if (dataSources.get() == null) {
? ? ? ? ? ? dataSources.set(dataSourceName);
? ? ? ? ? ? logger.info("设置数据源:{}", dataSourceName);
? ? ? ? }
? ? }
?
? ? public static String getDataSource() {
? ? ? ? return dataSources.get();
? ? }
?
? ? public static void clearDataSource() {
? ? ? ? dataSources.remove();
? ? }
}
代码设置了一个事务内使用同一个数据源。
2. 创建一个类继承AbstractRoutingDataSource
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
?
public class RoutingDataSource extends AbstractRoutingDataSource {
? ? private Logger logger = LogManager.getLogger();
?
? ? @Override
? ? protected Object determineCurrentLookupKey() {
? ? ? ? String dataSource = RoutingDataSourceHolder.getDataSource();
? ? ? ? logger.info("使用数据源:{}", dataSource);
? ? ? ? return dataSource;
? ? }
}
重写 determineCurrentLookupKey方法,返回要使用的数据源key值。?
以上两个类解决了动态数据源key值的问题,下面处理初始化targetDataSources对象。
3. 配置主从数据库类DataSourceConfigurer
1. DataSourceConfigurer
import com.alibaba.druid.pool.DruidDataSource;
import com.custom.common.utils.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
?
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
?
/**
?* 配置主从数据库
?*/
@Configuration
public class DataSourceConfigurer {
? ? private Logger logger = LogManager.getLogger();
?
? ? public final static String MASTER_DATASOURCE = "masterDataSource";
? ? public final static String SLAVE_DATASOURCE = "slaveDataSource";
?
? ? @Bean(MASTER_DATASOURCE)
? ? @ConfigurationProperties(prefix = "spring.datasource")
? ? public DruidDataSource masterDataSource(DataSourceProperties properties) {
? ? ? ? DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
? ? ? ? logger.info("配置主数据库:{}", build);
? ? ? ? return build;
? ? }
?
? ? @Bean(SLAVE_DATASOURCE)
? ? @ConfigurationProperties(prefix = "spring.slave-datasource")
? ? public DruidDataSource slaveDataSource(DataSourceProperties properties) {
? ? ? ? DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
? ? ? ? logger.info("配置从数据库:{}", build);
? ? ? ? return build;
? ? }
?
? ? /**
? ? ?* Primary 优先使用该Bean
? ? ?* DependsOn 先执行主从数据库的配置
? ? ?* Qualifier 指定使用哪个Bean
? ? ?*
? ? ?* @param masterDataSource
? ? ?* @param slaveDataSource
? ? ?* @return
? ? ?*/
? ? @Bean
? ? @Primary
? ? @DependsOn(value = {MASTER_DATASOURCE, SLAVE_DATASOURCE})
? ? public DataSource routingDataSource(@Qualifier(MASTER_DATASOURCE) DruidDataSource masterDataSource,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Qualifier(SLAVE_DATASOURCE) DruidDataSource slaveDataSource) {
? ? ? ? if (StringUtils.isBlank(slaveDataSource.getUrl())) {
? ? ? ? ? ? logger.info("没有配置从数据库,默认使用主数据库");
? ? ? ? ? ? return masterDataSource;
? ? ? ? }
? ? ? ? Map<Object, Object> map = new HashMap<>();
? ? ? ? map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource);
? ? ? ? map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource);
? ? ? ? RoutingDataSource routing = new RoutingDataSource();
? ? ? ? //设置动态数据源
? ? ? ? routing.setTargetDataSources(map);
? ? ? ? //设置默认数据源
? ? ? ? routing.setDefaultTargetDataSource(masterDataSource);
? ? ? ? logger.info("主从数据库配置完成");
? ? ? ? return routing;
? ? }
}
设置初始化targetDataSources对象关键代码
Map<Object, Object> map = new HashMap<>(); map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource); map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource); RoutingDataSource routing = new RoutingDataSource(); //设置动态数据源 routing.setTargetDataSources(map); //设置默认数据源 routing.setDefaultTargetDataSource(masterDataSource);
2. application.properties
# ----------------------------------------
# 主数据库
# ----------------------------------------
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/custom?useunicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
?
# ----------------------------------------
# 从数据库
# ----------------------------------------
spring.slave-datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.slave-datasource.url=jdbc:mysql://127.0.0.1:3309/custom?useunicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
spring.slave-datasource.username=root
spring.slave-datasource.password=root
spring.slave-datasource.driver-class-name=com.mysql.cj.jdbc.Driver
一个配置类处理了targetDataSources对象的初始化.
那问题都处理了,那具体要怎么使用呢,关键就是在事务之前调用RoutingDataSourceHolder.setDataSource()方法就可以了。我们写一个aop实现吧。
4. 创建aop注解和类
1. DataSourceWith
import java.lang.annotation.*;
?
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface DataSourceWith {
? ? String key() default "";
}
2.DataSourceWithAspect
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
?
import java.lang.reflect.Method;
?
@Aspect
@Order(-1)// 保证该AOP在@Transactional之前运行
@Component
public class DataSourceWithAspect {
?
? ? /**
? ? ?* 使用DataSourceWith注解就拦截
? ? ?*/
? ? @Pointcut("@annotation(com.custom.configure.datasource.DataSourceWith)||@within(com.custom.configure.datasource.DataSourceWith)")
? ? public void doPointcut() {
?
? ? }
?
? ? /**
? ? ?* 方法前,为了在事务前设置
? ? ?*/
? ? @Before("doPointcut()")
? ? public void doBefore(JoinPoint joinPoint) {
? ? ? ? MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
? ? ? ? Method method = methodSignature.getMethod();
? ? ? ? // 获取注解对象
? ? ? ? DataSourceWith dataSource = method.getAnnotation(DataSourceWith.class);
? ? ? ? if (dataSource == null) {
? ? ? ? ? ? //方法没有就获取类上的
? ? ? ? ? ? dataSource = method.getDeclaringClass().getAnnotation(DataSourceWith.class);
? ? ? ? }
? ? ? ? String key = dataSource.key();
? ? ? ? RoutingDataSourceHolder.setDataSource(key);
? ? }
?
? ? @After("doPointcut()")
? ? public void doAfter(JoinPoint joinPoint) {
? ? ? ? RoutingDataSourceHolder.clearDataSource();
? ? }
?
}
3.使用和效果
@DataSourceWith在方法上或者类上都可以。?
/**
? ? ?* 获取部门列表
? ? ?**/
? ? @DataSourceWith(key = DataSourceConfigurer.SLAVE_DATASOURCE)
? ? public List<Dept> findDeptTree() {
? ? ? ? logger.debug("获取部门树数据");
? ? ? ? List<Dept> deptList = new ArrayList<>();
? ? ? ? return deptList;
? ? }
结果:动态切换成功
原文链接:https://blog.csdn.net/m0_68615056/article/details/123738282
|