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

多数据源切换是为了满足什么业务场景?正常情况下,一个微服务或者说一个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多数据源切换的更多相关文章

  1. 利用Spring AOP自定义注解解决日志和签名校验

    转载:http://www.cnblogs.com/shipengzhi/articles/2716004.html 一.需解决的问题 部分API有签名参数(signature),Passport首先 ...

  2. (转)利用Spring AOP自定义注解解决日志和签名校验

    一.需解决的问题 部分API有签名参数(signature),Passport首先对签名进行校验,校验通过才会执行实现方法. 第一种实现方式(Origin):在需要签名校验的接口里写校验的代码,例如: ...

  3. spring aop 使用注解方式总结

    spring aop的注解方式:和xml的配置方式略有区别,详细如下: 1.首先还是建立需要的切面类:切面类里面定义好切点配置,以及所有的需要实现的通知方法. /** * */ package com ...

  4. spring AOP自定义注解方式实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  5. spring AOP自定义注解 实现日志管理

    今天继续实现AOP,到这里我个人认为是最灵活,可扩展的方式了,就拿日志管理来说,用Spring AOP 自定义注解形式实现日志管理.废话不多说,直接开始!!! 关于配置我还是的再说一遍. 在appli ...

  6. spring mvc+mybatis+多数据源切换

    spring mvc+mybatis+多数据源切换,选取oracle,mysql作为例子切换数据源.oracle为默认数据源,在测试的action中,进行mysql和oracle的动态切换. web. ...

  7. Spring Boot 2.X(五):MyBatis 多数据源配置

    前言 MyBatis 多数据源配置,最近在项目建设中,需要在原有系统上扩展一个新的业务模块,特意将数据库分库,以便减少复杂度.本文直接以简单的代码示例,如何对 MyBatis 多数据源配置. 准备 创 ...

  8. 总结切面编程AOP的注解式开发和XML式开发

    有段日子没有总结东西了,因为最近确实有点忙,一直在忙于hadoop集群的搭建,磕磕碰碰现在勉强算是能呼吸了,因为这都是在自己的PC上,资源确实有点紧张(搭建过程后期奉上),今天难得大家都有空(哈哈哈~ ...

  9. Spring AOP的注解方式实现

    spring也支持注解方式实现AOP,相对于配置文件方式,注解配置更加的轻量级,配置.修改更加方便. 1.开启AOP的注解配置方式 <!-- 开启aop属性注解 --> <aop:a ...

随机推荐

  1. 086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结

    086 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 03 面向对象基础总结 01 面向对象基础(类和对象)总结 本文知识点:面向对象基础(类和对象)总结 说明 ...

  2. Unicode、UTF8、GB2312、ANSI

    来源:https://blog.csdn.net/osanwenyu/article/details/48439461 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原 ...

  3. MATLAB中exist函数的用法

    exist:exist主要有两种形式,一个参数和两个参数的,作用都是用于确定某值是否存在:1. b = exist( a)      若 a 存在,则 b = 1: 否则 b = 0:2. b = e ...

  4. C语言编程入门之--第六章C语言控制语句

    导读:本章带读者理解什么是控制语句,然后逐个讲解C语言常用的控制语句,含有控制语句的代码量多起来后就要注意写代码的风格了,本章末节都是练习题,大量的练习才能掌握好控制语句的使用. 6.1 什么是控制语 ...

  5. 带UI 的小初高数学学习系统 —结对编程项目总结

    一. 项目综述 本系统是基于QT Creator 4.3.0开发环境,开发语言C++,能够实现用户注册,发送短信验证码,用户登陆,用户选择题目类型和数量,显示用户本次答题基本功能.支持对用户账号查重, ...

  6. C#数据结构-静态链表

    对于双向链表中的节点,都包括一个向前.向后的属性器用于指向前后两个节点,对于引用类型,对象存储的是指向内存片段的内存指针,那么我们可以将其简化看作向前向后的两个指针. 现在我们将引用类型替换为值类型i ...

  7. C# OOP编程

    1:面向对象的概念:什么是类.对象.以及类与对象的关系. 面向对象三大特征: 封装/继承/多台 2:封装性: 用访问修饰符来体现封装性. Public 公共的/ private 私有的/Protect ...

  8. Python+Appium自动化测试(2)-appium连接真机启动app

    app自动化测试的第一步,是启动被测app.appium环境搭建好后,我们就可以连接真机启动app了.环境为windows,Appium1.18.0,Android手机,被测app为今日头条app,让 ...

  9. 2016年 实验二、C2C模拟实验

    实验二.C2C模拟实验 [实验目的] 掌握网上购物的基本流程和C2C平台的运营 [实验条件] ⑴.个人计算机一台 ⑵.计算机通过局域网形式接入互联网. (3).奥派电子商务应用软件 [知识准备] 本实 ...

  10. Request对象基础应用实例代码一

    输入用户名:<br><input type="text" name="yhm"><br><br>输入密码:< ...