Spring+Mybatis动态切换数据源的方法

功能需求是公司要做一个大的运营平台:

1、运营平台有自身的数据库,维护用户、角色、菜单、部分以及权限等基本功能。

2、运营平台还需要提供其他不同服务(服务A,服务B)的后台运营,服务A、服务B的数据库是独立的。

所以,运营平台至少要连三个库:运营库,A库,B库,并且希望达到针对每个功能请求能够自动切换到对应的数据源(我最终实现是针对Service的方法级别进行切换的,也可以实现针对每个DAO层的方法进行切换。我们系统的功能是相互之间比较独立的)。

第一步:配置多数据源

1、定义数据源:

我采用的数据源是阿里的DruidDataSource(用DBCP也行,这个随便)。配置如下:

<!-- op dataSource -->
  <bean id="opDataSource" class="com.alibaba.druid.pool.DruidDataSource"
    init-method="init" destroy-method="close">
    <property name="url" value="${db.master.url}" />
    <property name="username" value="${db.master.user}" />
    <property name="password" value="${db.master.password}" />
    <property name="driverClassName" value="${db.master.driver}" />
    <property name="initialSize" value="5" />
    <property name="maxActive" value="100" />
    <property name="minIdle" value="10" />
    <property name="maxWait" value="60000" />
    <property name="validationQuery" value="SELECT 'x'" />
    <property name="testOnBorrow" value="false" />
    <property name="testOnReturn" value="false" />
    <property name="testWhileIdle" value="true" />
    <property name="timeBetweenEvictionRunsMillis" value="600000" />
    <property name="minEvictableIdleTimeMillis" value="300000" />
    <property name="removeAbandoned" value="true" />
    <property name="removeAbandonedTimeout" value="1800" />
    <property name="logAbandoned" value="true" />
    <!-- 配置监控统计拦截的filters -->
    <property name="filters" value="config,mergeStat,wall,log4j2" />
    <property name="connectionProperties" value="config.decrypt=true" />
  </bean>

  <!-- serverA dataSource -->
  <bean id="serverADataSource" class="com.alibaba.druid.pool.DruidDataSource"
    init-method="init" destroy-method="close">
    <property name="url" value="${db.serverA.master.url}" />
    <property name="username" value="${db.serverA.master.user}" />
    <property name="password" value="${db.serverA.master.password}" />
    <property name="driverClassName" value="${db.serverA.master.driver}" />
    <property name="initialSize" value="5" />
    <property name="maxActive" value="100" />
    <property name="minIdle" value="10" />
    <property name="maxWait" value="60000" />
    <property name="validationQuery" value="SELECT 'x'" />
    <property name="testOnBorrow" value="false" />
    <property name="testOnReturn" value="false" />
    <property name="testWhileIdle" value="true" />
    <property name="timeBetweenEvictionRunsMillis" value="600000" />
    <property name="minEvictableIdleTimeMillis" value="300000" />
    <property name="removeAbandoned" value="true" />
    <property name="removeAbandonedTimeout" value="1800" />
    <property name="logAbandoned" value="true" />
    <!-- 配置监控统计拦截的filters -->
    <property name="filters" value="config,mergeStat,wall,log4j2" />
    <property name="connectionProperties" value="config.decrypt=true" />
  </bean>

  <!-- serverB dataSource -->
  <bean id="serverBDataSource" class="com.alibaba.druid.pool.DruidDataSource"
    init-method="init" destroy-method="close">
    <property name="url" value="${db.serverB.master.url}" />
    <property name="username" value="${db.serverB.master.user}" />
    <property name="password" value="${db.serverB.master.password}" />
    <property name="driverClassName" value="${db.serverB.master.driver}" />
    <property name="initialSize" value="5" />
    <property name="maxActive" value="100" />
    <property name="minIdle" value="10" />
    <property name="maxWait" value="60000" />
    <property name="validationQuery" value="SELECT 'x'" />
    <property name="testOnBorrow" value="false" />
    <property name="testOnReturn" value="false" />
    <property name="testWhileIdle" value="true" />
    <property name="timeBetweenEvictionRunsMillis" value="600000" />
    <property name="minEvictableIdleTimeMillis" value="300000" />
    <property name="removeAbandoned" value="true" />
    <property name="removeAbandonedTimeout" value="1800" />
    <property name="logAbandoned" value="true" />
    <!-- 配置监控统计拦截的filters -->
    <property name="filters" value="config,mergeStat,wall,log4j2" />
    <property name="connectionProperties" value="config.decrypt=true" />
  </bean>

