MyBatis配置多数据源

MyBatis配置多数据源

在做项目的过程中,有时候一个数据源是不够,那么就需要配置多个数据源。本例介绍mybatis多数据源配置。

前言

一般项目单数据源,使用流程如下:

单个数据源绑定给sessionFactory,再在Dao层操作,若多个数据源的话,那不是就成了下图
MyBatis配置多数据源_第1张图片
可见,sessionFactory都写死在了Dao层,若我再添加个数据源的话,则又得添加一个sessionFactory。所以比较好的做法应该是下图
MyBatis配置多数据源_第2张图片

实现原理

  1. 扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上)
    从AbstractRoutingDataSource的源码中:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
  1. 我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:
public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
}

public Connection getConnection(String username, String password) throws SQLException {
     return determineTargetDataSource().getConnection(username, password);
}
  1. 获取连接的方法中,重点是determineTargetDataSource()方法,看源码:
/**
     * 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. 搭建一个Springmvc + Spring + Mybatis maven项目,POM文件中引入AOP相关依赖
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
  1. 编辑一个扩展AbstractRoutingDataSource类,DynamicDataSource.java
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();
    }

}
  1. 封装一个的对数据源进行操作的类,DataSourceHolder.java
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();
    }
}
  1. 当需要切换数据源的时候执行啦。手动在代码中调用写死吗?调用setDataSource方法
      但是这种方法比较死板,所以我们可以应用spring aop来设置,把配置的数据源类型都设置成为注解标签,在service层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源咯(就跟你设置事务一样)
@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";

}
  1. 编辑切面的Bean,DataSourceExchange.java
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());
            }
        }

    }
}
  1. 配置文件

<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"/>
  1. 在service上加上注解即可使用
@Transactional
@TargetDataSource(name=TargetDataSource.SLAVE)
public int addEmployeeFromSalve(Employee employee) {  
   return employeeMapper.insert(employee);
}

数据流转顺序:

  1. xml拦截到数据源名称
  2. 执行切面DataSourceExchange中的before方法,将数据源名称放入 DataSourceHolder中
  3. Spring 调用determineCurrentLookupKey()方法 ,从DataSourceHolder取出当前的数据库名称,并返回
  4. AbstractRoutingDataSource类中determineTargetDataSource()方法调用determineCurrentLookupKey()匹配到指定的数据库,并建立链接,即为切换到相应的数据库;
  5. 在指定的数据库中执行相应的sql

你可能感兴趣的:(mybatis,java)