Spring AOP如何实现注解式的Mybatis多数据源切换详解

一、为什么要使用多数据源切换?

多数据源切换是为了满足什么业务场景?正常情况下,一个微服务或者说一个WEB项目,在使用Mybatis作为数据库链接和操作框架的情况下通常只需要构建一个系统库,在该系统库创建业务表来满足需求,当然也有分为测试库和正式库dev/prod,不过这俩库的切换是使用配置文件进行切分的,在项目启动时或者打成maven JAR包指定environment-dev.properties或者environment-prod.properties。

那么当程序运行过程中,比如一个controller中既需要查询数据库A,又需要查询数据库B,而且两者都希望用entity(Mybatis中用于与表结构保持一直的bean)来接收查询结果,即都希望走Mybatis的entity-mapper-mapper.xml这么一套框架。这个时候最原始的方法是在代码中手动链接数据库比如:

 var conn:Connection = null
 try {
 Class.forName("com.mysql.jdbc.Driver")
 conn = DriverManager.getConnection("url","username","password")
 val statement = conn.createStatement()
 val result = statement.executeQuery("select * from **** where **** ")
 while(result.next()){
 }
 }

本文所采用的是修改dao层context配置文件添加基于Spring事务和AOP方式的注解式数据源切换。最终实现的效果如下:

 @Transactional //该注解表明该Service类开启Spring事务,事务的意思是指具有原子性的一个操作集合(本人理解),该事务做什么事在dao层的配置文件里配置,后面会讲。

 @Service //表明为Service类,使用Component也行,Spring在启动时会扫描该类将该类所需要的bean全部构建出来以供使用

 @TargetDataSource(name = "dataSource1") //重点,自定义的AOP注解,指定该TestService1类下的所有public方法都使用数据源dataSource1
 class TestService1{
 public void queryAllUser(){
  UserMapper userMapper = new UserMapper()
  userMapper.queryAllUser();
  System.out.println("使用数据源dataSource1查询用户信息")
 }
 }

 @Transactional
 @Service
 @TargetDataSource(name = "dataSource2")
 class TestService2{

 public void queryAllBook(){
  BookMapper bookMapper = new BookMapper()
  bookMapper.queryAllBook();
  System.out.println("使用数据源dataSource2查询书籍信息")
 }
 }

在每一个需要切换数据源的Service层使用TargetDataSource(name= “***”)即可指定当前线程的数据源,当然别忘记@Transactional事务的添加,该事务用于Mybatis查询数据时去获取当前线程的数据源为哪一个。如此在controller中正常调用Service中的方法就行了,如果需要查询两个数据库那么分别调用两个TestService中的方法即可。比如:

 //本人目前使用scala语言作为开发语言,Java没怎么写了,还是习惯Scala,以下程序还是使用Scala语言规范哈

 class testController{
 @AutoWired
 TestService1 testService1;
 @AutoWired
 TestService2 testService2;
 @RequestMapping(value = Array("/test"), produces = Array("application/json;charset=UTF-8"), method = Array(RequestMethod.GET))
  def test(): Unit = {
  val allUser = testService1.queryAllUser()
  println("使用TestService1查询数据源1中的所有用户")
  val allBook = testService2.queryAllBook("33287")
  println("使用TestService2查询数据源2中的所有书籍信息")
  }
 }

二、如何实现

接下来就详细讲述如何在Spring MVC和Mybatis的单套数据源支持上扩展多数据源切换能力。以下为双数据源,三数据源的实现方式相同。

1.首先在配置文件中添加第二个数据源的链接信息。

 environment-dev.properties
 #数据源1的链接信息
 db1.jdbc.username=xxx
 db1.jdbc.password=xxxxx
 db1.jdbc.driverClassName=com.mysql.jdbc.Driver
 db1.jdbc.url=xxxx?useUnicode=true&characterEncoding=utf8

 #新添加的数据源2的链接信息
 db2.jdbc.username=xxx
 db2.jdbc.password=xxxxx
 db2.jdbc.driverClassName=com.mysql.jdbc.Driver
 db2.jdbc.url=xxxx?useUnicode=true&characterEncoding=utf8

2.在dao层的context.xml配置文件中添加基于注解的事务管理以及AOP切面配置

