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 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();
}
}
代码设置了一个事务内使用同一个数据源。
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对象。
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
设置初始化targetDataSources对象关键代码
Map
# ----------------------------------------
# 主数据库
# ----------------------------------------
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实现吧。
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();
}
}
@DataSourceWith在方法上或者类上都可以。
/**
* 获取部门列表
**/
@DataSourceWith(key = DataSourceConfigurer.SLAVE_DATASOURCE)
public List findDeptTree() {
logger.debug("获取部门树数据");
List deptList = new ArrayList<>();
return deptList;
}
结果:动态切换成功
原文链接:https://blog.csdn.net/m0_68615056/article/details/123738282