我配置了三个数据源:oPDataSource(运营平台本身的数据源),serverADataSource,serverBDataSource。

2、配置multipleDataSource

multipleDataSource相当于以上三个数据源的一个代理,真正与Spring/Mybatis相结合的时multipleDataSource,和单独配置的DataSource使用没有分别:

<!-- Spring整合Mybatis:配置multipleDatasource -->
  <bean id="sqlSessionFactory"
    class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="multipleDataSource" />
    <!-- 自动扫描Mapping.xml文件 -->
    <property name="mapperLocations">
      <list>
        <value>classpath*:/sqlMapperXml/*.xml</value>
        <value>classpath*:/sqlMapperXml/*/*.xml</value>
      </list>
    </property>
    <property name="configLocation" value="classpath:xml/mybatis-config.xml"></property>
    <property name="typeAliasesPackage" value="com.XXX.platform.model" />
    <property name="globalConfig" ref="globalConfig" />
    <property name="plugins">
      <array>
        <!-- 分页插件配置 -->
        <bean id="paginationInterceptor"
          class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
          <property name="dialectType" value="mysql" />
          <property name="optimizeType" value="aliDruid" />
        </bean>
      </array>
    </property>
  </bean>

  <!-- MyBatis 动态实现 -->
  <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 对Dao 接口动态实现,需要知道接口在哪 -->
    <property name="basePackage" value="com.XXX.platform.mapper" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
  </bean>
  <!-- MP 全局配置 -->
  <bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
    <property name="idType" value="0" />
    <property name="dbColumnUnderline" value="true" />
  </bean>

  <!-- 事务管理配置multipleDataSource -->
  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="multipleDataSource"></property>
  </bean>

了解了multipleDataSource所处的位置之后,接下来重点看下multipleDataSource怎么实现,配置文件如下:

<bean id="multipleDataSource" class="com.xxxx.platform.commons.db.MultipleDataSource">
    <property name="defaultTargetDataSource" ref="opDataSource" />
    <property name="targetDataSources">
      <map>
        <entry key="opDataSource" value-ref="opDataSource" />
        <entry key="serverADataSource" value-ref="serverADataSource" />
        <entry key="serverBDataSource" value-ref="serverBDataSource" />
      </map>
    </property>
  </bean>

实现的Java代码如下,不需要过多的解释,很一目了然:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 *
 * @ClassName: MultipleDataSource
 * @Description: 配置多个数据源<br>
 * @author: yuzhu.peng
 * @date: 2018年1月12日 下午4:37:25
 */
public class MultipleDataSource extends AbstractRoutingDataSource {

  private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();

  public static void setDataSourceKey(String dataSource) {
    dataSourceKey.set(dataSource);
  }

  @Override
  protected Object determineCurrentLookupKey() {
    return dataSourceKey.get();
  }

  public static void removeDataSourceKey() {
    dataSourceKey.remove();
  }
}

继承自spring的AbstractRoutingDataSource,实现抽象方法determineCurrentLookupKey,这个方法会在每次获得数据库连接Connection的时候之前,决定本次连接的数据源Datasource,可以看下Spring的代码就很清晰了:

/*获取连接*/
  public Connection getConnection()
    throws SQLException {
    return determineTargetDataSource().getConnection();
  }

  protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    /*此处的determineCurrentLookupKey为抽象接口,获取具体的数据源名称*/
    Object lookupKey = determineCurrentLookupKey();
    DataSource 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;
  }
  /*抽象接口:也即我们的multipleDataSource实现的接口*/
  protected abstract Object determineCurrentLookupKey();

第二步:每次请求(Service方法级别)动态切换数据源

