功能需求是公司要做一個大的運營平臺:
1、運營平臺有自身的數(shù)據(jù)庫,維護(hù)用戶、角色、菜單、部分以及權(quán)限等基本功能。
2、運營平臺還需要提供其他不同服務(wù)(服務(wù)a,服務(wù)b)的后臺運營,服務(wù)a、服務(wù)b的數(shù)據(jù)庫是獨立的。
所以,運營平臺至少要連三個庫:運營庫,a庫,b庫,并且希望達(dá)到針對每個功能請求能夠自動切換到對應(yīng)的數(shù)據(jù)源(我最終實現(xiàn)是針對service的方法級別進(jìn)行切換的,也可以實現(xiàn)針對每個dao層的方法進(jìn)行切換。我們系統(tǒng)的功能是相互之間比較獨立的)。
第一步:配置多數(shù)據(jù)源
1、定義數(shù)據(jù)源:
我采用的數(shù)據(jù)源是阿里的druiddatasource(用dbcp也行,這個隨便)。配置如下:
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
|
<!-- op datasource --> <bean id= "opdatasource" class = "com.alibaba.druid.pool.druiddatasource" init-method= "init" destroy-method= "close" > <property name= "url" value= "${db.master.url}" /> <property name= "username" value= "${db.master.user}" /> <property name= "password" value= "${db.master.password}" /> <property name= "driverclassname" value= "${db.master.driver}" /> <property name= "initialsize" value= "5" /> <property name= "maxactive" value= "100" /> <property name= "minidle" value= "10" /> <property name= "maxwait" value= "60000" /> <property name= "validationquery" value= "select 'x'" /> <property name= "testonborrow" value= "false" /> <property name= "testonreturn" value= "false" /> <property name= "testwhileidle" value= "true" /> <property name= "timebetweenevictionrunsmillis" value= "600000" /> <property name= "minevictableidletimemillis" value= "300000" /> <property name= "removeabandoned" value= "true" /> <property name= "removeabandonedtimeout" value= "1800" /> <property name= "logabandoned" value= "true" /> <!-- 配置監(jiān)控統(tǒng)計攔截的filters --> <property name= "filters" value= "config,mergestat,wall,log4j2" /> <property name= "connectionproperties" value= "config.decrypt=true" /> </bean> <!-- servera datasource --> <bean id= "serveradatasource" class = "com.alibaba.druid.pool.druiddatasource" init-method= "init" destroy-method= "close" > <property name= "url" value= "${db.servera.master.url}" /> <property name= "username" value= "${db.servera.master.user}" /> <property name= "password" value= "${db.servera.master.password}" /> <property name= "driverclassname" value= "${db.servera.master.driver}" /> <property name= "initialsize" value= "5" /> <property name= "maxactive" value= "100" /> <property name= "minidle" value= "10" /> <property name= "maxwait" value= "60000" /> <property name= "validationquery" value= "select 'x'" /> <property name= "testonborrow" value= "false" /> <property name= "testonreturn" value= "false" /> <property name= "testwhileidle" value= "true" /> <property name= "timebetweenevictionrunsmillis" value= "600000" /> <property name= "minevictableidletimemillis" value= "300000" /> <property name= "removeabandoned" value= "true" /> <property name= "removeabandonedtimeout" value= "1800" /> <property name= "logabandoned" value= "true" /> <!-- 配置監(jiān)控統(tǒng)計攔截的filters --> <property name= "filters" value= "config,mergestat,wall,log4j2" /> <property name= "connectionproperties" value= "config.decrypt=true" /> </bean> <!-- serverb datasource --> <bean id= "serverbdatasource" class = "com.alibaba.druid.pool.druiddatasource" init-method= "init" destroy-method= "close" > <property name= "url" value= "${db.serverb.master.url}" /> <property name= "username" value= "${db.serverb.master.user}" /> <property name= "password" value= "${db.serverb.master.password}" /> <property name= "driverclassname" value= "${db.serverb.master.driver}" /> <property name= "initialsize" value= "5" /> <property name= "maxactive" value= "100" /> <property name= "minidle" value= "10" /> <property name= "maxwait" value= "60000" /> <property name= "validationquery" value= "select 'x'" /> <property name= "testonborrow" value= "false" /> <property name= "testonreturn" value= "false" /> <property name= "testwhileidle" value= "true" /> <property name= "timebetweenevictionrunsmillis" value= "600000" /> <property name= "minevictableidletimemillis" value= "300000" /> <property name= "removeabandoned" value= "true" /> <property name= "removeabandonedtimeout" value= "1800" /> <property name= "logabandoned" value= "true" /> <!-- 配置監(jiān)控統(tǒng)計攔截的filters --> <property name= "filters" value= "config,mergestat,wall,log4j2" /> <property name= "connectionproperties" value= "config.decrypt=true" /> </bean> |
我配置了三個數(shù)據(jù)源:opdatasource(運營平臺本身的數(shù)據(jù)源),serveradatasource,serverbdatasource。
2、配置multipledatasource
multipledatasource相當(dāng)于以上三個數(shù)據(jù)源的一個代理,真正與spring/mybatis相結(jié)合的時multipledatasource,和單獨配置的datasource使用沒有分別:
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
|
<!-- spring整合mybatis:配置multipledatasource --> <bean id= "sqlsessionfactory" class = "com.baomidou.mybatisplus.spring.mybatissqlsessionfactorybean" > <property name= "datasource" ref= "multipledatasource" /> <!-- 自動掃描mapping.xml文件 --> <property name= "mapperlocations" > <list> <value>classpath*:/sqlmapperxml /*.xml</value> <value>classpath*:/sqlmapperxml/*/ *.xml</value> </list> </property> <property name= "configlocation" value= "classpath:xml/mybatis-config.xml" ></property> <property name= "typealiasespackage" value= "com.xxx.platform.model" /> <property name= "globalconfig" ref= "globalconfig" /> <property name= "plugins" > <array> <!-- 分頁插件配置 --> <bean id= "paginationinterceptor" class = "com.baomidou.mybatisplus.plugins.paginationinterceptor" > <property name= "dialecttype" value= "mysql" /> <property name= "optimizetype" value= "alidruid" /> </bean> </array> </property> </bean> <!-- mybatis 動態(tài)實現(xiàn) --> <bean id= "mapperscannerconfigurer" class = "org.mybatis.spring.mapper.mapperscannerconfigurer" > <!-- 對dao 接口動態(tài)實現(xiàn),需要知道接口在哪 --> <property name= "basepackage" value= "com.xxx.platform.mapper" /> <property name= "sqlsessionfactorybeanname" value= "sqlsessionfactory" ></property> </bean> <!-- mp 全局配置 --> <bean id= "globalconfig" class = "com.baomidou.mybatisplus.entity.globalconfiguration" > <property name= "idtype" value= "0" /> <property name= "dbcolumnunderline" value= "true" /> </bean> <!-- 事務(wù)管理配置multipledatasource --> <bean id= "transactionmanager" class = "org.springframework.jdbc.datasource.datasourcetransactionmanager" > <property name= "datasource" ref= "multipledatasource" ></property> </bean> |
了解了multipledatasource所處的位置之后,接下來重點看下multipledatasource怎么實現(xiàn),配置文件如下:
1
2
3
4
5
6
7
8
9
10
|
<bean id= "multipledatasource" class = "com.xxxx.platform.commons.db.multipledatasource" > <property name= "defaulttargetdatasource" ref= "opdatasource" /> <property name= "targetdatasources" > <map> <entry key= "opdatasource" value-ref= "opdatasource" /> <entry key= "serveradatasource" value-ref= "serveradatasource" /> <entry key= "serverbdatasource" value-ref= "serverbdatasource" /> </map> </property> </bean> |
實現(xiàn)的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
|
import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource; /** * * @classname: multipledatasource * @description: 配置多個數(shù)據(jù)源<br> * @author: yuzhu.peng * @date: 2018年1月12日 下午4:37:25 */ public class multipledatasource extends abstractroutingdatasource { private static final threadlocal<string> datasourcekey = new inheritablethreadlocal<string>(); public static void setdatasourcekey(string datasource) { datasourcekey.set(datasource); } @override protected object determinecurrentlookupkey() { return datasourcekey.get(); } public static void removedatasourcekey() { datasourcekey.remove(); } } |
繼承自spring的abstractroutingdatasource,實現(xiàn)抽象方法determinecurrentlookupkey,這個方法會在每次獲得數(shù)據(jù)庫連接connection的時候之前,決定本次連接的數(shù)據(jù)源datasource,可以看下spring的代碼就很清晰了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/*獲取連接*/ public connection getconnection() throws sqlexception { return determinetargetdatasource().getconnection(); } protected datasource determinetargetdatasource() { assert.notnull(this.resolveddatasources, "datasource router not initialized"); /*此處的determinecurrentlookupkey為抽象接口,獲取具體的數(shù)據(jù)源名稱*/ object lookupkey = determinecurrentlookupkey(); datasource 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; } /*抽象接口:也即我們的multipledatasource實現(xiàn)的接口*/ protected abstract object determinecurrentlookupkey(); |
第二步:每次請求(service方法級別)動態(tài)切換數(shù)據(jù)源
實現(xiàn)思路是利用spring的aop思想,攔截每次的service方法調(diào)用,然后根據(jù)方法的整體路徑名,動態(tài)切換multipledatasource中的數(shù)據(jù)的key。我們的項目,針對不同服務(wù)也即不同數(shù)據(jù)庫的操作,是彼此之間互相獨立的,不太建議在同一個service方法中調(diào)用不同的數(shù)據(jù)源,這樣的話需要將動態(tài)判斷是否需要切換的頻次(aop攔截的頻次)放在dao級別,也就是sql級別。另外,還不方便進(jìn)行事務(wù)管理。
我們來看動態(tài)切換數(shù)據(jù)源的aop實現(xiàn):
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
|
import java.lang.reflect.proxy; import org.apache.commons.lang.classutils; 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; /** * 數(shù)據(jù)源切換aop * * @author yuzhu.peng * @since 2018-01-15 */ @aspect @order ( 1 ) public class multipledatasourceinterceptor { /** * 攔截器對所有的業(yè)務(wù)實現(xiàn)類請求之前進(jìn)行數(shù)據(jù)源切換 特別注意,由于用到了多數(shù)據(jù)源,mapper的調(diào)用最好只在*serviceimpl,不然調(diào)用到非默認(rèn)數(shù)據(jù)源的表時,會報表不存在的異常 * * @param joinpoint * @throws throwable */ @before ( "execution(* com.xxxx.platform.service..*.*serviceimpl.*(..))" ) public void setdatasoruce(joinpoint joinpoint) throws throwable { class <?> clazz = joinpoint.gettarget().getclass(); string classname = clazz.getname(); if (classutils.isassignable(clazz, proxy. class )) { classname = joinpoint.getsignature().getdeclaringtypename(); } // 對類名含有servera的設(shè)置為servera數(shù)據(jù)源,否則默認(rèn)為后臺的數(shù)據(jù)源 if (classname.contains( ".servera." )) { multipledatasource.setdatasourcekey(dbconstant.data_source_servera); } else if (classname.contains( ".serverb." )) { multipledatasource.setdatasourcekey(dbconstant.data_source_serverb); } else { multipledatasource.setdatasourcekey(dbconstant.data_source_op); } } /** * 當(dāng)操作完成時,釋放當(dāng)前的數(shù)據(jù)源 如果不釋放,頻繁點擊時會發(fā)生數(shù)據(jù)源沖突,本是另一個數(shù)據(jù)源的表,結(jié)果跑到另外一個數(shù)據(jù)源去,報表不存在 * * @param joinpoint * @throws throwable */ @after ( "execution(* com.xxxx.service..*.*serviceimpl.*(..))" ) public void removedatasoruce(joinpoint joinpoint) throws throwable { multipledatasource.removedatasourcekey(); } } |
攔截所有的serviceimpl方法,根據(jù)方法的全限定名去判斷屬于那個數(shù)據(jù)源的功能,然后選擇相應(yīng)的數(shù)據(jù)源,發(fā)放執(zhí)行完后,釋放當(dāng)前的數(shù)據(jù)源。注意我用到了spring的 @order,注解,接下來會講到,當(dāng)定義多個aop的時候,order是很有用的。
其他:
一開始項目中并沒有引入事務(wù),所以一切都o(jì)k,每次都能訪問到正確的數(shù)據(jù)源,當(dāng)加入spring的事務(wù)管理后,不能動態(tài)切換數(shù)據(jù)源了(也好像是事務(wù)沒有生效,反正是二者沒有同時有效),后來發(fā)現(xiàn)原因是aop的執(zhí)行順序問題,所以用到了上邊提到的spring的order:
order越小,先被執(zhí)行。至此,既可以動態(tài)切換數(shù)據(jù)源,又可以成功用事務(wù)(在同一個數(shù)據(jù)源)。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://www.cnblogs.com/zackzhuzi/archive/2018/01/26/8359940.html