業(yè)務需求
- 提供所有微服務數據源的圖形化維護功能
- 代碼生成可以根據選擇的數據源加載表等源信息
- 數據源管理要支持動態(tài)配置,實時生效
附錄效果圖
實現思路
本文提供方法僅供類似簡單業(yè)務場景,在生產環(huán)境和復雜的業(yè)務場景 請使用分庫分表的中間件(例如mycat)或者框架 sharding-sphere (一直在用)等
先來看spring 默認的數據源注入策略,如下代碼默認的事務管理器在初始化時回去加載數據源實現。這里就是我們動態(tài)數據源的入口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 默認的事務管理器 ppublic class datasourcetransactionmanager extends abstractplatformtransactionmanager implements resourcetransactionmanager, initializingbean { // 啟動時候注入一個數據源 public void setdatasource( @nullable datasource datasource) { if (datasource instanceof transactionawaredatasourceproxy) { this .datasource = ((transactionawaredatasourceproxy) datasource).gettargetdatasource(); } else { this .datasource = datasource; } } 」 |
通過注入一個新的datasourcetransactionmanager 實現,并且給它設置多個 datasource 來實現多數據源實現
看下spring 默認提供的路由數據源字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public abstract class abstractroutingdatasource extends abstractdatasource implements initializingbean { // 用戶設置的全部的數據源配置 @nullable private map<object, object> targetdatasources; // 為空默認的數據源配置 @nullable private object defaulttargetdatasource; // 路由鍵查找實現 private datasourcelookup datasourcelookup = new jndidatasourcelookup(); // 最終有效的數據源配置(一般清空對應上邊用戶的設置) @nullable private map<object, datasource> resolveddatasources; } |
開始動手
實現abstractroutingdatasource,定一個動態(tài)數據源實現,只需要實現他的路由key 查找方法即可。
這里的路由key 對應其實是resolveddatasources map 的key喲
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@slf4j public class dynamicdatasource extends abstractroutingdatasource { /** * 指定路由key,這里很簡單 獲取 threadlocal 中目標key 即可 * * @return */ @override protected object determinecurrentlookupkey() { return dynamicdatasourcecontextholder.getdatasourcetype(); } } |
把我們動態(tài)數據源實現注入到spring 的事務管理器,去數據庫查詢出來全部的數據源信息,定義一個個具體的數據源實現 我這里使用的hikaridatasource 給他賦值等等
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
|
@slf4j @configuration @allargsconstructor public class dynamicdatasourceconfig implements transactionmanagementconfigurer { private final map<object, object> datasourcemap = new hashmap<>( 8 ); private final datasourceproperties datasourceproperties; @bean ( "dynamicdatasource" ) public dynamicdatasource datasource() { jdbctemplate(dds).queryforlist(datasourceconstant.query_ds_sql); log.info( "開始 -> 初始化動態(tài)數據源" ); optional.of(dblist).ifpresent(list -> list.foreach(db -> { log.info( "數據源:{}" , db.get(datasourceconstant.ds_name)); hikaridatasource ds = new hikaridatasource(); datasourcemap.put(db.get(datasourceconstant.ds_route_key), ds); })); dynamicdatasource ds = new dynamicdatasource(); ds.settargetdatasources(datasourcemap); return ds; } @bean public platformtransactionmanager txmanager() { return new datasourcetransactionmanager(datasource()); } @override public platformtransactionmanager annotationdriventransactionmanager() { return txmanager(); } } |
怎么使用
只需要根據用戶前臺選擇的數據源key ,在業(yè)務類保存到ttl 即可,會自動根據選擇路由數據源
1
|
dynamicdatasourcecontextholder.setdatasourcetype(key) |
這里當然也可以根據aop 自定義注解等實現。
如何動態(tài)數據源動態(tài)配置
上邊其實已經完成了 我們想要的需求功能,但是有什么問題呢?
我們在數據源管理面維護了數據源,動態(tài)去修改這個 datasourcemap 其實是無效的,不能做到實時刷新
我們來看下 abstractroutingdatasource 的加載map 數據源的源碼,只有在初始化的時候調用 afterpropertiesset 去初始數據源map.
那我們只要獲取當前的dynamicdatasource bean 手動調用afterpropertiesset 即可。
整個代碼如下
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
|
public class dynamicdatasourceconfig implements transactionmanagementconfigurer { private final map<object, object> datasourcemap = new hashmap<>( 8 ); private final datasourceproperties datasourceproperties; private final stringencryptor stringencryptor; @bean ( "dynamicdatasource" ) public dynamicdatasource datasource() { dynamicdatasource ds = new dynamicdatasource(); hikaridatasource cads = new hikaridatasource(); cads.setjdbcurl(datasourceproperties.geturl()); cads.setdriverclassname(datasourceproperties.getdriverclassname()); cads.setusername(datasourceproperties.getusername()); cads.setpassword(datasourceproperties.getpassword()); ds.setdefaulttargetdatasource(cads); datasourcemap.put( 0 , cads); ds.settargetdatasources(datasourcemap); return ds; } /** * 組裝默認配置的數據源,查詢數據庫配置 */ @postconstruct public void init() { drivermanagerdatasource dds = new drivermanagerdatasource(); dds.seturl(datasourceproperties.geturl()); dds.setdriverclassname(datasourceproperties.getdriverclassname()); dds.setusername(datasourceproperties.getusername()); dds.setpassword(datasourceproperties.getpassword()); list<map<string, object>> dblist = new jdbctemplate(dds).queryforlist(datasourceconstant.query_ds_sql); log.info( "開始 -> 初始化動態(tài)數據源" ); optional.of(dblist).ifpresent(list -> list.foreach(db -> { log.info( "數據源:{}" , db.get(datasourceconstant.ds_name)); hikaridatasource ds = new hikaridatasource(); ds.setjdbcurl(string.valueof(db.get(datasourceconstant.ds_jdbc_url))); ds.setdriverclassname(driver. class .getname()); ds.setusername((string) db.get(datasourceconstant.ds_user_name)); string decpwd = stringencryptor.decrypt((string) db.get(datasourceconstant.ds_user_pwd)); ds.setpassword(decpwd); datasourcemap.put(db.get(datasourceconstant.ds_route_key), ds); })); log.info( "完畢 -> 初始化動態(tài)數據源,共計 {} 條" , datasourcemap.size()); } /** * 重新加載數據源配置 */ public boolean reload() { init(); dynamicdatasource datasource = datasource(); datasource.settargetdatasources(datasourcemap); datasource.afterpropertiesset(); return boolean . false ; } @bean public platformtransactionmanager txmanager() { return new datasourcetransactionmanager(datasource()); } @override public platformtransactionmanager annotationdriventransactionmanager() { return txmanager(); } |
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://segmentfault.com/a/1190000018844625