在做项目的过程中,有时候一个数据源是不够,那么就需要配置多个数据源。本例介绍mybatis多数据源配置。
一般项目单数据源,使用流程如下:
单个数据源绑定给sessionFactory,再在Dao层操作,若多个数据源的话,那不是就成了下图
可见,sessionFactory都写死在了Dao层,若我再添加个数据源的话,则又得添加一个sessionFactory。所以比较好的做法应该是下图
实现原理
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
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;
}
上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。
看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换。
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.0modelVersion>
5 <groupId>com.testgroupId>
6 <artifactId>test-spring-mybatisartifactId>
7 <packaging>warpackaging>
8 <version>1.0.0-SNAPSHOTversion>
9 <url>http://maven.apache.orgurl>
10
11
12 <properties>
13
14 <spring.version>5.1.4.RELEASEspring.version>
15
16
17 <mybatis.version>3.5.0mybatis.version>
18
19 <mybatis-spring.version>2.0.0mybatis-spring.version>
20
21
22 <mysql.version>8.0.13mysql.version>
23
24
25 <c3p0.version>0.9.5.4c3p0.version>
26
27
28 <slf4j-api.version>1.7.5slf4j-api.version>
29 <logback.version>0.9.30logback.version>
30
31
32 <servlet.version>3.0.1servlet.version>
33 <jsp-api.version>2.2jsp-api.version>
34
35
36 <jstl.version>1.2jstl.version>
37 <standard.version>1.1.2standard.version>
38
39
40 <junit.version>3.8.1junit.version>
41
42
43 <jdk.version>1.8jdk.version>
44 <maven.compiler.plugin.version>2.3.2maven.compiler.plugin.version>
45 properties>
46
47
48 <dependencies>
49
50
51 <dependency>
52 <groupId>org.springframeworkgroupId>
53 <artifactId>spring-coreartifactId>
54 <version>${spring.version}version>
55 dependency>
56
57 <dependency>
58 <groupId>org.springframeworkgroupId>
59 <artifactId>spring-beansartifactId>
60 <version>${spring.version}version>
61 dependency>
62
63 <dependency>
64 <groupId>org.springframeworkgroupId>
65 <artifactId>spring-contextartifactId>
66 <version>${spring.version}version>
67 dependency>
68
69 <dependency>
70 <groupId>org.springframeworkgroupId>
71 <artifactId>spring-expressionartifactId>
72 <version>${spring.version}version>
73 dependency>
74
75
76 <dependency>
77 <groupId>org.springframeworkgroupId>
78 <artifactId>spring-aopartifactId>
79 <version>${spring.version}version>
80 dependency>
81
82 <dependency>
83 <groupId>org.aspectjgroupId>
84 <artifactId>aspectjrtartifactId>
85 <version>1.9.2version>
86 dependency>
87
88 <dependency>
89 <groupId>org.aspectjgroupId>
90 <artifactId>aspectjweaverartifactId>
91 <version>1.9.2version>
92 dependency>
93
94
95 <dependency>
96 <groupId>org.springframeworkgroupId>
97 <artifactId>spring-webartifactId>
98 <version>${spring.version}version>
99 dependency>
100
101 <dependency>
102 <groupId>org.springframeworkgroupId>
103 <artifactId>spring-webmvcartifactId>
104 <version>${spring.version}version>
105 dependency>
106
107
108 <dependency>
109 <groupId>org.springframeworkgroupId>
110 <artifactId>spring-txartifactId>
111 <version>${spring.version}version>
112 dependency>
113
114
115 <dependency>
116 <groupId>org.springframeworkgroupId>
117 <artifactId>spring-ormartifactId>
118 <version>${spring.version}version>
119 dependency>
120
121
122 <dependency>
123 <groupId>org.springframeworkgroupId>
124 <artifactId>spring-jdbcartifactId>
125 <version>${spring.version}version>
126 dependency>
127
128
129 <dependency>
130 <groupId>org.mybatisgroupId>
131 <artifactId>mybatisartifactId>
132 <version>${mybatis.version}version>
133 dependency>
134
135
136 <dependency>
137 <groupId>org.mybatisgroupId>
138 <artifactId>mybatis-springartifactId>
139 <version>${mybatis-spring.version}version>
140 dependency>
141
142
143 <dependency>
144 <groupId>mysqlgroupId>
145 <artifactId>mysql-connector-javaartifactId>
146 <version>${mysql.version}version>
147 dependency>
148
149
150
151 <dependency>
152 <groupId>com.mchangegroupId>
153 <artifactId>c3p0artifactId>
154 <version>${c3p0.version}version>
155 dependency>
156
157
158
159 <dependency>
160 <groupId>org.slf4jgroupId>
161 <artifactId>slf4j-apiartifactId>
162 <version>${slf4j-api.version}version>
163 <type>jartype>
164 <scope>compilescope>
165 dependency>
166
167 <dependency>
168 <groupId>ch.qos.logbackgroupId>
169 <artifactId>logback-coreartifactId>
170 <version>${logback.version}version>
171 <type>jartype>
172 dependency>
173
174 <dependency>
175 <groupId>ch.qos.logbackgroupId>
176 <artifactId>logback-classicartifactId>
177 <version>${logback.version}version>
178 <type>jartype>
179 dependency>
180
181 <dependency>
182 <groupId>ch.qos.logbackgroupId>
183 <artifactId>logback-accessartifactId>
184 <version>${logback.version}version>
185 dependency>
186
187
188
189 <dependency>
190 <groupId>javax.servletgroupId>
191 <artifactId>javax.servlet-apiartifactId>
192 <version>${servlet.version}version>
193 <scope>providedscope>
194 dependency>
195 <dependency>
196 <groupId>javax.servlet.jspgroupId>
197 <artifactId>jsp-apiartifactId>
198 <version>${jsp-api.version}version>
199 <scope>providedscope>
200 dependency>
201
202
203 <dependency>
204 <groupId>javax.servletgroupId>
205 <artifactId>jstlartifactId>
206 <version>${jstl.version}version>
207 dependency>
208
209 <dependency>
210 <groupId>taglibsgroupId>
211 <artifactId>standardartifactId>
212 <version>${standard.version}version>
213 dependency>
214
215
216 <dependency>
217 <groupId>junitgroupId>
218 <artifactId>junitartifactId>
219 <version>${junit.version}version>
220 <scope>testscope>
221 dependency>
222
223 dependencies>
224
225 <build>
226 <plugins>
227
228 <plugin>
229 <groupId>org.apache.maven.pluginsgroupId>
230 <artifactId>maven-compiler-pluginartifactId>
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_mybatisfinalName>
239 build>
240 project>
pom.xml
package com.test.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源(依赖于spring)
* @author chenheng
* @date 2019-08-03 17:27:35
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSource();
}
}
package com.test.datasource;
public class DataSourceHolder {
// 线程本地环境
private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
// 设置数据源
public static void setDataSource(String customerType) {
dataSources.set(customerType);
}
// 获取数据源
public static String getDataSource() {
return (String) dataSources.get();
}
// 清除数据源
public static void clearDataSource() {
dataSources.remove();
}
}
@TargetDataSource(name=TargetDataSource.SLAVE)
public List<Employee> getEmpsFromSalve()
编辑注解标签TargetDataSource.java
package com.test.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name() default TargetDataSource.MASTER;
public static String MASTER = "dataSource1";
public static String SLAVE = "dataSource2";
}
package com.test.datasource;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import com.test.annotation.TargetDataSource;
public class DataSourceExchange implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
DataSourceHolder.clearDataSource();
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
// 这里TargetDataSource是自定义的注解
if (method.isAnnotationPresent(TargetDataSource.class)) {
TargetDataSource datasource = method.getAnnotation(TargetDataSource.class);
DataSourceHolder.setDataSource(datasource.name());
} else {
if(target.getClass().isAnnotationPresent(TargetDataSource.class))
{
TargetDataSource datasource = target.getClass().getAnnotation(TargetDataSource.class);
DataSourceHolder.setDataSource(datasource.name());
}
}
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://mybatis.org/schema/mybatis-spring
http://mybatis.org/schema/mybatis-spring.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:property-placeholder location="classpath:dbconfig.properties" />
<bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${datasource1.jdbc.url}">property>
<property name="driverClass" value="${datasource1.jdbc.driver}">property>
<property name="user" value="${datasource1.jdbc.username}">property>
<property name="password" value="${datasource1.jdbc.password}">property>
bean>
<bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${datasource2.jdbc.url}">property>
<property name="driverClass" value="${datasource2.jdbc.driver}">property>
<property name="user" value="${datasource2.jdbc.username}">property>
<property name="password" value="${datasource2.jdbc.password}">property>
bean>
<bean id="dataSource" class="com.test.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSource1" value-ref="dataSource1">entry>
<entry key="dataSource2" value-ref="dataSource2">entry>
map>
property>
<property name="defaultTargetDataSource" ref="dataSource1"/>
bean>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" order="2"/>
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="configLocation" value="classpath:mybatis-config.xml">property>
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml">property>
bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean">constructor-arg>
<constructor-arg name="executorType" value="BATCH">constructor-arg>
bean>
<mybatis-spring:scan base-package="com.test.dao"/>
<bean id="dataSourceExchange" class="com.test.datasource.DataSourceExchange"/>
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.test.service.*.*(..))"/>
<aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1"/>
aop:config>
beans>
注意:Spring中的事务是通过aop来实现的,当我们自己写aop拦截的时候,会遇到跟spring的事务aop执行的先后顺序问题,比如说动态切换数据源的问题,如果事务在前,数据源切换在后,会导致数据源切换失效,所以就用到了Order(排序)这个关键字
<aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1"/>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" order="2"/>
@Transactional
@TargetDataSource(name=TargetDataSource.SLAVE)
public int addEmployeeFromSalve(Employee employee) {
return employeeMapper.insert(employee);
}
数据流转顺序: