SpringBoot + Mybatis配合AOP和註解實現動態數據源切換配置

前言:

隨着應用用戶數量的增長,相應的併發請求的數量也會跟着不斷增長,慢慢地,單個數據庫已經沒有辦法知足咱們頻繁的數據庫操做請求了,在某些場景下,咱們可能會須要配置多個數據源,使用多個數據源(例如實現數據庫的讀寫分離)來緩解系統的壓力等,一樣的,Springboot官方提供了相應的實現來幫助開發者們配置多數據源,通常分爲兩種方式(目前我所瞭解到的),分包和AOP,而在上一篇文章Springboot +Mybatis實現多數據源配置中,咱們實現了靜態多數據源的配置,可是這種方式怎麼說呢,在實際的使用中不夠靈活,爲了解決這個問題,咱們可使用上文提到的第二種方法,即便用AOP面向切面編程的方式配合咱們的自定義註解來實如今不一樣數據源之間動態切換的目的。php

1. 數據庫準備:

數據庫準備仍然和以前的例子相同,具體建表sql語句則再也不詳細說明,表格以下:java

數據庫 testdatasource1 testdatasource2
數據表 sys_user sys_user2
字段 user_id(int), user_name(varchar) user_age(int)

並分別插入兩條記錄,爲了方便對比,其中testdatasource1爲芳年25歲的張三, testdatasource2爲芳年30歲的李四。mysql

2. 環境準備:

首先新建一個Springboot項目,我這裏版本是2.1.7.RELEASE,並在pom文件中引入相關依賴,和上次相比,此次主要額外新增了aop相關的依賴,以下:git

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
複製代碼

3.代碼部分

首先呢,在咱們Springboot的配置文件中配置咱們的datasourse,和以往不同的是,由於咱們有兩個數據源,因此要指定相關數據庫的名稱,其中主數據源爲primary,次數據源爲secondary以下:github

#配置主數據庫
spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

##配置次數據庫
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver


spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
複製代碼

須要咱們注意的是,Springboot2.0 在配置數據庫鏈接的時候須要使用jdbc-url,若是隻使用url的話會報spring

jdbcUrl is required with driverClassName.錯誤。sql

新建一個配置文件,DynamicDataSourceConfig 用來配置咱們相關的bean,代碼以下數據庫

@Configuration
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages 咱們接口文件的地址
public class DynamicDataSourceConfig {

