Spring AOP implements mybatis multi data source switching with annotation

mapleJia 2020-11-08 11:21:18
spring aop implements mybatis multi


One 、 Why use multiple data source switching ?
What business scenarios are multiple data sources switching for ? Under normal circumstances , A microservice or WEB project , In the use of Mybatis As a database link and operation framework, we usually only need to build a system library , Create business tables in the system library to meet the requirements , Of course, there are also test libraries and formal libraries dev/prod, However, the switch between the two libraries is based on the configuration file , At the start of the project or type it as maven JAR The package specifies environment-dev.properties perhaps environment-prod.properties.
So when the program is running , For example, a controller You need to query the database A, Also need to query the database B, And both want to use entity(Mybatis Used to keep the table structure constant bean) To receive the query results , They all want to go Mybatis Of entity-mapper-mapper.xml Such a framework . At this time, the most primitive method is to manually link the database in the code, such as :

 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()){
}
}
This paper adopts the modification of dao layer context Configuration files are added based on Spring Business and AOP Mode annotation data source switch . The final effect is as follows :
@Transactional // The note indicates that Service Class open Spring Business , Transaction means a set of operations that are atomic ( I understand ), What does the business do in dao Layer configuration file , The back can speak .
@Service // Show as Service class , Use Component It's OK ,Spring At startup, the class is scanned for the required bean All built for use
@TargetDataSource(name = "dataSource1") // a key , Self defined AOP annotation , Specify the TestService1 All under the class public Methods use data sources dataSource1
class TestService1{
public void queryAllUser(){
UserMapper userMapper = new UserMapper()
userMapper.queryAllUser();
System.out.println(" Using data sources dataSource1 Query user information ")
}
}
@Transactional
@Service
@TargetDataSource(name = "dataSource2")
class TestService2{
public void queryAllBook(){
BookMapper bookMapper = new BookMapper()
bookMapper.queryAllBook();
System.out.println(" Using data sources dataSource2 Search for book information ")
}
}
In every data source that needs to be switched Service Layer using TargetDataSource(name= “***”) You can specify the data source of the current thread , Of course, don't forget @Transactional The addition of transactions , This transaction is used for Mybatis When querying data, get the data source of the current thread . So it is controller Normal call in Service It's OK to use the method in , If you need to query two databases, call two databases respectively TestService You can use the method in . such as :
// I currently use scala Language as a development language ,Java Not much , Still used to Scala, The following program still uses Scala Language standard ha
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(" Use TestService1 Query data source 1 All users in ")
val allBook = testService2.queryAllBook("33287")
println(" Use TestService2 Query data source 2 All the book information in ")
}
}

Two 、 How to achieve
Next, I'll tell you how to do it in detail Spring MVC and Mybatis A single set of data sources supports the extension of multi data source switching capability . Here's a dual data source , The three data sources are implemented in the same way .

 1. First add the link information of the second data source in the configuration file .
environment-dev.properties
# data source 1 Link information for
db1.jdbc.username=xxx
db1.jdbc.password=xxxxx
db1.jdbc.driverClassName=com.mysql.jdbc.Driver
db1.jdbc.url=xxxx?useUnicode=true&characterEncoding=utf8
# Newly added data sources 2 Link information for
db2.jdbc.username=xxx
db2.jdbc.password=xxxxx
db2.jdbc.driverClassName=com.mysql.jdbc.Driver
db2.jdbc.url=xxxx?useUnicode=true&characterEncoding=utf8
2. stay dao Layer of context.xml Add annotation based transaction management and AOP Section configuration
(1) Add dual data sources to the configuration file , as follows :
<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) Use AbstractRoutingDataSource Realize dynamic data source selection
Add... To the configuration file
<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>
<!-- By default dataSource1 Data source -->
<property name="defaultTargetDataSource" ref="dataSource1" />
</bean>
stay dao Layer creation dataSourceManage package , Create the package in the following class DynamicDataSource,DataSourceHolder.
Class one :
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSoure();
}
}
Class two :
public class DataSourceHolder {
// Thread local environment
private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
// Set data source
public static void setDataSource(String customerType) {
dataSources.set(customerType);
}
// Acquisition data source
public static String getDataSoure() {
return (String) dataSources.get();
}
// Clear the data source
public static void clearDataSource() {
dataSources.remove();
}
}
Spring boot Provides AbstractRoutingDataSource Select the current data source according to user-defined rules , So we can do this before we execute the query , Set the data source for . Implement dynamically routable data sources , Before each database query operation . Its abstract method determineCurrentLookupKey() Decide which data source to use . Data source selection before completing database operation , It uses DataSourceHolder.getDataSoure();

(3) add to Spring Business , Be sure to query the database in the business code , from Spring Transactions to perform the above selection of data sources , This will not affect the business code, but also provide the nature of the transaction .
Add... To the configuration file

 <!-- Define transaction manager ( Declarative transactions ) -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Will all have @Transactional Annotated Bean Automatically configured for declarative transaction support -->
<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>
Pay attention to the configuration sqlSessionFactory The data source used in the transaction configuration needs to be consistent with that in the transaction configuration . And the top level of the configuration file bean Need to add xmlns:tx="http://www.springframework.org/schema/tx" and xsi:schemaLocation Add http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
(4) To configure AOP Provide Service Layer annotated declaration of the data source used
First add... To the configuration file AOP Support xmlns:aop="http://www.springframework.org/schema/aop",xsi:schemaLocation Add http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
<!-- Configure the tangent bean DataSourceExchange User defined facet class for data source switching -->
<bean id="dataSourceExchange" class="common.dao.mysql.datasource.DataSourceExchange" />
<!-- To configure AOP -->
<aop:config>
<!-- Configure tangent expression Definition dataSourceExchange The scope of interception in -->
<aop:pointcut id="servicePointcut" expression="execution(* common.dao.mysql.service.*.*(..))"/>
<aop:advisor advice-ref="dataSourceExchange" pointcut-ref="servicePointcut" order="1" />
</aop:config>
among execution(* common.dao.mysql.service.*.*(..)) by service All classes under ( finger TestService1 and TestService2) All of the public Methods add facet proxy, that is to say dataSourceExchange Handle .
And then in dataSourceManage Package created under DataSourceExchange Class implementation AfterReturningAdvice,MethodBeforeAdvice Two aop notice
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 {
// here TargetDataSource It's a custom annotation ,method For the method of querying database, for example, in No.1 queryAllUser(),Objects Is an array of parameters passed to the method ,o For the object that called the method , such as val allUser =
//testService1.queryAllUser() Medium 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());
}
}
}
}
And then in dataSourceManage Package created under TargetDataSource Annotation class
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name() default "dataSource1";
}
After the above configuration is completed, the final effect can be achieved .
complete dao The contents of the configuration file are as follows
<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>
<!-- By default dataSource1 Data source -->
<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>
<!-- Configure that can be executed in batches sqlSession -->
<!-- Configure the tangent bean -->
<bean id="dataSourceExchange" class="test.common.dao.mysql.datasource.DataSourceExchange" />
<!-- To configure AOP -->
<aop:config>
<!-- Configure tangent expression -->
<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>
Finally, we welcome your comments and corrections , Leave a message at any time , In the future, I hope to bring you better technical paste 
版权声明
本文为[mapleJia]所创,转载请带上原文链接,感谢

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云