【Mybatis】MyBatis之配置多數據源(十)


  在做項目的過程中,有時候一個數據源是不夠,那么就需要配置多個數據源。本例介紹mybatis多數據源配置

前言

  一般項目單數據源,使用流程如下:

    

  單個數據源綁定給sessionFactory,再在Dao層操作,若多個數據源的話,那不是就成了下圖

    

  可見,sessionFactory都寫死在了Dao層,若我再添加個數據源的話,則又得添加一個sessionFactory。所以比較好的做法應該是下圖

    

實現原理

   1、擴展Spring的AbstractRoutingDataSource抽象類(該類充當了DataSource的路由中介, 能有在運行時, 根據某種key值來動態切換到真正的DataSource上。)

     從AbstractRoutingDataSource的源碼中:

1 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

  2、我們可以看到,它繼承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子類,So我們可以分析下它的getConnection方法:

1 public Connection getConnection() throws SQLException {  
2     return determineTargetDataSource().getConnection();  
3 }  
4   
5 public Connection getConnection(String username, String password) throws SQLException {  
6      return determineTargetDataSource().getConnection(username, password);  
7 }

  3、 獲取連接的方法中,重點是determineTargetDataSource()方法,看源碼:

 1 /** 
 2      * Retrieve the current target DataSource. Determines the 
 3      * {@link #determineCurrentLookupKey() current lookup key}, performs 
 4      * a lookup in the {@link #setTargetDataSources targetDataSources} map, 
 5      * falls back to the specified 
 6      * {@link #setDefaultTargetDataSource default target DataSource} if necessary. 
 7      * @see #determineCurrentLookupKey() 
 8      */  
 9     protected DataSource determineTargetDataSource() {  
10         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
11         Object lookupKey = determineCurrentLookupKey();    
12         DataSource dataSource = this.resolvedDataSources.get(lookupKey);  
13         if (dataSource == null && (this.lenientFallback || lookupKey == null)) {  
14             dataSource = this.resolvedDefaultDataSource;  
15         }  
16         if (dataSource == null) {  
17             throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
18         }  
19         return dataSource;  
20     }

 

  上面這段源碼的重點在於determineCurrentLookupKey()方法,這是AbstractRoutingDataSource類中的一個抽象方法,而它的返回值是你所要用的數據源dataSource的key值,有了這個key值,resolvedDataSource(這是個map,由配置文件中設置好后存入的)就從中取出對應的DataSource,如果找不到,就用配置默認的數據源。

  看完源碼,應該有點啟發了吧,沒錯!你要擴展AbstractRoutingDataSource類,並重寫其中的determineCurrentLookupKey()方法,來實現數據源的切換

