1. 程式人生 > >43. Spring Boot動態資料來源(多資料來源自動切換)【從零開始學Spring Boot】

43. Spring Boot動態資料來源(多資料來源自動切換)【從零開始學Spring Boot】

【視訊&交流平臺】

http://study.163.com/course/introduction.htm?courseId=1004329008&utm_campaign=commission&utm_source=400000000155061&utm_medium=share

http://study.163.com/course/introduction.htm?courseId=1004638001&utm_campaign=commission&utm_source=400000000155061&utm_medium=share

https://gitee.com/happyangellxq520/spring-boot

http://412887952-qq-com.iteye.com/blog/2321532



在上一篇我們介紹了多資料來源,但是我們會發現在實際中我們很少直接獲取資料來源物件進行操作,我們常用的是jdbcTemplate或者是jpa進行操作資料庫。那麼這一節我們將要介紹怎麼進行多資料來源動態切換。新增本文實現的程式碼之後,只需要配置要資料來源就可以直接通過註解使用,在實際使用的時候特別的簡單。那麼本章主要分以下幾個步驟進行實戰。

(1)新建maven javaproject;

(2)在pom.xml新增依賴包;

(3)編寫啟動類App.java

(4)編寫配置檔案application.properties;

(5)動態資料來源路由類;

(6)註冊多資料來源;

(7)測試類測試;

接下來我們看看每一步具體的實現吧:

(1)新建maven java project;

新建一個maven project,取名為:spring-boot-multi-ds

(2)在pom.xml新增依賴包;

在pom.xml檔案中加入依賴的庫檔案,主要是springboot基本的,資料庫驅動,spring-jpa支援即可,具體pom.xml檔案如下:

<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">

 <modelVersion>4.0.0</modelVersion>

 <groupId>com.kfit</groupId>

 <artifactId>spring-boot-multids</artifactId>

 <version>0.0.1-SNAPSHOT</version>

 <packaging>jar</packaging>

 <name>spring-boot-multids</name>

 <url>http://maven.apache.org</url>

 <properties>

   <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

   <!--jdk版本號,這裡需要你本地進行的jdk進行修改,這裡angel使用的是1.8的版本. -->

   <java.version>1.8</java.version>

 </properties>

  <!--

              springboot父節點依賴,

引入這個之後相關的引入就不需要新增version配置,

              springboot會自動選擇最合適的版本進行新增。

在這裡使用的1.3.3版本,可能目前官方有最新的版本了,大家可以

使用最新的版本。

        -->

       <parent>

              <groupId>org.springframework.boot</groupId>

              <artifactId>spring-boot-starter-parent</artifactId>

              <version>1.3.3.RELEASE</version>

       </parent>

 <dependencies>

     <!-- 單元測試包,在這裡沒有使用到.-->

   <dependency>

     <groupId>junit</groupId>

     <artifactId>junit</artifactId>

     <scope>test</scope>

   </dependency>

   <!-- spring boot web支援:mvc,aop...

這個是最基本的,基本每一個基本的demo都是需要引入的。

   -->

       <dependency>

              <groupId>org.springframework.boot</groupId>

              <artifactId>spring-boot-starter-web</artifactId>

       </dependency>

       <!-- mysql驅動.

我們的demo是多資料來源,在這裡使用Mysql資料庫.

       -->

       <dependency>

              <groupId>mysql</groupId>

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

       </dependency>

       <!-- springjpa

              springjpa中帶有自帶的tomcat 資料連線池;

在程式碼中我們也需要用到.

        -->

       <dependency>

              <groupId>org.springframework.boot</groupId>

              <artifactId>spring-boot-starter-data-jpa</artifactId>

       </dependency>

 </dependencies>

</project>

在上面的配置檔案中都有相應的解釋,大家可以自己解讀下。

(3)編寫啟動類App.java

編寫spring boot的啟動類:

com.kfit.App:

package com.kfit;

import org.springframework.boot.SpringApplication;

importorg.springframework.boot.autoconfigure.SpringBootApplication;

/**

 *

 *@author Angel(QQ:412887952)

 *@version v.0.1

 */

@SpringBootApplication

publicclass App {

       publicstaticvoid main(String[]args) {

              SpringApplication.run(App.class,args);

       }

}

(4)編寫配置檔案application.properties;

在這裡主要是多資料來源和jpa的配置:

src/main/resources/application.properties:

########################################################

###配置檔案包括1個主資料來源和多個數據源,

###其中主資料來源在Spring中的beanName預設為dataSource,

###另外幾個資料來源的beanName分包為:ds1、ds2、ds3

###其中datasource的type屬性可以具體指定到我們需要的資料來源上面,

###不指定情況下預設為:org.apache.tomcat.jdbc.pool.DataSource

