前言
對(duì)于數(shù)據(jù)量在1千萬(wàn),單個(gè)mysql數(shù)據(jù)庫(kù)就可以支持,但是如果數(shù)據(jù)量大于這個(gè)數(shù)的時(shí)候,例如1億,那么查詢的性能就會(huì)很低。此時(shí)需要對(duì)數(shù)據(jù)庫(kù)做水平切分,常見(jiàn)的做法是按照用戶的賬號(hào)進(jìn)行hash,然后選擇對(duì)應(yīng)的數(shù)據(jù)庫(kù)。
最近公司項(xiàng)目需求,由于要兼容老系統(tǒng)的數(shù)據(jù)庫(kù)結(jié)構(gòu),需要搭建一個(gè) 可以動(dòng)態(tài)切換、添加數(shù)據(jù)源的后端服務(wù)。
參考了過(guò)去的項(xiàng)目,通過(guò)配置多個(gè)sqlsessionfactory 來(lái)實(shí)現(xiàn)多數(shù)據(jù)源,這么做的話,未免過(guò)于笨重,而且無(wú)法實(shí)現(xiàn)動(dòng)態(tài)添加數(shù)據(jù)源這個(gè)需求
通過(guò) spring abstractroutingdatasource 為我們抽象了一個(gè) dynamicdatasource 解決這一問(wèn)題
簡(jiǎn)單分析下 abstractroutingdatasource 的源碼
targetdatasources 就是我們的多個(gè)數(shù)據(jù)源,在初始化的時(shí)候會(huì)調(diào)用afterpropertiesset(),去解析我們的數(shù)據(jù)源 然后 put 到 resolveddatasources
實(shí)現(xiàn)了 datasource 的 getconnection(); 我們看看 determinetargetdatasource(); 做了什么
通過(guò)下面的 determinecurrentlookupkey();(這個(gè)方法需要我們實(shí)現(xiàn)) 返回一個(gè)key,然后從 resolveddatasources (其實(shí)也就是 targetdatasources) 中 get 一個(gè)數(shù)據(jù)源,實(shí)現(xiàn)了每次調(diào)用 getconnection(); 打開(kāi)連接 切換數(shù)據(jù)源,如果想動(dòng)態(tài)添加的話 只需要重新 set targetdatasources 再調(diào)用 afterpropertiesset() 即可
talk is cheap. show me the code
我使用的springboot版本為 1.5.x,下面是核心代碼
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
73
74
75
76
77
78
|
/** * 多數(shù)據(jù)源配置 * * @author taven * */ @configuration @mapperscan ( "com.gitee.taven.mapper" ) public class datasourceconfigurer { /** * datasource 自動(dòng)配置并注冊(cè) * * @return data source */ @bean ( "db0" ) @primary @configurationproperties (prefix = "datasource.db0" ) public datasource datasource0() { return druiddatasourcebuilder.create().build(); } /** * datasource 自動(dòng)配置并注冊(cè) * * @return data source */ @bean ( "db1" ) @configurationproperties (prefix = "datasource.db1" ) public datasource datasource1() { return druiddatasourcebuilder.create().build(); } /** * 注冊(cè)動(dòng)態(tài)數(shù)據(jù)源 * * @return */ @bean ( "dynamicdatasource" ) public datasource dynamicdatasource() { dynamicroutingdatasource dynamicroutingdatasource = new dynamicroutingdatasource(); map<object, object> datasourcemap = new hashmap<>(); datasourcemap.put( "dynamic_db0" , datasource0()); datasourcemap.put( "dynamic_db1" , datasource1()); dynamicroutingdatasource.setdefaulttargetdatasource(datasource0()); // 設(shè)置默認(rèn)數(shù)據(jù)源 dynamicroutingdatasource.settargetdatasources(datasourcemap); return dynamicroutingdatasource; } /** * sql session factory bean. * here to config datasource for sqlsessionfactory * <p> * you need to add @{@code @configurationproperties(prefix = "mybatis")}, if you are using *.xml file, * the {@code 'mybatis.type-aliases-package'} and {@code 'mybatis.mapper-locations'} should be set in * {@code 'application.properties'} file, or there will appear invalid bond statement exception * * @return the sql session factory bean */ @bean @configurationproperties (prefix = "mybatis" ) public sqlsessionfactorybean sqlsessionfactorybean() { sqlsessionfactorybean sqlsessionfactorybean = new sqlsessionfactorybean(); // 必須將動(dòng)態(tài)數(shù)據(jù)源添加到 sqlsessionfactorybean sqlsessionfactorybean.setdatasource(dynamicdatasource()); return sqlsessionfactorybean; } /** * 事務(wù)管理器 * * @return the platform transaction manager */ @bean public platformtransactionmanager transactionmanager() { return new datasourcetransactionmanager(dynamicdatasource()); } } |
通過(guò) threadlocal 獲取線程安全的數(shù)據(jù)源 key
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
|
package com.gitee.taven.config; public class dynamicdatasourcecontextholder { private static final threadlocal<string> contextholder = new threadlocal<string>() { @override protected string initialvalue() { return "dynamic_db0" ; } }; /** * to switch datasource * * @param key the key */ public static void setdatasourcekey(string key) { contextholder.set(key); } /** * get current datasource * * @return data source key */ public static string getdatasourcekey() { return contextholder.get(); } /** * to set datasource as default */ public static void cleardatasourcekey() { contextholder.remove(); } } |
動(dòng)態(tài) 添加、切換數(shù)據(jù)源
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
73
74
75
76
77
78
79
80
|
/** * 動(dòng)態(tài)數(shù)據(jù)源 * * @author taven * */ public class dynamicroutingdatasource extends abstractroutingdatasource { private final logger logger = loggerfactory.getlogger(getclass()); private static map<object, object> targetdatasources = new hashmap<>(); /** * 設(shè)置當(dāng)前數(shù)據(jù)源 * * @return */ @override protected object determinecurrentlookupkey() { logger.info( "current datasource is [{}]" , dynamicdatasourcecontextholder.getdatasourcekey()); return dynamicdatasourcecontextholder.getdatasourcekey(); } @override public void settargetdatasources(map<object, object> targetdatasources) { super .settargetdatasources(targetdatasources); dynamicroutingdatasource.targetdatasources = targetdatasources; } /** * 是否存在當(dāng)前key的 datasource * * @param key * @return 存在返回 true, 不存在返回 false */ public static boolean isexistdatasource(string key) { return targetdatasources.containskey(key); } /** * 動(dòng)態(tài)增加數(shù)據(jù)源 * * @param map 數(shù)據(jù)源屬性 * @return */ public synchronized boolean adddatasource(map<string, string> map) { try { connection connection = null ; // 排除連接不上的錯(cuò)誤 try { class .forname(map.get(druiddatasourcefactory.prop_driverclassname)); connection = drivermanager.getconnection( map.get(druiddatasourcefactory.prop_url), map.get(druiddatasourcefactory.prop_username), map.get(druiddatasourcefactory.prop_password)); system.out.println(connection.isclosed()); } catch (exception e) { return false ; } finally { if (connection != null && !connection.isclosed()) connection.close(); } string database = map.get( "database" ); //獲取要添加的數(shù)據(jù)庫(kù)名 if (stringutils.isblank(database)) return false ; if (dynamicroutingdatasource.isexistdatasource(database)) return true ; druiddatasource druiddatasource = (druiddatasource) druiddatasourcefactory.createdatasource(map); druiddatasource.init(); map<object, object> targetmap = dynamicroutingdatasource.targetdatasources; targetmap.put(database, druiddatasource); // 當(dāng)前 targetdatasources 與 父類 targetdatasources 為同一對(duì)象 所以不需要set // this.settargetdatasources(targetmap); this .afterpropertiesset(); logger.info( "datasource {} has been added" , database); } catch (exception e) { logger.error(e.getmessage()); return false ; } return true ; } } |
可以通過(guò) aop 或者 手動(dòng) dynamicdatasourcecontextholder.setdatasourcekey(string key) 切換數(shù)據(jù)源
需要注意的:當(dāng)我們開(kāi)啟了事務(wù)之后,是無(wú)法在去切換數(shù)據(jù)源的
本文項(xiàng)目源碼:https://gitee.com/yintianwen7/spring-dynamic-datasource
參考文獻(xiàn):https://github.com/helloworlde/springboot-dynamicdatasource
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)服務(wù)器之家的支持。
原文鏈接:https://www.jianshu.com/p/0a485c965b8b