案例

  1、搭建一個Springmvc + Spring + Mybatis  maven項目,POM文件中引入AOP相關依賴,參考:【Mybatis】MyBatis之整合Spring(八)

  1 <project xmlns="http://maven.apache.org/POM/4.0.0"
  2     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  4     <modelVersion>4.0.0</modelVersion>
  5     <groupId>com.test</groupId>
  6     <artifactId>test-spring-mybatis</artifactId>
  7     <packaging>war</packaging>
  8     <version>1.0.0-SNAPSHOT</version>
  9     <url>http://maven.apache.org</url>
 10 
 11     <!-- 定義maven變量 -->
 12     <properties>
 13         <!-- spring -->
 14         <spring.version>5.1.4.RELEASE</spring.version>
 15 
 16         <!-- Mybatis -->
 17         <mybatis.version>3.5.0</mybatis.version>
 18         <!-- Mybatis 整合 Spring -->
 19         <mybatis-spring.version>2.0.0</mybatis-spring.version>
 20 
 21         <!-- mysql -->
 22         <mysql.version>8.0.13</mysql.version>
 23 
 24         <!-- c3p0 連接池 -->
 25         <c3p0.version>0.9.5.4</c3p0.version>
 26 
 27         <!-- logback -->
 28         <slf4j-api.version>1.7.5</slf4j-api.version>
 29         <logback.version>0.9.30</logback.version>
 30 
 31         <!-- Servlet -->
 32         <servlet.version>3.0.1</servlet.version>
 33         <jsp-api.version>2.2</jsp-api.version>
 34 
 35         <!-- jstl -->
 36         <jstl.version>1.2</jstl.version>
 37         <standard.version>1.1.2</standard.version>
 38 
 39         <!-- test junit -->
 40         <junit.version>3.8.1</junit.version>
 41 
 42         <!-- jdk -->
 43         <jdk.version>1.8</jdk.version>
 44         <maven.compiler.plugin.version>2.3.2</maven.compiler.plugin.version>
 45     </properties>
 46 
 47 
 48     <dependencies>
 49 
 50         <!-- Spring IOC 核心容器 -->
 51         <dependency>
 52             <groupId>org.springframework</groupId>
 53             <artifactId>spring-core</artifactId>
 54             <version>${spring.version}</version>
 55         </dependency>
 56 
 57         <dependency>
 58             <groupId>org.springframework</groupId>
 59             <artifactId>spring-beans</artifactId>
 60             <version>${spring.version}</version>
 61         </dependency>
 62 
 63         <dependency>
 64             <groupId>org.springframework</groupId>
 65             <artifactId>spring-context</artifactId>
 66             <version>${spring.version}</version>
 67         </dependency>
 68 
 69         <dependency>
 70             <groupId>org.springframework</groupId>
 71             <artifactId>spring-expression</artifactId>
 72             <version>${spring.version}</version>
 73         </dependency>
 74 
 75         <!-- Spring AOP 切面 模塊 -->
 76         <dependency>
 77             <groupId>org.springframework</groupId>
 78             <artifactId>spring-aop</artifactId>
 79             <version>${spring.version}</version>
 80         </dependency>
 81 
 82         <dependency>
 83             <groupId>org.aspectj</groupId>
 84             <artifactId>aspectjrt</artifactId>
 85             <version>1.9.2</version>
 86         </dependency>
 87         
 88         <dependency>
 89             <groupId>org.aspectj</groupId>
 90             <artifactId>aspectjweaver</artifactId>
 91             <version>1.9.2</version>
 92         </dependency>
 93 
 94         <!-- Spring WEB MVC 模塊 -->
 95         <dependency>
 96             <groupId>org.springframework</groupId>
 97             <artifactId>spring-web</artifactId>
 98             <version>${spring.version}</version>
 99         </dependency>