实现思路是利用Spring的AOP思想,拦截每次的Service方法调用,然后根据方法的整体路径名,动态切换multipleDataSource中的数据的key。我们的项目,针对不同服务也即不同数据库的操作,是彼此之间互相独立的,不太建议在同一个service方法中调用不同的数据源,这样的话需要将动态判断是否需要切换的频次(AOP拦截的频次)放在DAO级别,也就是SQL级别。另外,还不方便进行事务管理。

我们来看动态切换数据源的AOP实现:

import java.lang.reflect.Proxy;

import org.apache.commons.lang.ClassUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;

/**
 * 数据源切换AOP
 *
 * @author yuzhu.peng
 * @since 2018-01-15
 */
@Aspect
@Order(1)
public class MultipleDataSourceInterceptor {
  /**
   * 拦截器对所有的业务实现类请求之前进行数据源切换 特别注意,由于用到了多数据源,Mapper的调用最好只在*ServiceImpl,不然调用到非默认数据源的表时,会报表不存在的异常
   *
   * @param joinPoint
   * @throws Throwable
   */
  @Before("execution(* com.xxxx.platform.service..*.*ServiceImpl.*(..))")
  public void setDataSoruce(JoinPoint joinPoint)
    throws Throwable {
    Class<?> clazz = joinPoint.getTarget().getClass();
    String className = clazz.getName();
    if (ClassUtils.isAssignable(clazz, Proxy.class)) {
      className = joinPoint.getSignature().getDeclaringTypeName();
    }
    // 对类名含有serverA的设置为serverA数据源,否则默认为后台的数据源
    if (className.contains(".serverA.")) {
      MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverA);
    }
    else if (className.contains(".serverB.")) {
      MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverB);
    }
    else {
      MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_OP);
    }
  }

  /**
   * 当操作完成时,释放当前的数据源 如果不释放,频繁点击时会发生数据源冲突,本是另一个数据源的表,结果跑到另外一个数据源去,报表不存在
   *
   * @param joinPoint
   * @throws Throwable
   */
  @After("execution(* com.xxxx.service..*.*ServiceImpl.*(..))")
  public void removeDataSoruce(JoinPoint joinPoint)
    throws Throwable {
    MultipleDataSource.removeDataSourceKey();
  }
}

拦截所有的ServiceImpl方法,根据方法的全限定名去判断属于那个数据源的功能,然后选择相应的数据源,发放执行完后,释放当前的数据源。注意我用到了Spring的 @Order,注解,接下来会讲到,当定义多个AOP的时候,order是很有用的。

其他:

一开始项目中并没有引入事务,所以一切都OK,每次都能访问到正确的数据源,当加入SPring的事务管理后,不能动态切换数据源了(也好像是事务没有生效,反正是二者没有同时有效),后来发现原因是AOP的执行顺序问题,所以用到了上边提到的SPring的Order:

order越小,先被执行。至此,既可以动态切换数据源,又可以成功用事务(在同一个数据源)。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例
  • 详解Spring Boot整合Mybatis实现 Druid多数据源配置
  • springboot + mybatis配置多数据源示例
  • Spring+MyBatis多数据源配置实现示例
  • Spring与Mybatis相结合实现多数据源切换功能
  • spring mybatis多数据源实例详解
  • 详解SpringBoot和Mybatis配置多数据源
  • Spring3 整合MyBatis3 配置多数据源动态选择SqlSessionFactory详细教程
  • Spring + Mybatis 项目实现动态切换数据源实例详解
  • Spring MVC Mybatis多数据源的使用实例解析
(0)

