動態(tài)數(shù)據(jù)源
在很多具體應(yīng)用場景的時候,我們需要用到動態(tài)數(shù)據(jù)源的情況,比如多租戶的場景,系統(tǒng)登錄時需要根據(jù)用戶信息切換到用戶對應(yīng)的數(shù)據(jù)庫。又比如業(yè)務(wù)a要訪問a數(shù)據(jù)庫,業(yè)務(wù)b要訪問b數(shù)據(jù)庫等,都可以使用動態(tài)數(shù)據(jù)源方案進(jìn)行解決。接下來,我們就來講解如何實現(xiàn)動態(tài)數(shù)據(jù)源,以及在過程中剖析動態(tài)數(shù)據(jù)源背后的實現(xiàn)原理。
實現(xiàn)案例
本教程案例基于 spring boot + mybatis + mysql 實現(xiàn)。
數(shù)據(jù)庫設(shè)計
首先需要安裝好mysql數(shù)據(jù)庫,新建數(shù)據(jù)庫 example,創(chuàng)建example表,用來測試數(shù)據(jù)源,sql腳本如下:
1
2
3
4
5
6
7
|
create table `example` ( `pk` bigint( 20 ) unsigned not null auto_increment comment '主鍵' , `message` varchar( 100 ) not null , `create_time` datetime not null comment '創(chuàng)建時間' , `modify_time` datetime default null comment '生效時間' , primary key (`pk`) ) engine=innodb auto_increment= 2 default charset=utf8 row_format=compact comment= '測試用例表' |
添加依賴
添加spring boot,spring aop,mybatis,mysql相關(guān)依賴。
pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <dependency> <groupid>org.mybatis.spring.boot</groupid> <artifactid>mybatis-spring-boot-starter</artifactid> <version> 1.3 . 1 </version> </dependency> <!-- spring aop --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <version> 5.1 . 8 </version> </dependency> |
自定義配置文件
新建自定義配置文件resource/config/mysql/db.properties,添加數(shù)據(jù)源:
1
2
3
4
5
|
#數(shù)據(jù)庫設(shè)置 spring.datasource.example.jdbc-url=jdbc:mysql: //localhost:3306/example?characterencoding=utf-8 spring.datasource.example.username=root spring.datasource.example.password= 123456 spring.datasource.example.driver- class -name=com.mysql.jdbc.driver |
啟動類
啟動類添加 exclude = {datasourceautoconfiguration.class}, 以禁用數(shù)據(jù)源默認(rèn)自動配置。
數(shù)據(jù)源默認(rèn)自動配置會讀取 spring.datasource.* 的屬性創(chuàng)建數(shù)據(jù)源,所以要禁用以進(jìn)行定制。
dynamicdatasourceapplication.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.main.example.dynamic.datasource; import org.springframework.boot.springapplication; import org.springframework.boot.autoconfigure.springbootapplication; import org.springframework.boot.autoconfigure.jdbc.datasourceautoconfiguration; @springbootapplication (exclude = { datasourceautoconfiguration. class }) public class dynamicdatasourceapplication { public static void main(string[] args) { springapplication.run(dynamicdatasourceapplication. class , args); } } |
數(shù)據(jù)源配置類
創(chuàng)建一個數(shù)據(jù)源配置類,主要做以下幾件事情:
1. 配置 dao,model(bean),xml mapper文件的掃描路徑。
2. 注入數(shù)據(jù)源配置屬性,創(chuàng)建數(shù)據(jù)源。
3. 創(chuàng)建一個動態(tài)數(shù)據(jù)源,裝入數(shù)據(jù)源。
4. 將動態(tài)數(shù)據(jù)源設(shè)置到sql會話工廠和事務(wù)管理器。
如此,當(dāng)進(jìn)行數(shù)據(jù)庫操作時,就會通過我們創(chuàng)建的動態(tài)數(shù)據(jù)源去獲取要操作的數(shù)據(jù)源了。
dbsourceconfig.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
package com.main.example.config.dao; import com.main.example.common.dataenum; import com.main.example.common.dynamicdatasource; import org.mybatis.spring.sqlsessionfactorybean; import org.springframework.boot.context.properties.configurationproperties; import org.springframework.boot.jdbc.datasourcebuilder; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.context.annotation.propertysource; import org.springframework.core.io.support.pathmatchingresourcepatternresolver; import org.springframework.jdbc.datasource.datasourcetransactionmanager; import org.springframework.transaction.platformtransactionmanager; import javax.sql.datasource; import java.util.hashmap; import java.util.map; //數(shù)據(jù)庫配置統(tǒng)一在config/mysql/db.properties中 @configuration @propertysource (value = "classpath:config/mysql/db.properties" ) public class dbsourceconfig { private string typealiasespackage = "com.main.example.bean.**.*" ; @bean (name = "exampledatasource" ) @configurationproperties (prefix = "spring.datasource.example" ) public datasource exampledatasource() { return datasourcebuilder.create().build(); } /* * 動態(tài)數(shù)據(jù)源 * dbmap中存放數(shù)據(jù)源名稱與數(shù)據(jù)源實例,數(shù)據(jù)源名稱存于dataenum.dbsource中 * setdefaulttargetdatasource方法設(shè)置默認(rèn)數(shù)據(jù)源 */ @bean(name = "dynamicdatasource") public datasource dynamicdatasource() { dynamicdatasource dynamicdatasource = new dynamicdatasource(); //配置多數(shù)據(jù)源 map<object, object> dbmap = new hashmap(); dbmap.put(dataenum.dbsource.example.getname(), exampledatasource()); dynamicdatasource.settargetdatasources(dbmap); // 設(shè)置默認(rèn)數(shù)據(jù)源 dynamicdatasource.setdefaulttargetdatasource(exampledatasource()); return dynamicdatasource; } /* * 數(shù)據(jù)庫連接會話工廠 * 將動態(tài)數(shù)據(jù)源賦給工廠 * mapper存于resources/mapper目錄下 * 默認(rèn)bean存于com.main.example.bean包或子包下,也可直接在mapper中指定 */ @bean (name = "sqlsessionfactory" ) public sqlsessionfactorybean sqlsessionfactory() throws exception { sqlsessionfactorybean sqlsessionfactory = new sqlsessionfactorybean(); sqlsessionfactory.setdatasource(dynamicdatasource()); sqlsessionfactory.settypealiasespackage(typealiasespackage); //掃描bean pathmatchingresourcepatternresolver resolver = new pathmatchingresourcepatternresolver(); sqlsessionfactory.setmapperlocations(resolver.getresources( "classpath*:mapper/*.xml" )); // 掃描映射文件 return sqlsessionfactory; } @bean public platformtransactionmanager transactionmanager() { // 配置事務(wù)管理, 使用事務(wù)時在方法頭部添加@transactional注解即可 return new datasourcetransactionmanager(dynamicdatasource()); } } |
動態(tài)數(shù)據(jù)源類
我們上一步把這個動態(tài)數(shù)據(jù)源設(shè)置到了sql會話工廠和事務(wù)管理器,這樣在操作數(shù)據(jù)庫時就會通過動態(tài)數(shù)據(jù)源類來獲取要操作的數(shù)據(jù)源了。
動態(tài)數(shù)據(jù)源類集成了spring提供的abstractroutingdatasource類,abstractroutingdatasource 中獲取數(shù)據(jù)源的方法就是 determinetargetdatasource,而此方法又通過 determinecurrentlookupkey 方法獲取查詢數(shù)據(jù)源的key。
所以如果我們需要動態(tài)切換數(shù)據(jù)源,就可以通過以下兩種方式定制:
1. 覆寫 determinecurrentlookupkey 方法
通過覆寫 determinecurrentlookupkey 方法,從一個自定義的 dbsourcecontext.getdbsource() 獲取數(shù)據(jù)源key值,這樣在我們想動態(tài)切換數(shù)據(jù)源的時候,只要通過 dbsourcecontext.setdbsource(key) 的方式就可以動態(tài)改變數(shù)據(jù)源了。這種方式要求在獲取數(shù)據(jù)源之前,要先初始化各個數(shù)據(jù)源到 dbsourcecontext 中,我們案例就是采用這種方式實現(xiàn)的,所以要將數(shù)據(jù)源都事先初始化到dynamicdatasource 中。
2. 可以通過覆寫 determinetargetdatasource,因為數(shù)據(jù)源就是在這個方法創(chuàng)建并返回的,所以這種方式就比較自由了,支持到任何你希望的地方讀取數(shù)據(jù)源信息,只要最終返回一個 datasource 的實現(xiàn)類即可。比如你可以到數(shù)據(jù)庫、本地文件、網(wǎng)絡(luò)接口等方式讀取到數(shù)據(jù)源信息然后返回相應(yīng)的數(shù)據(jù)源對象就可以了。
dynamicdatasource.java:
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.main.example.common; import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource; public class dynamicdatasource extends abstractroutingdatasource { @override protected object determinecurrentlookupkey() { return dbsourcecontext.getdbsource(); } } |
數(shù)據(jù)源上下文
動態(tài)數(shù)據(jù)源的切換主要是通過調(diào)用這個類的方法來完成的。在任何想要進(jìn)行切換數(shù)據(jù)源的時候都可以通過調(diào)用這個類的方法實現(xiàn)切換。比如系統(tǒng)登錄時,根據(jù)用戶信息調(diào)用這個類的數(shù)據(jù)源切換方法切換到用戶對應(yīng)的數(shù)據(jù)庫。完整代碼如下:
dbsourcecontext.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package com.main.example.common; import org.apache.log4j.logger; public class dbsourcecontext { private static logger logger = logger.getlogger(dbsourcecontext. class ); private static final threadlocal<string> dbcontext = new threadlocal<string>(); public static void setdbsource(string source) { logger.debug( "set source ====>" + source); dbcontext.set(source); } public static string getdbsource() { logger.debug( "get source ====>" + dbcontext.get()); return dbcontext.get(); } public static void cleardbsource() { dbcontext.remove(); } } |
注解式數(shù)據(jù)源
到這里,在任何想要動態(tài)切換數(shù)據(jù)源的時候,只要調(diào)用dbsourcecontext.setdbsource(key) 就可以完成了。
接下來我們實現(xiàn)通過注解的方式來進(jìn)行數(shù)據(jù)源的切換,原理就是添加注解(如@dbsource(value="example")),然后實現(xiàn)注解切面進(jìn)行數(shù)據(jù)源切換。
創(chuàng)建一個動態(tài)數(shù)據(jù)源注解,擁有一個value值,用于標(biāo)識要切換的數(shù)據(jù)源的key。
dbsource.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package com.main.example.config.dao; import java.lang.annotation.*; /** * 動態(tài)數(shù)據(jù)源注解 * @author * @date april 12, 2019 */ @target ({elementtype.method, elementtype.type}) @retention (retentionpolicy.runtime) @documented public @interface dbsource { /** * 數(shù)據(jù)源key值 * @return */ string value(); } |
創(chuàng)建一個aop切面,攔截帶 @datasource 注解的方法,在方法執(zhí)行前切換至目標(biāo)數(shù)據(jù)源,執(zhí)行完成后恢復(fù)到默認(rèn)數(shù)據(jù)源。
dynamicdatasourceaspect.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
package com.main.example.config.dao; import com.main.example.common.dbsourcecontext; import org.apache.log4j.logger; import org.aspectj.lang.joinpoint; import org.aspectj.lang.annotation.after; import org.aspectj.lang.annotation.aspect; import org.aspectj.lang.annotation.before; import org.springframework.core.annotation.order; import org.springframework.stereotype.component; /** * 動態(tài)數(shù)據(jù)源切換處理器 * @author linzhibao * @date april 12, 2019 */ @aspect @order (- 1 ) // 該切面應(yīng)當(dāng)先于 @transactional 執(zhí)行 @component public class dynamicdatasourceaspect { private static logger logger = logger.getlogger(dynamicdatasourceaspect. class ); /** * 切換數(shù)據(jù)源 * @param point * @param dbsource */ //@before("@annotation(dbsource)") 注解在對應(yīng)方法,攔截有@dbsource的方法 //注解在類對象,攔截有@dbsource類下所有的方法 @before ( "@within(dbsource)" ) public void switchdatasource(joinpoint point, dbsource dbsource) { // 切換數(shù)據(jù)源 dbsourcecontext.setdbsource(dbsource.value()); } /** * 重置數(shù)據(jù)源 * @param point * @param dbsource */ //注解在類對象,攔截有@dbsource類下所有的方法 @after ( "@within(dbsource)" ) public void restoredatasource(joinpoint point, dbsource dbsource) { // 將數(shù)據(jù)源置為默認(rèn)數(shù)據(jù)源 dbsourcecontext.cleardbsource(); } } |
到這里,動態(tài)數(shù)據(jù)源相關(guān)的處理代碼就完成了。
編寫用戶業(yè)務(wù)代碼
接下來編寫用戶查詢業(yè)務(wù)代碼,用來進(jìn)行測試,dao層只需添加一個查詢接口即可。
exampledao.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package com.main.example.dao; import com.main.example.common.dataenum; import com.main.example.config.dao.dbsource; import org.springframework.context.annotation.bean; import org.springframework.stereotype.component; import javax.annotation.resource; import java.util.list; @component ( "exampledao" ) //切換數(shù)據(jù)源注解,以dataenum.dbsource中的值為準(zhǔn) @dbsource ( "example" ) public class exampledao extends daobase { private static final string mapper_name_space = "com.main.example.dao.examplemapper" ; public list<string> selectallmessages() { return selectlist(mapper_name_space, "selectallmessages" ); } } |
controler代碼:
testexampledao.java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.main.example.dao; import org.springframework.beans.factory.annotation.autowired; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.restcontroller; import java.util.arraylist; import java.util.list; @restcontroller public class testexampledao { @autowired exampledao exampledao; @requestmapping (value = "/test/example" ) public list<string> selectallmessages() { try { list<string> ldata = exampledao.selectallmessages(); if (ldata == null ){system.out.println( "*********it is null.***********" ); return null ;} for (string d : ldata) { system.out.println(d); } return ldata; } catch (exception e) { e.printstacktrace(); } return new arraylist<>(); } } |
examplemapper.xml代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
<?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.main.example.dao.examplemapper" > <select id= "selectallmessages" resulttype= "java.lang.string" > select message from example </select> </mapper> |
測試效果
啟動系統(tǒng),訪問 http://localhost:80/test/example">http://localhost:80/test/example,分別測試兩個接口,成功返回數(shù)據(jù)。
可能遇到的問題
1.報錯:java.lang.illegalargumentexception: jdbcurl is required with driverclassname
原因:
spring boot從1.x升級到2.x版本之后,一些配置及用法有了變化,如果不小心就會碰到“jdbcurl is required with driverclassname.”的錯誤
解決方法:
在1.0 配置數(shù)據(jù)源的過程中主要是寫成:spring.datasource.url 和spring.datasource.driverclassname。
而在2.0升級之后需要變更成:spring.datasource.jdbc-url和spring.datasource.driver-class-name即可解決!
2.自定義配置文件
自定義配置文件需要在指定配置類上加上@propertysource標(biāo)簽,例如:
1
|
@propertysource (value = "classpath:config/mysql/db.properties" ) |
若是作用于配置類中的方法,則在方法上加上@configurationproperties,例如:
1
|
@configurationproperties (prefix = "spring.datasource.example" ) |
配置項前綴為spring.datasource.example
若是作用于配置類上,則在類上加上@configurationproperties(同上),并且在啟動類上加上@enableconfigurationproperties(xxx.class)
3.多數(shù)據(jù)源
需要在啟動類上取消自動裝載數(shù)據(jù)源,如:
1
2
3
|
@springbootapplication (exclude = { datasourceautoconfiguration. class }) |
以上所述是小編給大家介紹的spring boot + mybatis 實現(xiàn)動態(tài)數(shù)據(jù)源詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對服務(wù)器之家網(wǎng)站的支持!
原文鏈接:https://www.cnblogs.com/fnlingnzb-learner/p/10710145.html#top