(1)在配置文件中添加双数据源,如下:

 <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="driverClassName" value="${db1.jdbc.driverClassName}"/>
  <property name="password" value="${db1.jdbc.password}"/>
  <property name="username" value="${db1.jdbc.username}"/>
  <property name="url" value="${db1.jdbc.url}"/>
  <property name="initialSize" value="5"/>
  <property name="maxActive" value="10"/>
 </bean>

 <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="driverClassName" value="${db2.jdbc.driverClassName}"/>
  <property name="password" value="${db2.jdbc.password}"/>
  <property name="username" value="${db2.jdbc.username}"/>
  <property name="url" value="${db2.jdbc.url}"/>
  <property name="initialSize" value="5"/>
  <property name="maxActive" value="10"/>
 </bean>

(2)使用AbstractRoutingDataSource实现动态数据源选择

配置文件中添加

 <bean id="dataSource" class="common.dao.mysql.dataSourceManage.DynamicDataSource">
  <property name="targetDataSources">
  <map key-type="java.lang.String">
  <entry key="dataSource1" value-ref="dataSource1" />
  <entry key="dataSource2" value-ref="dataSource2" />
  </map>
  </property>
  <!-- 默认使用dataSource1的数据源 -->
  <property name="defaultTargetDataSource" ref="dataSource1" />
 </bean>

在dao层创建dataSourceManage包,在包中创建如下类DynamicDataSource,DataSourceHolder。

类一:

 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 public class DynamicDataSource extends AbstractRoutingDataSource {
 @Override
 protected Object determineCurrentLookupKey() {
  return DataSourceHolder.getDataSoure();
 }
 }

类二:

 public class DataSourceHolder {

 //线程本地环境
 private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();

 //设置数据源
 public static void setDataSource(String customerType) {
  dataSources.set(customerType);
 }

 //获取数据源
 public static String getDataSoure() {
  return (String) dataSources.get();
 }

 //清除数据源
 public static void clearDataSource() {
  dataSources.remove();
 }
 }

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。以上完成数据库操作之前的数据源选择,使用的是DataSourceHolder.getDataSoure();

(3)添加Spring事务,确定在业务代码中查询数据库时,由Spring事务去执行以上对数据源的选择,这样既不影响业务代码又能提供事务的性质保证。

在配置文件中添加

 <!-- 定义事务管理器(声明式的事务) -->
 <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 <property name="dataSource" ref="dataSource" />
 </bean>
 <!-- 将所有具有@Transactional注解的Bean自动配置为声明式事务支持 -->
 <tx:annotation-driven transaction-manager="dataSourceTransactionManager" />

 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource"/>
 <property name="mapperLocations">
 <list>
  <value>classpath:common/dao/mysql/mapper/*Mapper.xml</value>
 </list>
 </property>
 </bean>

注意配置sqlSessionFactory中使用的数据源需要和事务配置中的保持一直。以及配置文件的顶层bean需要添加 xmlns:tx="http://www.springframework.org/schema/tx"和xsi:schemaLocation中添加http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd

(4)配置AOP提供Service层注解式声明使用的数据源

首先在配置文件中添加AOP支持xmlns:aop="http://www.springframework.org/schema/aop",xsi:schemaLocation中添加http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd

 <!--配置切面的bean DataSourceExchange 自定义的切面类实现数据源切换-->
 <bean id="dataSourceExchange" class="common.dao.mysql.datasource.DataSourceExchange" />
 <!--配置AOP -->
 <aop:config>
  <!--配置切点表达式 定义dataSourceExchange中的拦截使用范围-->
  <aop:pointcut id="servicePointcut" expression="execution(* common.dao.mysql.service.*.*(..))"/>
  <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1" />
 </aop:config>

其中execution(* common.dao.mysql.service.*.*(..))为service下的所有类(指TestService1和TestService2)的所有public方法都加上切面代理即使用dataSourceExchange处理。

然后在dataSourceManage包下创建DataSourceExchange类实现AfterReturningAdvice,MethodBeforeAdvice两个aop通知

 import java.lang.reflect.Method;
 import org.springframework.aop.AfterReturningAdvice;
 import org.springframework.aop.MethodBeforeAdvice;

 public class DataSourceExchange implements MethodBeforeAdvice, AfterReturningAdvice {

 @Override
 public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
  DataSourceHolder.clearDataSource();
 }

 @Override
 public void before(Method method, Object[] objects, Object o) throws Throwable {

  //这里TargetDataSource是自定义注解,method为查询数据库的方法比如一中的queryAllUser(),Objects为传给该方法的参数数组,o为调用该方法的对象,比如val allUser =
  //testService1.queryAllUser()中的testService1
  if (method.isAnnotationPresent(TargetDataSource.class)) {
  TargetDataSource dataSource = method.getAnnotation(TargetDataSource.class);
  DataSourceHolder.setDataSource(dataSource.name());
  } else {
  if (o.getClass().isAnnotationPresent(TargetDataSource.class)) {
   TargetDataSource dataSource = o.getClass().getAnnotation(TargetDataSource.class);
   DataSourceHolder.setDataSource(dataSource.name());
  }
  }
 }
 }

然后在dataSourceManage包下创建TargetDataSource注解类

 import java.lang.annotation.*;

 @Target({ElementType.METHOD, ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface TargetDataSource {
 String name() default "dataSource1";
 }

以上配置完成之后即可达成一中的最终效果。

完整的dao配置文件内容如下

 <beans
  xmlns="http://www.springframework.org/schema/beans"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop
    https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd ">

 <context:annotation-config/>
 <context:component-scan base-package="com.test.common.dao"/>
 <bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource">
   <property name="driverClassName" value="${db1.jdbc.driverClassName}"/>
   <property name="password" value="${db1.jdbc.password}"/>
   <property name="username" value="${db1.jdbc.username}"/>
   <property name="url" value="${db1.jdbc.url}"/>
   <property name="initialSize" value="5"/>
   <property name="maxActive" value="10"/>
 </bean>

 <bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="driverClassName" value="${db2.jdbc.driverClassName}"/>
  <property name="password" value="${db2.jdbc.password}"/>
  <property name="username" value="${db2.jdbc.username}"/>
  <property name="url" value="${db2.jdbc.url}"/>
  <property name="initialSize" value="5"/>
  <property name="maxActive" value="10"/>
 </bean>

 <bean id="dataSource" class="test.common.dao.mysql.dataSourceManage.DynamicDataSource">
  <property name="targetDataSources">
   <map key-type="java.lang.String">
   <entry key="dataSource1" value-ref="dataSource1" />
   <entry key="dataSource2" value-ref="dataSource2" />
   </map>
  </property>
   <!-- 默认使用dataSource1的数据源 -->
  <property name="defaultTargetDataSource" ref="dataSource1" />
 </bean>

 <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
 </bean>
 <tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource"/>
 <property name="mapperLocations">
  <list>
  <value>classpath:test/common/dao/mysql/mapper/*Mapper.xml</value>
  </list>
 </property>
 </bean>

 <!--配置可以批量执行的sqlSession -->
 <!--配置切面的bean -->
 <bean id="dataSourceExchange" class="test.common.dao.mysql.datasource.DataSourceExchange" />
 <!--配置AOP -->
 <aop:config>
 <!--配置切点表达式 -->
  <aop:pointcut id="servicePointcut" expression="execution(* test.common.dao.mysql.service.*.*(..))"/>
  <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1" />
 </aop:config>

 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 <property name="basePackage" value="test.common.dao"/>
 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
 </bean>
 </beans>

到此这篇关于Spring AOP如何实现注解式的Mybatis多数据源切换的文章就介绍到这了,更多相关Spring AOP注解式的Mybatis多数据源切换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring Boot + Mybatis多数据源和动态数据源配置方法

    网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种方式,记录一下配置方法供大家参考. 应用场景 项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库. 多数据源 首先要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源.在@SpringBootApplication注解中添加ex

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

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

  • 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+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 Boot整合Mybatis实现 Druid多数据源配置

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

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

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

  • Spring Boot 整合mybatis 使用多数据源的实现方法

    前言 本篇教程偏向实战,程序猿直接copy代码加入到自己的项目中做简单的修修改改便可使用,而对于springboot以及mybatis不在此进行展开介绍,如有读者希望了解可以给我留言,并持续关注,我后续会慢慢更新.(黑色区域代码部分,安卓手机可手动向左滑动,来查看全部代码) 整合 其实整合很简单,如果是用gradle的话,在build.gradle文件里加入 compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1')

  • Spring Boot+Mybatis+Druid+PageHelper实现多数据源并分页的方法

    前言 本篇文章主要讲述的是SpringBoot整合Mybatis.Druid和PageHelper 并实现多数据源和分页.其中SpringBoot整合Mybatis这块,在之前的的一篇文章中已经讲述了,这里就不过多说明了.重点是讲述在多数据源下的如何配置使用Druid和PageHelper . Druid介绍和使用 在使用Druid之前,先来简单的了解下Druid. Druid是一个数据库连接池.Druid可以说是目前最好的数据库连接池!因其优秀的功能.性能和扩展性方面,深受开发人员的青睐. D

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

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

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

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

随机推荐