100 
101         <dependency>
102             <groupId>org.springframework</groupId>
103             <artifactId>spring-webmvc</artifactId>
104             <version>${spring.version}</version>
105         </dependency>
106 
107         <!-- Spring 事物 模塊 -->
108         <dependency>
109             <groupId>org.springframework</groupId>
110             <artifactId>spring-tx</artifactId>
111             <version>${spring.version}</version>
112         </dependency>
113 
114         <!-- Spring ORM 對象關系映射 模塊 -->
115         <dependency>
116             <groupId>org.springframework</groupId>
117             <artifactId>spring-orm</artifactId>
118             <version>${spring.version}</version>
119         </dependency>
120 
121         <!-- Spring JDBC 模塊 -->
122         <dependency>
123             <groupId>org.springframework</groupId>
124             <artifactId>spring-jdbc</artifactId>
125             <version>${spring.version}</version>
126         </dependency>
127 
128         <!-- Mybatis -->
129         <dependency>
130             <groupId>org.mybatis</groupId>
131             <artifactId>mybatis</artifactId>
132             <version>${mybatis.version}</version>
133         </dependency>
134 
135         <!-- Mybatis 整合 Spring -->
136         <dependency>
137             <groupId>org.mybatis</groupId>
138             <artifactId>mybatis-spring</artifactId>
139             <version>${mybatis-spring.version}</version>
140         </dependency>
141 
142         <!-- mysql -->
143         <dependency>
144             <groupId>mysql</groupId>
145             <artifactId>mysql-connector-java</artifactId>
146             <version>${mysql.version}</version>
147         </dependency>
148 
149         <!-- c3p0 連接池 -->
150         <!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
151         <dependency>
152             <groupId>com.mchange</groupId>
153             <artifactId>c3p0</artifactId>
154             <version>${c3p0.version}</version>
155         </dependency>
156 
157 
158         <!-- logback -->
159         <dependency>
160             <groupId>org.slf4j</groupId>
161             <artifactId>slf4j-api</artifactId>
162             <version>${slf4j-api.version}</version>
163             <type>jar</type>
164             <scope>compile</scope>
165         </dependency>
166 
167         <dependency>
168             <groupId>ch.qos.logback</groupId>
169             <artifactId>logback-core</artifactId>
170             <version>${logback.version}</version>
171             <type>jar</type>
172         </dependency>
173 
174         <dependency>
175             <groupId>ch.qos.logback</groupId>
176             <artifactId>logback-classic</artifactId>
177             <version>${logback.version}</version>
178             <type>jar</type>
179         </dependency>
180 
181         <dependency>
182             <groupId>ch.qos.logback</groupId>
183             <artifactId>logback-access</artifactId>
184             <version>${logback.version}</version>
185         </dependency>
186 
187 
188         <!-- Servlet -->
189         <dependency>
190             <groupId>javax.servlet</groupId>
191             <artifactId>javax.servlet-api</artifactId>
192             <version>${servlet.version}</version>
193             <scope>provided</scope>
194         </dependency>
195         <dependency>
196             <groupId>javax.servlet.jsp</groupId>
197             <artifactId>jsp-api</artifactId>
198             <version>${jsp-api.version}</version>
199             <scope>provided</scope>
200         </dependency>
201 
202         <!-- jstl -->
203         <dependency>
204             <groupId>javax.servlet</groupId>
205             <artifactId>jstl</artifactId>
206             <version>${jstl.version}</version>
207         </dependency>
208 
209         <dependency>
210             <groupId>taglibs</groupId>
211             <artifactId>standard</artifactId>
212             <version>${standard.version}</version>
213         </dependency>
214 
215         <!-- test -->
216         <dependency>
217             <groupId>junit</groupId>
218             <artifactId>junit</artifactId>
219             <version>${junit.version}</version>
220             <scope>test</scope>
221         </dependency>
222 
223     </dependencies>
224 
225     <build>
226         <plugins>
227             <!-- define the project compile level -->
228             <plugin>
229                 <groupId>org.apache.maven.plugins</groupId>
230                 <artifactId>maven-compiler-plugin</artifactId>
231                 <version>${maven.compiler.plugin.version}</version>
232                 <configuration>
233                     <source>${jdk.version}</source>
234                     <target>${jdk.version}</target>
235                 </configuration>
236             </plugin>
237         </plugins>
238         <finalName>test_spring_mybatis</finalName>
239     </build>
240 </project>
pom.xml

  2、編輯一個擴展AbstractRoutingDataSource類,DynamicDataSource.java

 1 package com.test.datasource;
 2 
 3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 4 
 5 /**
 6  *     動態數據源(依賴於spring)
 7  * @author chenheng
 8  * @date 2019-08-03 17:27:35
 9  *
10  */
11 public class DynamicDataSource extends AbstractRoutingDataSource {
12 
13     @Override
14     protected Object determineCurrentLookupKey() {
15          return DataSourceHolder.getDataSource();
16     }
17 
18 }

  3、 封裝一個的對數據源進行操作的類,DataSourceHolder.java

 1 package com.test.datasource;
 2 
 3 public class DataSourceHolder {
 4     
 5     // 線程本地環境
 6     private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
 7 
 8     // 設置數據源
 9     public static void setDataSource(String customerType) {
10         dataSources.set(customerType);
11     }
12 
13     // 獲取數據源
14     public static String getDataSource() {
15         return (String) dataSources.get();
16     }
17 
18     // 清除數據源
19     public static void clearDataSource() {
20         dataSources.remove();
21     }
22 }

  4、當需要切換數據源的時候執行啦。手動在代碼中調用寫死嗎?調用setDataSource方法

    但是這種方法比較死板,所以我們可以應用spring aop來設置,把配置的數據源類型都設置成為注解標簽,在service層中需要切換數據源的方法上,寫上注解標簽,調用相應方法切換數據源咯(就跟你設置事務一樣)

