spring多數據源實現的方式大概有2中,一種是新建多個mapperscan掃描不同包,另外一種則是通過繼承abstractroutingdatasource實現動態路由。今天作者主要基于后者做的實現,且方式1的實現比較簡單這里不做過多探討。
實現方式
方式1的實現(核心代碼):
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
|
@configuration @mapperscan (basepackages = "com.goofly.test1" , sqlsessiontemplateref = "test1sqlsessiontemplate" ) public class datasource1config1 { @bean (name = "datasource1" ) @configurationproperties (prefix = "spring.datasource.test1" ) @primary public datasource testdatasource() { return datasourcebuilder.create().build(); } // .....略 } @configuration @mapperscan (basepackages = "com.goofly.test2" , sqlsessiontemplateref = "test1sqlsessiontemplate" ) public class datasourceconfig2 { @bean (name = "datasource2" ) @configurationproperties (prefix = "spring.datasource.test2" ) @primary public datasource testdatasource() { return datasourcebuilder.create().build(); } // .....略 } |
方式2的實現(核心代碼):
1
2
3
4
5
6
7
8
9
|
public class dynamicroutingdatasource extends abstractroutingdatasource { private static final logger log = logger.getlogger(dynamicroutingdatasource. class ); @override protected object determinecurrentlookupkey() { //從threadlocal中取值 return dynamicdatasourcecontextholder.get(); } } |
第1種方式雖然實現比較加單,劣勢就是不同數據源的mapper文件不能在同一包名,就顯得不太靈活了。所以為了更加靈活的作為一個組件的存在,作者采用的第二種方式實現。
設計思路
- 當請求經過被注解修飾的類后,此時會進入到切面邏輯中。
- 切面邏輯會獲取注解中設置的key值,然后將該值存入到threadlocal中
- 執行完切面邏輯后,會執行abstractroutingdatasource.determinecurrentlookupkey()方法,然后從threadlocal中獲取之前設置的key值,然后將該值返回。
- 由于abstractroutingdatasource的targetdatasources是一個map,保存了數據源key和數據源的對應關系,所以能夠順利的找到該對應的數據源。
源碼解讀
org.springframework.jdbc.datasource.lookup.abstractroutingdatasource,如下:
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
|
public abstract class abstractroutingdatasource extends abstractdatasource implements initializingbean { private map<object, object> targetdatasources; private object defaulttargetdatasource; private boolean lenientfallback = true ; private datasourcelookup datasourcelookup = new jndidatasourcelookup(); private map<object, datasource> resolveddatasources; private datasource resolveddefaultdatasource; protected datasource determinetargetdatasource() { assert .notnull( this .resolveddatasources, "datasource router not initialized" ); object lookupkey = determinecurrentlookupkey(); datasource datasource = this .resolveddatasources.get(lookupkey); if (datasource == null && ( this .lenientfallback || lookupkey == null )) { datasource = this .resolveddefaultdatasource; } if (datasource == null ) { throw new illegalstateexception( "cannot determine target datasource for lookup key [" + lookupkey + "]" ); } return datasource; } /** * determine the current lookup key. this will typically be * implemented to check a thread-bound transaction context. * <p>allows for arbitrary keys. the returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolvespecifiedlookupkey} method. */ protected abstract object determinecurrentlookupkey(); //........略 |
targetdatasources是一個map結構,保存了key與數據源的對應關系;
datasourcelookup是一個datasourcelookup類型,默認實現是jndidatasourcelookup。點開該類源碼會發現,它實現了通過key獲取datasource的邏輯。當然,這里可以通過setdatasourcelookup()來改變其屬性,因為關于此處有一個坑,后面會講到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class jndidatasourcelookup extends jndilocatorsupport implements datasourcelookup { public jndidatasourcelookup() { setresourceref( true ); } @override public datasource getdatasource(string datasourcename) throws datasourcelookupfailureexception { try { return lookup(datasourcename, datasource. class ); } catch (namingexception ex) { throw new datasourcelookupfailureexception( "failed to look up jndi datasource with name '" + datasourcename + "'" , ex); } } } |
組件使用
多數據源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# db1 spring.datasource.master.url = jdbc:mysql: //127.0.0.1:3306/test?useunicode=true&characterencoding=utf8&usessl=false spring.datasource.master.username = root spring.datasource.master.password = 123456 spring.datasource.master.driverclassname = com.mysql.jdbc.driver spring.datasource.master.validationquery = true spring.datasource.master.testonborrow = true ## db2 spring.datasource.slave.url = jdbc:mysql: //127.0.0.1:3306/test1?useunicode=true&characterencoding=utf8&usessl=false spring.datasource.slave.username = root spring.datasource.slave.password = 123456 spring.datasource.slave.driverclassname = com.mysql.jdbc.driver spring.datasource.slave.validationquery = true spring.datasource.slave.testonborrow = true #主數據源名稱 spring.maindb=master #mapperper包路徑 mapper.basepackages =com.btps.xli.multidb.demo.mapper |
單數據源
為了讓使用者能夠用最小的改動實現最好的效果,作者對單數據源的多種配置做了兼容。
示例配置1(配置數據源名稱):
1
2
3
4
5
6
7
8
9
10
11
|
spring.datasource.master.url = jdbc:mysql: //127.0.0.1:3306/test?useunicode=true&characterencoding=utf8&usessl=false spring.datasource.master.username = root spring.datasource.master.password = 123456 spring.datasource.master.driverclassname = com.mysql.jdbc.driver spring.datasource.master.validationquery = true spring.datasource.master.testonborrow = true # mapper包路徑 mapper.basepackages = com.goofly.xli.multidb.demo.mapper # 主數據源名稱 spring.maindb=master |
示例配置2(不配置數據源名稱):
1
2
3
4
5
6
7
8
9
|
spring.datasource.url = jdbc:mysql: //127.0.0.1:3306/test?useunicode=true&characterencoding=utf8&usessl=false spring.datasource.username = root spring.datasource.password = 123456 spring.datasource.driverclassname = com.mysql.jdbc.driver spring.datasource.validationquery = true spring.datasource.testonborrow = true # mapper包路徑 mapper.basepackages = com.goofly.xli.multidb.demo.mapper |
踩坑之路
多數據源的循環依賴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
description: the dependencies of some of the beans in the application context form a cycle: happinesscontroller (field private com.db.service.happinessservice com.db.controller.happinesscontroller.happinessservice) ↓ happinessserviceimpl (field private com.db.mapper.masterdao com.db.service.happinessserviceimpl.masterdao) ↓ masterdao defined in file [e:\gitrepository\framework-gray\test-db\target\classes\com\db\mapper\masterdao. class ] ↓ sqlsessionfactory defined in class path resource [com/goofly/xli/datasource/core/dynamicdatasourceconfiguration. class ] ┌─────┐ | dynamicdatasource defined in class path resource [com/goofly/xli/datasource/core/dynamicdatasourceconfiguration. class ] ↑ ↓ | firstdatasource defined in class path resource [com/goofly/xli/datasource/core/dynamicdatasourceconfiguration. class ] ↑ ↓ | datasourceinitializer |
解決方案:
在spring boot啟動的時候排除datasourceautoconfiguration即可。如下:
1
2
3
4
5
6
|
public class dbmain { public static void main(string[] args) { springapplication.run(dbmain. class , args); } } |
但是作者在創建多數據源的時候由于并未創建多個datasource的bean,而是只創建了一個即需要做動態數據源的那個bean。 其他的datasource則直接創建實例然后存放在map里面,然后再設置到dynamicroutingdatasource#settargetdatasources即可。
因此這種方式也不會出現循環依賴的問題!
動態刷新數據源
筆者在設計之初是想構建一個動態刷新數據源的方案,所以利用了springcloud的@refreshscope去標注數據源,然后利用refreshscope#refresh實現刷新。但是在實驗的時候發現由druid創建的數據源會因此而關閉,由spring的datasourcebuilder創建的數據源則不會發生任何變化。
? 最后關于此也沒能找到解決方案。同時思考,如果只能的可以實現動態刷新的話,那么數據源的原有連接會因為刷新而中斷嗎還是會有其他處理?
多數據源事務
有這么一種特殊情況,一個事務中調用了兩個不同數據源,這個時候動態切換數據源會因此而失效。
翻閱了很多文章,大概找了2中解決方案,一種是atomikos進行事務管理,但是貌似性能并不是很理想。
另外一種則是通過優先級控制,切面的的優先級必須要大于數據源的優先級,用注解@order控制。
此處留一個坑!
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://segmentfault.com/a/1190000017989247