SpringBoot框架:通過AOP和自定義注解完成druid連接池的動態數據源切換(三)


一、引入依賴

  引入數據庫連接池的依賴——druid和面向切面編程的依賴——aop,如下所示:

        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!-- aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

二、創建數據庫

  1、主數據庫

  使用前文中已經創建的名為spring_boot_demo的數據庫。

  spring_boot_demo中t_user數據如下:

  

  2、輔數據庫

  數據庫名為other_data,庫中建立數據表t_user,表結構與spring_boot_demo中的t_user一致。

  實際項目中,大多是跨數據庫的數據源切換,常用在同公司的多個不同系統中共用一個用戶數據庫,或者二次開發項目在原有數據庫基礎上做拓展,保留原有的數據連接。

  這里為了方便操作,就都在mysql下部署數據庫並且使表結構一致,方便形成數據對比。

  other_data中插入數據如下:

  

三、修改數據庫連接配置信息

  在application.yml中,修改數據庫連接配置如下:

spring:
  application:
    name: spring-boot-demo
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      primary:
        url: jdbc:mysql://localhost:3306/spring_boot_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8
        username: root
        password: root
        driverClassName: com.mysql.jdbc.Driver
      second:
        url: jdbc:mysql://localhost:3306/other_data?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8
        username: root
        password: root
        driverClassName: com.mysql.jdbc.Driver

  

四、編寫代碼

  結構如下:

  

  1、枚舉類DataSourceName:

  該類用來存放數據源的名稱,定義兩個數據源名稱分別為PRIMARYSECOND

package com.example.demo.enums;

/**
 * DataSource的name常量
 * 便於切換
 * @author 我命傾塵
 */
public enum DataSourceName {

    /**
     * 主數據源 spring_boot_demo
     */
    PRIMARY("PRIMARY"),

    /**
     * 副數據源other_data
     */
    SECOND("SECOND");

    private String dataSourceName;
    private DataSourceName(String dataSourceName){
        this.dataSourceName=dataSourceName;
    }
    DataSourceName(){

    }
    public String getDataSourceName(){
        return this.dataSourceName;
    }
}

  2、配置類DynamicDataSourceConfig:

  通過@ConfigurationProperties讀取配置文件中的數據源配置信息,並通過DruidDataSourceBuilder.create().build()創建數據連接,將多個數據源放入map,注入到IoC中:

package com.example.demo.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.example.demo.bean.DynamicDataSource;
import com.example.demo.enums.DataSourceName;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 我命傾塵
 */
@Configuration
public class DynamicDataSourceConfig {
    /**
     * 創建DataSource Bean,將數據源配置從配置文件中讀出
     */ @Bean @ConfigurationProperties("spring.datasource.druid.primary")
    public DataSource oneDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean @ConfigurationProperties("spring.datasource.druid.second")
    public DataSource twoDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 將數據源放入到 這個map中,注入到IoC
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource){
        Map<Object,Object> targetDataSources=new HashMap<>(2);
        targetDataSources.put(DataSourceName.PRIMARY.getDataSourceName(),oneDataSource);
        targetDataSources.put(DataSourceName.SECOND.getDataSourceName(),twoDataSource);
        return new DynamicDataSource(oneDataSource,targetDataSources);
    }
}

  3、動態數據源DynamicDataSource:

  通過繼承AbstractRoutingDataSource類,在構造函數中調用父類的方法,將配置類中放入map的數據源集合定為備選數據源,將傳來的oneDataSource作為默認數據源

package com.example.demo.bean;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

/**
 * @author 我命傾塵
 */
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();
    }
    public static String getDataSource(){
        return contextHolder.get();
    }
    public static void setDataSource(String dataSource){
        contextHolder.set(dataSource);
    }
    public static void clearDataSource(){
        contextHolder.remove();
    }

    @Override protected Object determineCurrentLookupKey() {
        return getDataSource();
    }
}

  setTargetDataSources設置備選的數據源集合,

  setDefaultTargetDataSource設置默認數據源,

  determineCurrentLookupKey決定當前數據源的對應的key

  4、自定義注釋類DataSource:

package com.example.demo.annotation;

import com.example.demo.enums.DataSourceName;

import java.lang.annotation.*;

/**
 * @author 我命傾塵
 */ @Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    DataSourceName value() default DataSourceName.PRIMARY;
}

  @Documented指定被標注的注解會包含在javadoc中,

  @Target指定注釋可能出現在Java程序中的語法位置(ElementType.METHOD則說明注解可能出現在方法上),

  @Retention指定注釋的保留時間(RetentionPolicy.RUNTIME則是在java文件編譯成class類時也依舊保存該注釋)。

  5、切面類DataSourceAspect:

package com.example.demo.aspect;

import com.example.demo.annotation.DataSource;
import com.example.demo.bean.DynamicDataSource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author 我命傾塵
 */
@Aspect
@Component
public class DataSourceAspect implements Ordered {
    private Logger log= LoggerFactory.getLogger(DataSourceAspect.class);

    /**
     * 切點:所有配置DataSource注解的方法
     */ @Pointcut("@annotation(com.example.demo.annotation.DataSource)")
    public void dataSourcePointCut(){

    }

    @Around(value = "dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable{
        Object result;
        MethodSignature signature=(MethodSignature)point.getSignature();
        Method method=signature.getMethod();
        DataSource ds=method.getAnnotation(DataSource.class);
        /**
         * 判斷DataSource的值
         * 獲取當前方法應用的數據源
         */
        DynamicDataSource.setDataSource(ds.value().getDataSourceName());
        try{
            result=point.proceed();
        }finally {
            DynamicDataSource.clearDataSource();
        }
        return result;
    }

    @Override public int getOrder() {
        return 1;
    }
}

  Spring框架有很多相同接口的實現類,提供了Ordered接口來處理相同接口實現類之間的優先級問題。

  通過環繞切面,對方法上的注釋進行了檢驗,如果獲取到有DataSource注釋,則會進行數據源的切換,否則按默認數據源進行處理。

  6、引入配置類:

  既然手動配置了動態切換數據連接池,就要在入口類中排除自動引入,並引入數據源的配置類,以及開啟AOP:

package com.example.demo;

import com.example.demo.config.DynamicDataSourceConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;

@MapperScan("com.example.demo.mapper")
@Import({DynamicDataSourceConfig.class})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableAspectJAutoProxy public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

  通過@Import引入配置類,在@SpringBootApplication后進行自動引入的排除。

  @EnableAspectJAutoProxy用來開啟AOP。

五、簡單測試

  1、不使用注解:

  UserController中的方法如下:

  @RequestMapping("/user/age")   public int getAgeOfUser(){
        return userService.getAgeByUsername("springbootdemo");
  }

  所得到的結果如下:

  

  這個結果是從主數據源spring_boot_demo數據庫的表中得到的數據。

  2、在方法前添加注解@DataSource(DataSourceName.SECOND)

  UserController中的方法如下:

  @RequestMapping("/user/age")
  @DataSource(DataSourceName.SECOND)
  public int getAgeOfUser(){
        return userService.getAgeByUsername("springbootdemo");
  }

  結果如下:

  

  這個結果則是從輔數據源other_data中得到的數據。

 

  前言:SpringBoot框架:使用mybatis連接mysql數據庫完成數據訪問(二)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM