首先描述下笔者的基本步骤:
1.yml配置文件中定义多数据源;
2.自定义一个注解类(@DataSource 可标注在类或方法上),定义String类型的变量value,value中存储数据源名称;
3.创建子类(MyThreadLocal),定义一个ThreadLocal类型的静态变量,用来存储第2条中@DataSource注解定义的value变量的值,并且创建方法来获取、移除ThreadLocal中value值;
4.创建一个切面类,定义切点(添加了@DataSource注解的类或方法),使用环绕通知,在原方法执行前,将@DataSource注解的value值存入ThreadLocal静态变量中 => 执行原方法 => 移除ThreadLocal中value值;
5.创建一个类(DruidProperties),读取配置文件中数据源信息,并生成多套数据源配置,用Map<Object,Object>存储;
6.创建一个类,继承AbstractRoutingDataSource类(这个类可以实现动态数据源切换),重写determineCurrentLookupKey()方法,指定要切换数据源的key,在子类构造方法中注入所有数据源。
1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.yml配置
server: port: 8081 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver ds: master: #数据源1 url: jdbc:mysql://192.168.192.129:3306/test01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 slave: #数据源2 url: jdbc:mysql://192.168.192.129:3306/test02?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 initialSize: 5 minIdle: 10 maxActive: 20 maxWait: 60000
3.自定义注解@DataSource
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})//作用在类和方法上
// @Inherited//当以后我们在定义一个作用于类的注解时候,如果希望该注解也作用于其子类,那么可以用@Inherited 来进行修饰。
public @interface DataSource {
String value() default DataSourceName.DEFAULT_DATASOURCE_NAME;
}
value默认值为常量("master")
public interface DataSourceName {
String DEFAULT_DATASOURCE_NAME="master";
String OTHER_DATASOURCE_NAME="slave";
}
4.创建类,声明ThreadLocal静态变量,并编写存储、获取、移除方法
public class MyThreadLocal {
//ThreadLocal 为每一个线程都提供一份变量的副本,从而实现同时访问而互不相干扰
private static ThreadLocal<String> threadLocal=new ThreadLocal<>();
/**
* set(T value):设置线程本地变量的内容。
* get():获取线程本地变量的内容。
* remove():移除线程本地变量。注意在线程池的线程复用场景中在线程执行完毕时一定要调用remove,避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。
* @param dsType
*/
//存储数据源名称
public static void setDataSourceName(String dataSourceName){
threadLocal.set(dataSourceName);
}
//获取当前线程所使用的数据源名称
public static String getDataSourceName(){
return threadLocal.get();
}
//移除当前线程存储的数据源名称
public static void removeDataSourceName(){
threadLocal.remove();
}
}
5.创建切面类,拦截@DataSource标注的方法或类
@Aspect
@Component
public class DataSourceAspect {
//定义切点,拦截@DataSource标注的类和方法
@Pointcut("@annotation(com.xxx.ds.annotation.DataSource)||@within(com.xxx.ds.annotation.DataSource)")
public void pointCut(){}
@Around("pointCut()")
public Object useMyDataSource(ProceedingJoinPoint pjp){
// 获取注解中数据源名称
// 1.获取当前方法
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
// 2.获取当前方法上@DataSource的值,若方法上不存在,查找类上的值
DataSource dataSource=getDataSource(methodSignature);
// 3.若注解不为空,获取值,并存入本地线程
if(dataSource!=null){
String value = dataSource.value();
//将值存入ThreadLocal中
MyThreadLocal.setDataSourceName(value);
}
//4.执行原方法,有返回值直接返回
try {
return pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}finally {
//5.移除本地线程的值
MyThreadLocal.removeDataSourceName();
}
return null;
}
private DataSource getDataSource(MethodSignature methodSignature) {
//查找方法上的@DataSource注解 AnnotationUtils.findAnnotation(Method,annotationType)
DataSource annotation = AnnotationUtils.findAnnotation(methodSignature.getMethod(), DataSource.class);
if(annotation!=null){//方法上有值,直接返回
return annotation;
}
//如果方法中没有@DataSource注解,查找类上的@DataSource注解 AnnotationUtils.findAnnotation(Class,annotationType)
return AnnotationUtils.findAnnotation(methodSignature.getDeclaringType(), DataSource.class);
}
}
6.创建类,读取yml配置文件中的值,并存储所有数据源
@Component
@ConfigurationProperties("spring.datasource")
public class DruidProperties {
private String type;
private String driverClassName;
private Map<String,Map<String,String>> ds;
private Integer initialSize;
private Integer minIdle;
private Integer maxActive;
private Integer maxWait;
//将yml中所有数据源加载到DruidDataSource中
public Map<Object,Object> loadAllDataSource(){
Map<Object, Object> map = new HashMap<>();
try {
for (Map.Entry<String, Map<String, String>> entry : ds.entrySet()) {
//map中的内容为:{master={Datasource{xxx=xxx,xxx=xxx},salver={DataSource{xxx=xxx,xxx=xxx}}}}
/**
* 将数据源名称设置为主键,(url、username、password构造DruidSource对象,将DruidSource传入initDataSource方法,初始化DataSource)
*/
map.put(entry.getKey(),initDataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(entry.getValue())));
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
public DataSource initDataSource(DruidDataSource druidDataSource){
druidDataSource.setDbType(type);
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setInitialSize(initialSize);
druidDataSource.setMinIdle(minIdle);
druidDataSource.setMaxActive(maxActive);
druidDataSource.setMaxWait(maxWait);
return druidDataSource;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Map<String, Map<String, String>> getDs() {
return ds;
}
public void setDs(Map<String, Map<String, String>> ds) {
this.ds = ds;
}
public Integer getInitialSize() {
return initialSize;
}
public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Integer getMaxActive() {
return maxActive;
}
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
public Integer getMaxWait() {
return maxWait;
}
public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
}
}
7.创建类继承AbstractRoutingDataSource,获取ThreadLocal中的数据源名称,动态加载
@Component
public class MyRoutingDataSource extends AbstractRoutingDataSource {
public MyRoutingDataSource(DruidProperties druidProperties) {
//1.设置所有数据源
super.setTargetDataSources(druidProperties.loadAllDataSource());
//2.设置默认数据源
super.setDefaultTargetDataSource(druidProperties.loadAllDataSource().get(DataSourceName.DEFAULT_DATASOURCE_NAME));
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
//根据ThreadLocal中数据源名称选择数据源
System.out.println("线程中的值为: "+MyThreadLocal.getDataSourceName());
return MyThreadLocal.getDataSourceName();
}
}
8.在业务方法上添加@DataSource注解,并制定value值,实现动态切换
最后附上该demo地址: