【Mybatis】MyBatis之配置多数据源(十)
来源于 https://www.cnblogs.com/h--d/p/11304082.html
在做项目的过程中,有时候一个数据源是不够,那么就需要配置多个数据源。本例介绍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


浙公网安备 33010602011771号