相关推荐

  • 详解Spring Boot整合Mybatis实现 Druid多数据源配置

    一.多数据源的应用场景 目前,业界流行的数据操作框架是 Mybatis,那 Druid 是什么呢? Druid 是 Java 的数据库连接池组件.Druid 能够提供强大的监控和扩展功能.比如可以监控 SQL ,在监控业务可以查询慢查询 SQL 列表等.Druid 核心主要包括三部分: 1. DruidDriver 代理 Driver,能够提供基于 Filter-Chain 模式的插件体系. 2. DruidDataSource 高效可管理的数据库连接池 3. SQLParser 当业务数据量达

  • Spring MVC Mybatis多数据源的使用实例解析

    项目需要从其他网站获取数据,因为是临时加的需求,在开始项目时没想到需要多数据源 于是百度了一下,发现只需要改动一下Spring 的applicationContext.xml文件和编写三个工具类就可以完美实现 applicationContext.xml <!-- 多数据源配置 --> <bean id="ds1" class="org.apache.commons.dbcp.BasicDataSource"> <property na

  • Spring+MyBatis多数据源配置实现示例

    最近用到了MyBatis配置多数据源,原以为简单配置下就行了,实际操作后发现还是要费些事的,这里记录下,以作备忘 不多废话,直接上代码,后面会有简单的实现介绍 jdbc和log4j的配置 #定义输出格式 ConversionPattern=%d %-5p [%t] %c - %m%n log4j.rootLogger=DEBUG,Console log4j.logger.com.cnblogs.lzrabbit=DEBUG log4j.logger.org.springframework=ERR

  • Spring + Mybatis 项目实现动态切换数据源实例详解

    项目背景:项目开发中数据库使用了读写分离,所有查询语句走从库,除此之外走主库. 最简单的办法其实就是建两个包,把之前数据源那一套配置copy一份,指向另外的包,但是这样扩展很有限,所有采用下面的办法. 参考了两篇文章如下: http://www.jb51.net/article/111840.htm http://www.jb51.net/article/111842.htm 这两篇文章都对原理进行了分析,下面只写自己的实现过程其他不再叙述. 实现思路是: 第一步,实现动态切换数据源:配置两个D

  • spring mybatis多数据源实例详解

    同一个项目有时会涉及到多个数据库,也就是多数据源.多数据源又可以分为两种情况: 1)两个或多个数据库没有相关性,各自独立,其实这种可以作为两个项目来开发.比如在游戏开发中一个数据库是平台数据库,其它还有平台下的游戏对应的数据库: 2)两个或多个数据库是master-slave的关系,比如有mysql搭建一个 master-master,其后又带有多个slave:或者采用MHA搭建的master-slave复制: 目前我所知道的 Spring 多数据源的搭建大概有两种方式,可以根据多数据源的情况进

  • Spring与Mybatis相结合实现多数据源切换功能

    废话不多说,关键代码如下所示: 1. 代码: DbContextHolder public class DbContextHolder { //线程安全的ThreadLocal private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static void setDbType(String dbType) { contextHolder.set(dbType

  • Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例

    本文将介绍使用Spring Boot集成Mybatis并实现主从库分离的实现(同样适用于多数据源).延续之前的Spring Boot 集成MyBatis.项目还将集成分页插件PageHelper.通用Mapper以及Druid. 新建一个Maven项目,最终项目结构如下: 多数据源注入到sqlSessionFactory POM增加如下依赖: <!--JSON--> <dependency> <groupId>com.fasterxml.jackson.core<

  • Spring3 整合MyBatis3 配置多数据源动态选择SqlSessionFactory详细教程

    一.摘要 这篇文章将介绍Spring整合Mybatis 如何完成SqlSessionFactory的动态切换的.并且会简单的介绍下MyBatis整合Spring中的官方的相关代码. Spring整合MyBatis切换SqlSessionFactory有两种方法 第一. 继承SqlSessionDaoSupport,重写获取SqlSessionFactory的方法. 第二.继承SqlSessionTemplate 重写getSqlSessionFactory.getConfiguration和Sq

  • springboot + mybatis配置多数据源示例

    在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1)DatabaseType列出所有的数据源的key---key 2)DatabaseContextHolder是一个线程安全的DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法 3)DynamicDataSource继承AbstractRoutingDataSource并重写其中的方法determineCurrentLookupKey(),在该方法中使用Da

  • 详解SpringBoot和Mybatis配置多数据源

    目前业界操作数据库的框架一般是 Mybatis,但在很多业务场景下,我们需要在一个工程里配置多个数据源来实现业务逻辑.在SpringBoot中也可以实现多数据源并配合Mybatis框架编写xml文件来执行SQL.在SpringBoot中,配置多数据源的方式十分便捷, 下面开始上代码: 在pom.xml文件中需要添加一些依赖 <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.b

随机推荐