1 @TargetDataSource(name=TargetDataSource.SLAVE)
2 public List<Employee> getEmpsFromSalve()

    編輯注解標簽TargetDataSource.java

 1 package com.test.annotation;
 2 
 3 import java.lang.annotation.*;
 4 
 5 @Target({ElementType.METHOD, ElementType.TYPE})
 6 @Retention(RetentionPolicy.RUNTIME)
 7 @Documented
 8 public @interface TargetDataSource {
 9     
10     String name() default TargetDataSource.MASTER;
11  
12     public static String MASTER = "dataSource1";
13  
14     public static String SLAVE = "dataSource2";
15  
16 }

  5、編輯切面的Bean,DataSourceExchange.java

 1 package com.test.datasource;
 2 
 3 import java.lang.reflect.Method;
 4 
 5 import org.springframework.aop.AfterReturningAdvice;
 6 import org.springframework.aop.MethodBeforeAdvice;
 7 
 8 import com.test.annotation.TargetDataSource;
 9 
10 public class DataSourceExchange implements MethodBeforeAdvice, AfterReturningAdvice {
11 
12     @Override
13     public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
14         DataSourceHolder.clearDataSource();
15     }
16 
17     @Override
18     public void before(Method method, Object[] args, Object target) throws Throwable {
19         // 這里TargetDataSource是自定義的注解
20         if (method.isAnnotationPresent(TargetDataSource.class)) {
21             TargetDataSource datasource = method.getAnnotation(TargetDataSource.class);
22             DataSourceHolder.setDataSource(datasource.name());
23         } else {
24             if(target.getClass().isAnnotationPresent(TargetDataSource.class))
25             {
26                 TargetDataSource datasource = target.getClass().getAnnotation(TargetDataSource.class);
27                 DataSourceHolder.setDataSource(datasource.name());
28             }
29         }
30 
31     }
32 } 

  6、配置文件

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:aop="http://www.springframework.org/schema/aop"
 5     xmlns:context="http://www.springframework.org/schema/context"
 6     xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
 7     xmlns:tx="http://www.springframework.org/schema/tx"
 8     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 9         http://www.springframework.org/schema/beans/spring-beans.xsd
