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

前言

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

    

  单个数据源绑定给sessionFactory,再在Dao层操作,若多个数据源的话,那不是就成了下图

    

  可见,sessionFactory都写死在了Dao层,若我再添加个数据源的话,则又得添加一个sessionFactory。所以比较好的做法应该是下图

    

实现原理

   1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)

     从AbstractRoutingDataSource的源码中:

 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

  2、我们可以看到,它继承了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);
}

  3、 获取连接的方法中,重点是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相关依赖,参考:【Mybatis】MyBatis之整合Spring(八)

 <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>test-spring-mybatis</artifactId>
<packaging>war</packaging>
<version>1.0.0-SNAPSHOT</version>
<url>http://maven.apache.org</url> <!-- 定义maven变量 -->
<properties>
<!-- spring -->
<spring.version>5.1.4.RELEASE</spring.version> <!-- Mybatis -->
<mybatis.version>3.5.0</mybatis.version>
<!-- Mybatis 整合 Spring -->
<mybatis-spring.version>2.0.0</mybatis-spring.version> <!-- mysql -->
<mysql.version>8.0.13</mysql.version> <!-- c3p0 连接池 -->
<c3p0.version>0.9.5.4</c3p0.version> <!-- logback -->
<slf4j-api.version>1.7.5</slf4j-api.version>
<logback.version>0.9.30</logback.version> <!-- Servlet -->
<servlet.version>3.0.1</servlet.version>
<jsp-api.version>2.2</jsp-api.version> <!-- jstl -->
<jstl.version>1.2</jstl.version>
<standard.version>1.1.2</standard.version> <!-- test junit -->
<junit.version>3.8.1</junit.version> <!-- jdk -->
<jdk.version>1.8</jdk.version>
<maven.compiler.plugin.version>2.3.2</maven.compiler.plugin.version>
</properties> <dependencies> <!-- Spring IOC 核心容器 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency> <!-- Spring AOP 切面 模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency> <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.2</version>
</dependency> <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency> <!-- Spring WEB MVC 模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency> <!-- Spring 事物 模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency> <!-- Spring ORM 对象关系映射 模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency> <!-- Spring JDBC 模块 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency> <!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency> <!-- Mybatis 整合 Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency> <!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency> <!-- c3p0 连接池 -->
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency> <!-- logback -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j-api.version}</version>
<type>jar</type>
<scope>compile</scope>
</dependency> <dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
<type>jar</type>
</dependency> <dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<type>jar</type>
</dependency> <dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency> <!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>${jsp-api.version}</version>
<scope>provided</scope>
</dependency> <!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency> <dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>${standard.version}</version>
</dependency> <!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency> </dependencies> <build>
<plugins>
<!-- define the project compile level -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.plugin.version}</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
</plugins>
<finalName>test_spring_mybatis</finalName>
</build>
</project>

pom.xml

  2、编辑一个扩展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();
} }

  3、 封装一个的对数据源进行操作的类,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();
}
}

  4、当需要切换数据源的时候执行啦。手动在代码中调用写死吗?调用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"; }

  5、编辑切面的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());
}
} }

  6、配置文件

 <?xml version="1.0" encoding="UTF-8"?>
<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> <!-- 数据源:Spring用来控制业务逻辑。数据源、事务控制、aop -->
<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> <!-- spring事务管理 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean> <!-- 开启基于注解的事务 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" order="2"/> <!--
整合mybatis
目的:1、spring管理所有组件。mapper的实现类。
service==>Dao @Autowired:自动注入mapper;
2、spring用来管理事务,spring声明式事务
-->
<!--创建出SqlSessionFactory对象 -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- configLocation指定全局配置文件的位置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!--mapperLocations: 指定mapper文件的位置-->
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>
</bean> <!--配置一个可以进行批量执行的sqlSession -->
<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> <!-- 扫描所有的mapper接口的实现,让这些mapper能够自动注入;
base-package:指定mapper接口的包名
-->
<mybatis-spring:scan base-package="com.test.dao"/> <!-- 配置切面的Bean -->
<bean id="dataSourceExchange" class="com.test.datasource.DataSourceExchange"/> <!-- 配置AOP -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut id="servicePointcut" expression="execution(* com.test.service.*.*(..))"/>
<!-- 关键配置,切换数据源一定要比持久层代码更先执行(事务也算持久层代码) <aop:advisor advice-ref="txAdvice" pointcut-ref="service" order="2"/> -->
<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"/>

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

 @Transactional
@TargetDataSource(name=TargetDataSource.SLAVE)
public int addEmployeeFromSalve(Employee employee) { return employeeMapper.insert(employee);
}

   

  

数据流转顺序:

  1.xml<aop>拦截到数据源名称

  2.执行切面DataSourceExchange中的before方法,将数据源名称放入 DataSourceHolder中

  3.Spring 调用determineCurrentLookupKey()方法<DynamicDataSource中重写AbstractRoutingDataSource类中的方法> ,从DataSourceHolder取出当前的数据库名称,并返回

  4.AbstractRoutingDataSource类中determineTargetDataSource()方法调用determineCurrentLookupKey()匹配到指定的数据库,并建立链接,即为切换到相应的数据库;

  5.在指定的数据库中执行相应的sql

  

【Mybatis】MyBatis之配置多数据源(十)的更多相关文章

  1. springboot+mybatis +yml文件配置多数据源

    记录一下java开发中多数据源的配置过程, 参考博客:https://blog.csdn.net/weinichendian/article/details/72903757,我在这里进行了整理,使用 ...

  2. mybatis数据库基本配置包括数据源事物类型等

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC ...

  3. 【Mybatis】MyBatis之配置自定义数据源(十一)

    本例是在[Mybatis]MyBatis之配置多数据源(十)的基础上进行拓展,查看本例请先学习第十章 实现原理 1.扩展Spring的AbstractRoutingDataSource抽象类(该类充当 ...

  4. mybatis源码解读(三)——数据源的配置

    在mybatis-configuration.xml 文件中,我们进行了如下的配置: <!-- 可以配置多个运行环境,但是每个 SqlSessionFactory 实例只能选择一个运行环境常用: ...

  5. spring+myBatis 配置多数据源,切换数据源

    注:本文来源于  tianzhiwuqis <spring+myBatis 配置多数据源,切换数据源> 一个项目里一般情况下只会使用到一个数据库,但有的需求是要显示其他数据库的内容,像这样 ...

  6. SpringBoot系列七:SpringBoot 整合 MyBatis(配置 druid 数据源、配置 MyBatis、事务控制、druid 监控)

    1.概念:SpringBoot 整合 MyBatis 2.背景 SpringBoot 得到最终效果是一个简化到极致的 WEB 开发,但是只要牵扯到 WEB 开发,就绝对不可能缺少数据层操作,所有的开发 ...

  7. [教程] Spring+Mybatis环境配置多数据源

    一.简要概述 在做项目的时候遇到需要从两个数据源获取数据,项目使用的Spring + Mybatis环境,看到网上有一些关于多数据源的配置,自己也整理学习一下,然后自动切换实现从不同的数据源获取数据功 ...

  8. Spring Boot + MyBatis + Pagehelper 配置多数据源

    前言: 本文为springboot结合mybatis配置多数据源,在项目当中很多情况是使用主从数据源来读写分离,还有就是操作多库,本文介绍如何一个项目同时使用2个数据源. 也希望大家带着思考去学习!博 ...

  9. spring配置多数据源——mybatis

    这篇文章是配置mybatis多数据源文章,如果是hibernate的话也是没什么影响,配置都是差不多的. 在这家公司上班差不多一星期了,不小心点开配置文件一看这项目配置了两个数据源,蒙了. 之后上网查 ...

随机推荐

  1. hive函数之数学函数

    hive函数之数学函数   round(double d)--返回double型d的近似值(四舍五入),返回bigint型: round(double d,int n)--返回保留double型d的n ...

  2. .net序列化 - Newtonsoft(Json.Net)简单应用

    Newtonsoft.Json,一款.NET中开源的Json序列化和反序列化类库. Json.Net是一个读写Json效率比较高的.Net框架.Json.Net 使得在.Net环境下使用Json更加简 ...

  3. C++异常处理(一)----基本语法

    实验环境 win7 下的vs2017,基本原则:throw抛出的数据类型,和cathc语句的数据类型要一致 异常的引发和异常的处理可以分布在不同函数中,所以c++的异常是跨栈的 异常是由“地地道道“的 ...

  4. com.alibaba.fastjson把JSONObject转换为Map<String, String>对象

    https://www.cnblogs.com/fomeiherz/p/6351287.html JSONObject obj = new JSONObject();{obj.put("ke ...

  5. [GCP] Goolge compute Engine

    Which of the following is a PAAS option for hosting web apps on GCP? App Engine standard or flexible ...

  6. Storm 安装时 部分supervisor启动成功,并不在web ui上显示

    今天帮公司搭建集群时,发现启动了三个Supervisor 发现只有一个显示在Web UI 上. 于是我就简单地检查了下另外两台没有启动的 storm supervisor的日志, 发现没有报出什么异常 ...

  7. Kylin 1 背景、历史与使命

    1.1 背景和历史 今天,大数据领域的发展如火如荼,各种新技术层出不穷,整个生态欣欣向荣.作为大数据领域最重要的技术——Apache Hadoop,从诞生至今已有10周年.它最初只是致力于简单的分布式 ...

  8. mysql 运算操作符

    Name Description AND, && Logical AND = Assign a value (as part of a SET statement, or as par ...

  9. macos high sierra 删除多余的管理员的步骤

    自己查看了好多文档, 一个比较可靠的地址有两个,不过发现跟我的不一样, 我没有采用. 也在这里贴出来, 供需要的"折腾者"们看看. [某个外国大大的方法] (https://med ...

  10. 《挑战30天C++入门极限》C++运算符重载函数基础及其值返回状态

        C++运算符重载函数基础及其值返回状态 运算符重载是C++的重要组成部分,它可以让程序更加的简单易懂,简单的运算符使用可以使复杂函数的理解更直观. 对于普通对象来说我们很自然的会频繁使用算数运 ...