Spring+Mybatis環境配置多數據源
一、簡要概述
在做項目的時候遇到需要從兩個數據源獲取數據,項目使用的Spring + Mybatis環境,看到網上有一些關於多數據源的配置,自己也整理學習一下,然後自動切換實現從不同的數據源獲取數據功能。
二、代碼詳解
2.1 DataSourceConstants 數據源常量類
/**
* 數據源名稱常量類
* 對應 application.xml 中 bean multipleDataSource
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
public class DataSourceConstants {
/**
* 數據源1,默認數據源配置
*/
public static final String DATASOURCE_1 = "dataSource1";
/**
* 數據源2
*/
public static final String DATASOURCE_2 = "dataSource2";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2.2 DataSourceType 自定義數據源註解
/**
* 自定義數據源類型註解
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceType {
String value() default DataSourceConstants.DATASOURCE_1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2.3 MultipleDataSource 多數據源配置類
MultipleDataSource 繼承 AbstractRoutingDataSource 類,為什麼繼承這個類就可以了?請看 第五章 :實現原理。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 自定義多數據源配置類
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>();
/**
* 設置數據源
* @param dataSource 數據源名稱
*/
public static void setDataSource(String dataSource){
dataSourceHolder.set(dataSource);
}
/**
* 獲取數據源
* @return
*/
public static String getDatasource() {
return dataSourceHolder.get();
}
/**
* 清除數據源
*/
public static void clearDataSource(){
dataSourceHolder.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return dataSourceHolder.get();
}
}
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
2.4 MultipleDataSourceAop 多數據源自動切換通知類
注意:請設置 @Order(0)。否則可能出現 數據源切換失敗問題! 因為要在事務開啟之前就進行判斷,並進行切換數據源!
/**
* 多數據源自動切換通知類<br>
* <p>
* 首先判斷當前類是否被該DataSourceType註解進行注釋,如果沒有指定註解,則採用默認的數據源配置; <br>
* 如果有,則讀取註解中的value值,將數據源切到value指定的數據源
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
@Aspect // for aop
@Component // for auto scan
@Order(0) // execute before @Transactional
public class MultipleDataSourceAop {
private final Logger logger = Logger.getLogger(MultipleDataSourceAop.class);
/**
* 攔截 com.**.servicee中所有的方法,根據配置情況進行數據源切換
* com.jiuling.tz.service
* com.jiuling.web.service
* @param joinPoint
* @throws Throwable
*/
@Before("execution(* com.dufy.*.service.*.*(..))")
public void changeDataSource(JoinPoint joinPoint) throws Throwable {
try {
// 攔截的實體類,就是當前正在執行的service
Class<?> clazz = joinPoint.getTarget().getClass();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 提取目標對象方法註解和類型註解中的數據源標識
Class<?>[] types = method.getParameterTypes();
if (clazz.isAnnotationPresent(DataSourceType.class)) {
DataSourceType source = clazz.getAnnotation(DataSourceType.class);
MultipleDataSource.setDataSource(source.value());
logger.info("Service Class 數據源切換至--->" + source.value());
}
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSourceType.class)) {
DataSourceType source = m.getAnnotation(DataSourceType.class);
MultipleDataSource.setDataSource(source.value());
logger.info("Service Method 數據源切換至--->" + source.value());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 方法結束後
*/
@After("execution(* com.dufy.*.service.*.*(..))")
public void afterReturning() throws Throwable {
try {
MultipleDataSource.clearDataSource();
logger.debug("數據源已移除!");
} catch (Exception e) {
e.printStackTrace();
logger.debug("數據源移除報錯!");
}
}
}
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
三、配置詳情
在 applicationContext.xml 中配置詳情
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 自動掃描包 ,將帶有註解的類 納入spring容器管理 -->
<context:component-scan base-package="com.dufy"></context:component-scan>
<!-- 引入配置文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:jdbc.properties</value>
</list>
</property>
</bean>
<!-- dataSource1 配置 -->
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本屬性 url、user、password -->
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${ds.initialSize}"/>
<property name="minIdle" value="${ds.minIdle}"/>
<property name="maxActive" value="${ds.maxActive}"/>
<!-- 配置獲取連接等待超時的時間 -->
<property name="maxWait" value="${ds.maxWait}"/>
<!-- 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${ds.timeBetweenEvictionRunsMillis}"/>
<!-- 配置一個連接在池中最小生存的時間,單位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${ds.minEvictableIdleTimeMillis}"/>
<property name="validationQuery" value="SELECT "x""/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<!-- 打開PSCache,並且指定每個連接上PSCache的大小 -->
<property name="poolPreparedStatements" value="false"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
<!-- 配置監控統計攔截的filters -->
<property name="filters" value="stat"/>
</bean>
<!-- dataSource2 配置-->
<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本屬性 url、user、password -->
<property name="url" value="${jd.jdbc.url}"/>
<property name="username" value="${jd.jdbc.username}"/>
<property name="password" value="${jd.jdbc.password}"/>
<!-- 其他配置省略 -->
</bean>
<!--多數據源配置-->
<bean id="multipleDataSource" class="com.jiuling.core.ds.MultipleDataSource">
<property name="defaultTargetDataSource" ref="dataSource1" />
<property name="targetDataSources">
<map key-type = "java.lang.String">
<entry key="dataSource1" value-ref="dataSource1"/>
<entry key="dataSource2" value-ref="dataSource2"/>
<!-- 這裡還可以加多個dataSource -->
</map>
</property>
</bean>
<!-- mybatis文件配置,掃描所有mapper文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="multipleDataSource"
p:configLocation="classpath:mybatis-config.xml"
p:mapperLocations="classpath:com/dufy/*/dao/*.xml"/>
<!-- spring與mybatis整合配置,掃描所有dao -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.dufy.*.dao"
p:sqlSessionFactoryBeanName="sqlSessionFactory"/>
<!-- 對dataSource 數據源進行事務管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="multipleDataSource"/>
<!-- 事務管理 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 對insert,update,delete 開頭的方法進行事務管理,只要有異常就回滾 -->
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
<!-- select,count開頭的方法,開啟只讀,提高資料庫訪問性能 -->
<tx:method name="select*" read-only="true"/>
<tx:method name="count*" read-only="true"/>
<!-- 對其他方法 使用默認的事務管理 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 事務 aop 配置 -->
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* com.dufy.*.service..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>
</aop:config>
<!-- 配置使Spring採用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- 啟用對事務註解的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
jdbc.properties 配置內容
##-------------mysql資料庫連接配置 ---------------------###
# dataSource1
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.110:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jdbc.username=test
jdbc.password=123456
#配置初始化大小、最小、最大
ds.initialSize=1
ds.minIdle=1
ds.maxActive=20
#配置獲取連接等待超時的時間
ds.maxWait=60000
#配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
ds.timeBetweenEvictionRunsMillis=60000
#配置一個連接在池中最小生存的時間,單位是毫秒
ds.minEvictableIdleTimeMillis=300000
# dataSource2
jd.jdbc.url=jdbc:mysql://192.168.1.120:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jd.jdbc.username=root
jd.jdbc.password=123456
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
四、測試切換
注意:測試服務類的包路徑,因為只有被AOP攔截的到的指定的Service才會進行數據源的切換。
package com.dufy.web.service.impl.Data1ServiceImpl;
/**
* 使用 dataSourc1 ,配置dataSourc1的數據源
* @author:duf
* @version:1.0.0
* @date 2018/12/17
*/
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_1)
public class Data1ServiceImpl implements Data1Service {
@Resource
private Data1Mapper data1Mapper;
@Override
public List<String> selectCaseByUpdateTime(String name) {
List<String> data1 = data1Mapper.selectData1(name);
return data1;
}
}
package com.dufy.web.service.impl.Data2ServiceImpl;
/**
* 使用 dataSourc2 ,配置dataSourc2的數據源
* @author:duf
* @version:1.0.0
* @date 2018/12/17
*/
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_2)
public class Data2ServiceImpl implements Data2Service {
@Resource
private Data2Mapper data2Mapper;
@Override
public List<String> selectCaseByUpdateTime(String name) {
List<String> data2 = data2Mapper.selectData2(name);
return data2;
}
}
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
通過測試後發現,兩個Service伺服器分別調用自己的配置的數據源進行數據的獲取!
五、實現原理
基於AbstractRoutingDataSource 實現 多數據源配置,通過AOP來進行數據源的靈活切換。AOP的相關原理這裡不做說明,就簡單說一下 AbstractRoutingDataSource ,它是如何切換數據源的!
首先我們繼承 AbstractRoutingDataSource 它是一個抽象類,然後要實現它裡面的一個抽象方法。
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
1
實現了InitializingBean,InitializingBean介面為bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是繼承該介面的類,在初始化bean的時候都會執行該方法。
在AbstractRoutingDataSource 中afterPropertiesSet方法的實現。
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property "targetDataSources" is required");
} else {
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
Iterator var1 = this.targetDataSources.entrySet().iterator();
while(var1.hasNext()) {
Entry<Object, Object> entry = (Entry)var1.next();
Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
afterPropertiesSet方法將配置在 applicationContext.xml中的 targetDataSources 解析並構造出一個HashMap。
然後在實際過程中當需要訪問資料庫的時候,會首先獲取一個Connection,下面看一下獲取 Connection 的方法。
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
// 這裡需要注意,通過子類的 determineCurrentLookupKey 方法 獲取 lookupKey
Object lookupKey = this.determineCurrentLookupKey();
// 轉換為對應的 DataSource 數據源
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
// 如果符合上述條件,則獲取默認的數據源,也就是在 ApplicationContext.xml 配置的默認數據源
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
// 抽象方法,用於子類實現
protected abstract Object determineCurrentLookupKey();
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
知道了 AbstractRoutingDataSource 的抽象方法後,通過AOP攔截,將Service上面配置不同的數據源進行裝配到當前請求的ThreadLocal中, 最後 在獲取Connection的時候,就能通過determineCurrentLookupKey方法獲取到 設置的數據源。
@Override
protected Object determineCurrentLookupKey() {
return dataSourceHolder.get();
}
1
2
3
4
再次強調: 必須保證切換數據源的Aspect必須在@Transactional這個Aspect之前執行,使用@Order(0)來保證切換數據源先於@Transactional執行)。
---------------------
作者:每天都在變得更好的阿飛
原文:https://blog.csdn.net/u010648555/article/details/85109308
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
打開今日頭條,查看更多圖片※c井 用時間日期作為文件名
※非常震撼的純CSS3人物行走動畫
TAG:程序員小新人學習 |