10         http://mybatis.org/schema/mybatis-spring 
11         http://mybatis.org/schema/mybatis-spring.xsd
12         http://www.springframework.org/schema/aop
13         http://www.springframework.org/schema/aop/spring-aop.xsd
14         http://www.springframework.org/schema/tx 
15         http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
16         http://www.springframework.org/schema/context 
17         http://www.springframework.org/schema/context/spring-context-4.0.xsd">
18 
19     <!-- 引入數據庫的配置文件 -->
20     <context:property-placeholder location="classpath:dbconfig.properties" />
21     
22  
23     <bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource">
24         <property name="jdbcUrl" value="${datasource1.jdbc.url}"></property>
25         <property name="driverClass" value="${datasource1.jdbc.driver}"></property>
26         <property name="user" value="${datasource1.jdbc.username}"></property>
27         <property name="password" value="${datasource1.jdbc.password}"></property>
28     </bean>
29     
30     <bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
31         <property name="jdbcUrl" value="${datasource2.jdbc.url}"></property>
32         <property name="driverClass" value="${datasource2.jdbc.driver}"></property>
33         <property name="user" value="${datasource2.jdbc.username}"></property>
34         <property name="password" value="${datasource2.jdbc.password}"></property>
35     </bean>
36     
37  
38     
39     <!-- 數據源:Spring用來控制業務邏輯。數據源、事務控制、aop -->
40     <bean id="dataSource" class="com.test.datasource.DynamicDataSource">
41         <property name="targetDataSources">
42             <map key-type="java.lang.String">
43                 <entry key="dataSource1" value-ref="dataSource1"></entry>
44                 <entry key="dataSource2" value-ref="dataSource2"></entry>
45             </map>
46         </property>
47         <!-- 默認目標數據源為你主庫數據源 -->
48         <property name="defaultTargetDataSource" ref="dataSource1"/>
49     </bean>
50   
51     
52     <!-- spring事務管理 -->
53     <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
54         <property name="dataSource" ref="dataSource"></property>
55     </bean>
56 
57     <!-- 開啟基於注解的事務 -->
58     <tx:annotation-driven transaction-manager="dataSourceTransactionManager" order="2"/>
59     
60     <!-- 
61     整合mybatis 
62         目的:1、spring管理所有組件。mapper的實現類。
63                 service==>Dao   @Autowired:自動注入mapper;
64             2、spring用來管理事務,spring聲明式事務
65     -->
66     <!--創建出SqlSessionFactory對象  -->
67     <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
68         <property name="dataSource" ref="dataSource"></property>
69         <!-- configLocation指定全局配置文件的位置 -->
70         <property name="configLocation" value="classpath:mybatis-config.xml"></property>
71         <!--mapperLocations: 指定mapper文件的位置-->
72         <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
73     </bean>
74     
75     <!--配置一個可以進行批量執行的sqlSession  -->
76     <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
77         <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
78         <constructor-arg name="executorType" value="BATCH"></constructor-arg>
79     </bean>
80     
81     <!-- 掃描所有的mapper接口的實現,讓這些mapper能夠自動注入;
82     base-package:指定mapper接口的包名
83      -->
84     <mybatis-spring:scan base-package="com.test.dao"/>
85     
86     
87     <!-- 配置切面的Bean -->
88     <bean id="dataSourceExchange" class="com.test.datasource.DataSourceExchange"/>
89     
90     
91     <!-- 配置AOP -->
92     <aop:config>
93         <!-- 配置切點表達式  -->
94         <aop:pointcut id="servicePointcut" expression="execution(* com.test.service.*.*(..))"/>
95         <!-- 關鍵配置,切換數據源一定要比持久層代碼更先執行(事務也算持久層代碼) <aop:advisor advice-ref="txAdvice" pointcut-ref="service" order="2"/> -->
96         <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1"/>
97     </aop:config>
98     
99 </beans>

  注意:Spring中的事務是通過aop來實現的,當我們自己寫aop攔截的時候,會遇到跟spring的事務aop執行的先后順序問題,比如說動態切換數據源的問題,如果事務在前,數據源切換在后,會導致數據源切換失效,所以就用到了Order(排序)這個關鍵字

1 <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1"/>
1 <!-- 開啟基於注解的事務 -->
2 <tx:annotation-driven transaction-manager="dataSourceTransactionManager" order="2"/>

  7、在service上加上注解即可使用

1 @Transactional
2 @TargetDataSource(name=TargetDataSource.SLAVE)
3 public int addEmployeeFromSalve(Employee employee) {
4     
5     return employeeMapper.insert(employee);
6 }

    

  

數據流轉順序:

   1.xml<aop>攔截到數據源名稱

   2.執行切面DataSourceExchange中的before方法,將數據源名稱放入 DataSourceHolder中

   3.Spring 調用determineCurrentLookupKey()方法<DynamicDataSource中重寫AbstractRoutingDataSource類中的方法> ,從DataSourceHolder取出當前的數據庫名稱,並返回

  4.AbstractRoutingDataSource類中determineTargetDataSource()方法調用determineCurrentLookupKey()匹配到指定的數據庫,並建立鏈接,即為切換到相應的數據庫;

  5.在指定的數據庫中執行相應的sql

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM