Spring AOP如何實(shí)現(xiàn)注解式的Mybatis多數(shù)據(jù)源切換詳解
一、為什么要使用多數(shù)據(jù)源切換?
多數(shù)據(jù)源切換是為了滿足什么業(yè)務(wù)場(chǎng)景?正常情況下,一個(gè)微服務(wù)或者說一個(gè)WEB項(xiàng)目,在使用Mybatis作為數(shù)據(jù)庫(kù)鏈接和操作框架的情況下通常只需要構(gòu)建一個(gè)系統(tǒng)庫(kù),在該系統(tǒng)庫(kù)創(chuàng)建業(yè)務(wù)表來滿足需求,當(dāng)然也有分為測(cè)試庫(kù)和正式庫(kù)dev/prod,不過這倆庫(kù)的切換是使用配置文件進(jìn)行切分的,在項(xiàng)目啟動(dòng)時(shí)或者打成maven JAR包指定environment-dev.properties或者environment-prod.properties。
那么當(dāng)程序運(yùn)行過程中,比如一個(gè)controller中既需要查詢數(shù)據(jù)庫(kù)A,又需要查詢數(shù)據(jù)庫(kù)B,而且兩者都希望用entity(Mybatis中用于與表結(jié)構(gòu)保持一直的bean)來接收查詢結(jié)果,即都希望走M(jìn)ybatis的entity-mapper-mapper.xml這么一套框架。這個(gè)時(shí)候最原始的方法是在代碼中手動(dòng)鏈接數(shù)據(jù)庫(kù)比如:
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事務(wù)和AOP方式的注解式數(shù)據(jù)源切換。最終實(shí)現(xiàn)的效果如下:
@Transactional //該注解表明該Service類開啟Spring事務(wù),事務(wù)的意思是指具有原子性的一個(gè)操作集合(本人理解),該事務(wù)做什么事在dao層的配置文件里配置,后面會(huì)講。
@Service //表明為Service類,使用Component也行,Spring在啟動(dòng)時(shí)會(huì)掃描該類將該類所需要的bean全部構(gòu)建出來以供使用
@TargetDataSource(name = "dataSource1") //重點(diǎn),自定義的AOP注解,指定該TestService1類下的所有public方法都使用數(shù)據(jù)源dataSource1
class TestService1{
public void queryAllUser(){
UserMapper userMapper = new UserMapper()
userMapper.queryAllUser();
System.out.println("使用數(shù)據(jù)源dataSource1查詢用戶信息")
}
}
@Transactional
@Service
@TargetDataSource(name = "dataSource2")
class TestService2{
public void queryAllBook(){
BookMapper bookMapper = new BookMapper()
bookMapper.queryAllBook();
System.out.println("使用數(shù)據(jù)源dataSource2查詢書籍信息")
}
}
在每一個(gè)需要切換數(shù)據(jù)源的Service層使用TargetDataSource(name= “***”)即可指定當(dāng)前線程的數(shù)據(jù)源,當(dāng)然別忘記@Transactional事務(wù)的添加,該事務(wù)用于Mybatis查詢數(shù)據(jù)時(shí)去獲取當(dāng)前線程的數(shù)據(jù)源為哪一個(gè)。如此在controller中正常調(diào)用Service中的方法就行了,如果需要查詢兩個(gè)數(shù)據(jù)庫(kù)那么分別調(diào)用兩個(gè)TestService中的方法即可。比如:
//本人目前使用scala語(yǔ)言作為開發(fā)語(yǔ)言,Java沒怎么寫了,還是習(xí)慣Scala,以下程序還是使用Scala語(yǔ)言規(guī)范哈
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查詢數(shù)據(jù)源1中的所有用戶")
val allBook = testService2.queryAllBook("33287")
println("使用TestService2查詢數(shù)據(jù)源2中的所有書籍信息")
}
}
二、如何實(shí)現(xiàn)
接下來就詳細(xì)講述如何在Spring MVC和Mybatis的單套數(shù)據(jù)源支持上擴(kuò)展多數(shù)據(jù)源切換能力。以下為雙數(shù)據(jù)源,三數(shù)據(jù)源的實(shí)現(xiàn)方式相同。
1.首先在配置文件中添加第二個(gè)數(shù)據(jù)源的鏈接信息。
environment-dev.properties #數(shù)據(jù)源1的鏈接信息 db1.jdbc.username=xxx db1.jdbc.password=xxxxx db1.jdbc.driverClassName=com.mysql.jdbc.Driver db1.jdbc.url=xxxx?useUnicode=true&characterEncoding=utf8 #新添加的數(shù)據(jù)源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配置文件中添加基于注解的事務(wù)管理以及AOP切面配置
(1)在配置文件中添加雙數(shù)據(jù)源,如下:
<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實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源選擇
配置文件中添加
<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> <!-- 默認(rèn)使用dataSource1的數(shù)據(jù)源 --> <property name="defaultTargetDataSource" ref="dataSource1" /> </bean>
在dao層創(chuàng)建dataSourceManage包,在包中創(chuàng)建如下類DynamicDataSource,DataSourceHolder。
類一:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSoure();
}
}
類二:
public class DataSourceHolder {
//線程本地環(huán)境
private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
//設(shè)置數(shù)據(jù)源
public static void setDataSource(String customerType) {
dataSources.set(customerType);
}
//獲取數(shù)據(jù)源
public static String getDataSoure() {
return (String) dataSources.get();
}
//清除數(shù)據(jù)源
public static void clearDataSource() {
dataSources.remove();
}
}
Spring boot提供了AbstractRoutingDataSource 根據(jù)用戶定義的規(guī)則選擇當(dāng)前的數(shù)據(jù)源,這樣我們可以在執(zhí)行查詢之前,設(shè)置使用的數(shù)據(jù)源。實(shí)現(xiàn)可動(dòng)態(tài)路由的數(shù)據(jù)源,在每次數(shù)據(jù)庫(kù)查詢操作前執(zhí)行。它的抽象方法 determineCurrentLookupKey() 決定使用哪個(gè)數(shù)據(jù)源。以上完成數(shù)據(jù)庫(kù)操作之前的數(shù)據(jù)源選擇,使用的是DataSourceHolder.getDataSoure();
(3)添加Spring事務(wù),確定在業(yè)務(wù)代碼中查詢數(shù)據(jù)庫(kù)時(shí),由Spring事務(wù)去執(zhí)行以上對(duì)數(shù)據(jù)源的選擇,這樣既不影響業(yè)務(wù)代碼又能提供事務(wù)的性質(zhì)保證。
在配置文件中添加
<!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 將所有具有@Transactional注解的Bean自動(dòng)配置為聲明式事務(wù)支持 --> <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中使用的數(shù)據(jù)源需要和事務(wù)配置中的保持一直。以及配置文件的頂層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層注解式聲明使用的數(shù)據(jù)源
首先在配置文件中添加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 自定義的切面類實(shí)現(xiàn)數(shù)據(jù)源切換--> <bean id="dataSourceExchange" class="common.dao.mysql.datasource.DataSourceExchange" /> <!--配置AOP --> <aop:config> <!--配置切點(diǎn)表達(dá)式 定義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包下創(chuàng)建DataSourceExchange類實(shí)現(xiàn)AfterReturningAdvice,MethodBeforeAdvice兩個(gè)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為查詢數(shù)據(jù)庫(kù)的方法比如一中的queryAllUser(),Objects為傳給該方法的參數(shù)數(shù)組,o為調(diào)用該方法的對(duì)象,比如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包下創(chuàng)建TargetDataSource注解類
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name() default "dataSource1";
}
以上配置完成之后即可達(dá)成一中的最終效果。
完整的dao配置文件內(nèi)容如下
<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>
<!-- 默認(rèn)使用dataSource1的數(shù)據(jù)源 -->
<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>
<!--配置可以批量執(zhí)行的sqlSession -->
<!--配置切面的bean -->
<bean id="dataSourceExchange" class="test.common.dao.mysql.datasource.DataSourceExchange" />
<!--配置AOP -->
<aop:config>
<!--配置切點(diǎn)表達(dá)式 -->
<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>
到此這篇關(guān)于Spring AOP如何實(shí)現(xiàn)注解式的Mybatis多數(shù)據(jù)源切換的文章就介紹到這了,更多相關(guān)Spring AOP注解式的Mybatis多數(shù)據(jù)源切換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JVM默認(rèn)時(shí)區(qū)為:Asia/Shanghai與java程序中GMT+08不一致異常
這篇文章主要介紹了JVM默認(rèn)時(shí)區(qū)為:Asia/Shanghai與java程序中GMT+08不一致異常問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
詳解如何在SpringBoot中優(yōu)雅地重試調(diào)用第三方API
作為后端程序員,我們的日常工作就是調(diào)用一些第三方服務(wù),將數(shù)據(jù)存入數(shù)據(jù)庫(kù),返回信息給前端。本文為大家介紹了如何在SpringBoot中優(yōu)雅地重試調(diào)用第三方API,需要的可以參考一下2022-12-12
Java 字符數(shù)組轉(zhuǎn)字符串的常用方法
文章總結(jié)了在Java中將字符數(shù)組轉(zhuǎn)換為字符串的幾種常用方法,包括使用String構(gòu)造函數(shù)、String.valueOf()方法、StringBuilder以及Arrays.toString()方法,每種方法都有其適用的場(chǎng)景和性能特點(diǎn),感興趣的朋友跟隨小編一起看看吧2025-01-01
基于request.getAttribute與request.getParameter的區(qū)別詳解
本篇文章小編為大家介紹,基于request.getAttribute與request.getParameter的區(qū)別詳解。需要的朋友參考下2013-04-04
java設(shè)計(jì)模式之簡(jiǎn)單工廠模式
這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式之簡(jiǎn)單工廠模式,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
java數(shù)據(jù)庫(kù)操作類演示實(shí)例分享(java連接數(shù)據(jù)庫(kù))
java數(shù)據(jù)庫(kù)操作類演示實(shí)例分享,大家參考使用吧2013-12-12
Springboot前后端分離項(xiàng)目配置跨域?qū)崿F(xiàn)過程解析
這篇文章主要介紹了Springboot前后端分離項(xiàng)目配置跨域?qū)崿F(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
idea打包java可執(zhí)行jar包的實(shí)現(xiàn)步驟
這篇文章主要介紹了idea打包java可執(zhí)行jar包的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12

