SpringBoot+AOP实现动态切换数据源

 首先描述下笔者的基本步骤:

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地址:

dynamicDataSource2: SpringBoot+AOP实现动态切换数据源

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值