###當然你也可以把這些資料來源配置到主dataSource資料庫中,然後讀取資料庫生成多資料來源。當然這樣做的必要性並不大,難不成資料來源還會經常變嗎。

########################################################

#主資料來源,預設的

#spring.datasource.type=com.zaxxer.hikari.HikariDataSource

spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://localhost:3306/test

spring.datasource.username=root

spring.datasource.password=root

#更多資料來源

custom.datasource.names=ds1,ds2,ds3

#custom.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource

custom.datasource.ds1.driverClassName=com.mysql.jdbc.Driver

custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1

custom.datasource.ds1.username=root

custom.datasource.ds1.password=root

#custom.datasource.ds2.type=com.zaxxer.hikari.HikariDataSource

custom.datasource.ds2.driverClassName=com.mysql.jdbc.Driver

custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test

custom.datasource.ds2.username=root

custom.datasource.ds2.password=root

#custom.datasource.ds3.type=com.zaxxer.hikari.HikariDataSource

custom.datasource.ds3.driverClassName=com.mysql.jdbc.Driver

custom.datasource.ds3.url=jdbc:mysql://localhost:3306/test

custom.datasource.ds3.username=root

custom.datasource.ds3.password=root

#下面為連線池的補充設定,應用到上面所有資料來源中

spring.datasource.maximum-pool-size=100

spring.datasource.max-idle=10

spring.datasource.max-wait=10000

spring.datasource.min-idle=5

spring.datasource.initial-size=5

spring.datasource.validation-query=SELECT 1

spring.datasource.test-on-borrow=false

spring.datasource.test-while-idle=true

spring.datasource.time-between-eviction-runs-millis=18800

########################################################

### Java Persistence Api

########################################################

# Specify the DBMS

spring.jpa.database= MYSQL

# Show or not log for each sqlquery

spring.jpa.show-sql= true

# Hibernate ddl auto (create,create-drop, update)

spring.jpa.hibernate.ddl-auto= update

# Naming strategy

#[org.hibernate.cfg.ImprovedNamingStrategy  #org.hibernate.cfg.DefaultNamingStrategy]

spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy

# stripped before adding them tothe entity manager)

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

(5)動態資料來源路由類;

動態資料來源能進行自動切換的核心就是spring底層提供了AbstractRoutingDataSource類進行資料來源的路由的,我們主要繼承這個類,實現裡面的方法即可實現我們想要的,這裡主要是實現方法:determineCurrentLookupKey(),而此方法只需要返回一個數據庫的名稱即可,所以我們核心的是有一個類來管理資料來源的執行緒池,這個類才是動態資料來源的核心處理類。還有另外就是我們使用aop技術在執行事務方法前進行資料來源的切換。所以這裡有幾個需要編碼的類,具體如下:

動態資料來源上下文>com.kfit.config.datasource.DynamicDataSourceContextHolder:

package com.kfit.config.datasource.dynamic;

import java.util.ArrayList;

import java.util.List;

/**

 *動態資料來源上下文.

 *

 *@author Angel(QQ:412887952)

 *@version v.0.1

 */

publicclass DynamicDataSourceContextHolder {

       /*

        *當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,

        *所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。

        */

       privatestaticfinalThreadLocal<String>contextHolder =new ThreadLocal<String>();

       /*

        *管理所有的資料來源id;

        *主要是為了判斷資料來源是否存在;

        */

       publicstaticList<String>dataSourceIds =new ArrayList<String>();

       /**

        *使用setDataSourceType設定當前的

        *@param dataSourceType

        */

       publicstaticvoidsetDataSourceType(StringdataSourceType) {

              contextHolder.set(dataSourceType);

       }

       publicstatic StringgetDataSourceType() {

              returncontextHolder.get();

       }

       publicstaticvoidclearDataSourceType() {

              contextHolder.remove();

       }

       /**

    *判斷指定DataSrouce當前是否存在

    *

    *@param dataSourceId

    *@return

    *@author SHANHY

    *@create  2016年1月24日

    */

   publicstaticboolean containsDataSource(StringdataSourceId){

        returndataSourceIds.contains(dataSourceId);

   }

}

資料來源路由類>com.kfit.config.datasource.dynamic.DynamicDataSource:

package com.kfit.config.datasource.dynamic;

importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**

 *動態資料來源.

 *@author Angel(QQ:412887952)

 *@version v.0.1

 */

publicclass DynamicDataSourceextendsAbstractRoutingDataSource {

       /*

        *程式碼中的determineCurrentLookupKey方法取得一個字串,

        *該字串將與配置檔案中的相應字串進行匹配以定位資料來源,配置檔案,即applicationContext.xml檔案中需要要如下程式碼:(non-Javadoc)

        * @seeorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()

        */

       @Override

       protected ObjectdetermineCurrentLookupKey() {

              /*

               * DynamicDataSourceContextHolder程式碼中使用setDataSourceType

               *設定當前的資料來源,在路由類中使用getDataSourceType進行獲取,

               * 交給AbstractRoutingDataSource進行注入使用。

               */

              returnDynamicDataSourceContextHolder.getDataSourceType();

       }

}

