在做項目的過程中,有時候一個數據源是不夠,那么就需要配置多個數據源。本例介紹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>
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