    // 將這個對象放入Spring容器中
    @Bean(name = "PrimaryDataSource")
    // 表示這個數據源是默認數據源
    @Primary
    // 讀取application.properties中的配置參數映射成爲一個對象
    // prefix表示參數的前綴
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource getDateSource1() {
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "SecondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource getDateSource2() {
        return DataSourceBuilder.create().build();
    }

    
    @Bean(name = "dynamicDataSource")
    public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource, @Qualifier("SecondaryDataSource") DataSource secondaryDataSource) {
        
        //這個地方是比較核心的targetDataSource 集合是咱們數據庫和名字之間的映射
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource);
        targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource);
        DynamicDataSource dataSource = new DynamicDataSource(); 
        dataSource.setTargetDataSources(targetDataSource);
        dataSource.setDefaultTargetDataSource(primaryDataSource);//設置默認對象
        return dataSource;
    }
    
    
    @Bean(name = "SqlSessionFactory")
    public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource);
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*/*.xml"));//設置咱們的xml文件路徑
        return bean.getObject();
    }
}

複製代碼

而在這全部的配置中,最核心的地方就是DynamicDataSource這個類了,DynamicDataSource是咱們自定義的動態切換數據源的類,該類繼承了AbstractRoutingDataSource 類並重寫了它的determineCurrentLookupKey()方法。編程

AbstractRoutingDataSource 類內部維護了一個名爲targetDataSources的Map,並提供的setter方法用於設置數據源關鍵字與數據源的關係,實現類被要求實現其determineCurrentLookupKey()方法,由此方法的返回值決定具體從哪一個數據源中獲取鏈接。同時AbstractRoutingDataSource類提供了程序運行時動態切換數據源的方法,在dao類或方法上標註須要訪問數據源的關鍵字,路由到指定數據源,獲取鏈接。瀏覽器

DynamicDataSource代碼以下:

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
        return dataBaseType;
    }

}


複製代碼

DataSourceType類的代碼以下:

public class DataSourceType {

    //內部枚舉類,用於選擇特定的數據類型
    public enum DataBaseType {
        Primary, Secondary
    }

    // 使用ThreadLocal保證線程安全
    private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();

    // 往當前線程裏設置數據源類型
    public static void setDataBaseType(DataBaseType dataBaseType) {
        if (dataBaseType == null) {
            throw new NullPointerException();
        }
        TYPE.set(dataBaseType);
    }

    // 獲取數據源類型
    public static DataBaseType getDataBaseType() {
        DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get();
        return dataBaseType;
    }

    // 清空數據類型
    public static void clearDataBaseType() {
        TYPE.remove();
    }

}

複製代碼

接下來編寫咱們相關的Mapper和xml文件,代碼以下:

@Component
@Mapper
public interface PrimaryUserMapper {

    List<User> findAll();
}


@Component
@Mapper
public interface SecondaryUserMapper {

    List<User> findAll();
}


複製代碼
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jdkcb.mybatisstuday.mapper.one.PrimaryUserMapper">

    <select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User">
                select * from sys_user;
    </select>

</mapper>


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jdkcb.mybatisstuday.mapper.two.SecondaryUserMapper">

    <select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User">
                select * from sys_user2;
    </select>


</mapper>
複製代碼

相關接口文件編寫好以後,就能夠編寫咱們的aop代碼了:

@Aspect
@Component
public class DataSourceAop {
    //在primary方法前執行
    @Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.primary(..))")
    public void setDataSource2test01() {
        System.err.println("Primary業務");
        DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
    }

    //在secondary方法前執行
    @Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.secondary(..))")
    public void setDataSource2test02() {
        System.err.println("Secondary業務");
        DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
    }
}

複製代碼

編寫咱們的測試 UserController: 代碼以下:

@RestController
public class UserController {

    @Autowired
    private PrimaryUserMapper primaryUserMapper;
    @Autowired
    private SecondaryUserMapper secondaryUserMapper;


    @RequestMapping("primary")
    public Object primary(){
        List<User> list = primaryUserMapper.findAll();
        return list;
    }
    @RequestMapping("secondary")
    public Object secondary(){
        List<User> list = secondaryUserMapper.findAll();
        return list;
    }

}

複製代碼

4. 測試:

啓動項目,在瀏覽器中分別輸入http://127.0.0.1:8080/primary 和http://127.0.0.1:8080/primary ,結果以下:

[{"user_id":1,"user_name":"張三","user_age":25}]

[{"user_id":1,"user_name":"李四","user_age":30}]
複製代碼

5.等等

等等,嘖嘖嘖,我看你這不行啊,還不夠靈活,幾個菜啊,喝成這樣,這就算靈活了?

那確定不能的,aop也有aop的好處,好比兩個包下的代碼分別用兩個不一樣的數據源,就能夠直接用aop表達式就能夠完成了,可是,若是想本例中方法級別的攔截,就顯得優勢不太靈活了,這個適合就須要咱們的註解上場了。

6.配合註解實現

首先自定義咱們的註解 @DataSource

/** * 切換數據註解 能夠用於類或者方法級別 方法級別優先級 > 類級別 */
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "primary"; //該值即key值,默認使用默認數據庫
}
複製代碼

經過使用aop攔截,獲取註解的屬性value的值。若是value的值並無在咱們DataBaseType裏面,則使用咱們默認的數據源,若是有的話,則切換爲相應的數據源。

@Aspect
@Component
public class DynamicDataSourceAspect {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    @Before("@annotation(dataSource)")//攔截咱們的註解
    public void changeDataSource(JoinPoint point, DataSource dataSource) throws Throwable {
        String value = dataSource.value();
        if (value.equals("primary")){
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
        }else if (value.equals("secondary")){
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
        }else {
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);//默認使用主數據庫
        }

    }

    @After("@annotation(dataSource)") //清除數據源的配置
    public void restoreDataSource(JoinPoint point, DataSource dataSource) {
        DataSourceType.clearDataBaseType();


    }
}
複製代碼

7 .測試:

修改咱們的mapper,添加咱們的自定義的@DataSouse註解,並註解掉咱們DataSourceAop類裏面的內容。以下:

@Component
@Mapper
public interface PrimaryUserMapper {

    @DataSource
    List<User> findAll();
}

@Component
@Mapper
public interface SecondaryUserMapper {

    @DataSource("secondary")//指定數據源爲:secondary
    List<User> findAll();
}


複製代碼

啓動項目,在瀏覽器中分別輸入http://127.0.0.1:8080/primary 和http://127.0.0.1:8080/primary ,結果以下:

[{"user_id":1,"user_name":"張三","user_age":25}]

[{"user_id":1,"user_name":"李四","user_age":30}]
複製代碼

到此,就算真正的大功告成啦。

最後的最後,你們好,我是韓數,哼,關注我,有你好果子吃(叉腰)。

記得點個贊再走哦~

等一下:

相關源碼歡迎去個人github下載(歡迎star):

github.com/hanshuaikan…

相關文章
相關標籤/搜索