指定資料來源註解類>com.kfit.config.datasource.dynamic.TargetDataSource:

package com.kfit.config.datasource.dynamic;

importjava.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

importjava.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

 *在方法上使用,用於指定使用哪個資料來源

 *@author Angel(QQ:412887952)

 *@version v.0.1

 */

@Target({ ElementType.METHOD, ElementType.TYPE })

@Retention(RetentionPolicy.RUNTIME)

@Documented

public@interfaceTargetDataSource {

       Stringvalue();

}

切換資料來源Advice>com.kfit.config.datasource.dynamic.DynamicDataSourceAspect:

package com.kfit.config.datasource.dynamic;

import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.annotation.After;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Before;

importorg.springframework.core.annotation.Order;

importorg.springframework.stereotype.Component;

/**

 *切換資料來源Advice

 *@author Angel(QQ:412887952)

 *@version v.0.1

 */

@Aspect

@Order(-10)//保證該AOP在@Transactional之前執行

@Component

publicclass DynamicDataSourceAspect {

       /*

        * @Before("@annotation(ds)")

        *的意思是:

        *

        * @Before:在方法執行之前進行執行:

        * @annotation(targetDataSource)

        *會攔截註解targetDataSource的方法,否則不攔截;

        */

       @Before("@annotation(targetDataSource)")

   publicvoid changeDataSource(JoinPointpoint, TargetDataSourcetargetDataSource) throws Throwable {

              //獲取當前的指定的資料來源;

        String dsId = targetDataSource.value();

        //如果不在我們注入的所有的資料來源範圍之內,那麼輸出警告資訊,系統自動使用預設的資料來源。

        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {

            System.err.println("資料來源[{}]不存在,使用預設資料來源 > {}"+targetDataSource.value()+point.getSignature());

        } else {

               System.out.println("UseDataSource : {} > {}"+targetDataSource.value()+point.getSignature());

               //找到的話,那麼設定到動態資料來源上下文中。

            DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());

        }

   }

       @After("@annotation(targetDataSource)")

   publicvoid restoreDataSource(JoinPointpoint, TargetDataSourcetargetDataSource) {

              System.out.println("RevertDataSource : {} > {}"+targetDataSource.value()+point.getSignature());

              //方法執行完畢之後,銷燬當前資料來源資訊,進行垃圾回收。

        DynamicDataSourceContextHolder.clearDataSourceType();

   }

}

(6)註冊多資料來源;

以上都是動態資料來源在注入的時候使用的程式碼,其實很重要的一部分程式碼就是註冊我們在application.properties配置的多資料來源,這才是重點,這裡我們使用

ImportBeanDefinitionRegistrar進行註冊,具體的程式碼如下:

package com.kfit.config.datasource.dynamic;

import java.util.HashMap;

import java.util.Map;

import javax.sql.DataSource;

importorg.springframework.beans.MutablePropertyValues;

import org.springframework.beans.PropertyValues;

importorg.springframework.beans.factory.support.BeanDefinitionRegistry;

importorg.springframework.beans.factory.support.GenericBeanDefinition;

importorg.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;

importorg.springframework.boot.bind.RelaxedDataBinder;

importorg.springframework.boot.bind.RelaxedPropertyResolver;

importorg.springframework.context.EnvironmentAware;

importorg.springframework.context.annotation.ImportBeanDefinitionRegistrar;

import org.springframework.core.convert.ConversionService;

importorg.springframework.core.convert.support.DefaultConversionService;

importorg.springframework.core.env.Environment;

importorg.springframework.core.type.AnnotationMetadata;

/**

 *動態資料來源註冊;

 *@author Angel(QQ:412887952)

 *@version v.0.1

 */

publicclass DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

       //如配置檔案中未指定資料來源型別,使用該預設值

   privatestaticfinal ObjectDATASOURCE_TYPE_DEFAULT ="org.apache.tomcat.jdbc.pool.DataSource";

   private ConversionServiceconversionService =newDefaultConversionService();

   private PropertyValuesdataSourcePropertyValues;

       // 預設資料來源

   private DataSourcedefaultDataSource;

   private Map<String, DataSource>customDataSources =newHashMap<String, DataSource>();

       /**

        *載入多資料來源配置

        */

       @Override

       publicvoidsetEnvironment(Environmentenvironment) {

              System.out.println("DynamicDataSourceRegister.setEnvironment()");

              initDefaultDataSource(environment);

        initCustomDataSources(environment);

       }

       /**

        *載入主資料來源配置.

        *@param