AOP獲取方法注解實現動態切換數據源(以下方式尚未經過測試,僅提供思路)
------
自定義一個用於切換數據源的注解:
package com.xxx.annotation; import org.springframework.stereotype.Component; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Component public @interface DataSource { String value() default ""; }
定義一個工具類,方便設置、刪除、獲取從數據源注解中得到的不同數據源類型:
package com.xxx.utils.dataSource; public class DataSourceHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); /** * @Description: 設置數據源類型 * @param dataSourceType 數據庫類型 * @return void * @throws */ public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } /** * @Description: 獲取數據源類型 * @param * @return String * @throws */ public static String getDataSourceType() { return contextHolder.get(); } /** * @Description: 清除數據源類型 * @param * @return void * @throws */ public static void clearDataSourceType() { contextHolder.remove(); } }
--------
配置動態數據源:
spring配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" 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" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <!-- 引入配置文件 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:jdbc.properties" /> </bean> <bean id="dataSource_R" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name ="jndiName"> <!-- 下面第一行為測試環境配置,第二行為生產環境配置,運行時保留一個 --> <value>java:comp/env/jdbc/JTORDER</value> <!-- <value>jdbc/JTORDER</value> --> </property> </bean> <bean id="dataSource_RW" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name ="jndiName"> <!-- 下面第一行為測試環境配置,第二行為生產環境配置,運行時保留一個 --> <value>java:comp/env/jdbc/JTORDER</value> <!-- <value>jdbc/JTORDER</value> --> </property> </bean> <!-- 動態數據源 --> <bean id="dataSource" class="com.xxx.utils.dataSource.RoutingDataSource"> <!-- 為targetDataSources注入兩個數據源 --> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="R" value-ref="dataSource_R"/> <entry key="RW" value-ref="dataSource_RW"/> </map> </property> <!-- 為指定數據源RoutingDataSource注入默認的數據源--> <property name="defaultTargetDataSource" ref="dataSource_R"/> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自動掃描mapping.xml文件 --> <!--<property name="mapperLocations" value="classpath:mapping/*.xml"></property>--> <property name="configLocation" value="classpath:mybatis-config.xml" /> <property name="mapperLocations" value="classpath*:module.*.mapper/*.xml"></property> </bean> <!-- (事務管理)transaction manager, use JtaTransactionManager for global tx --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- xml接口映射文件 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.xxx.mapper"></property> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> <!-- 配置事物切點 --> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* com.xxx.service.impl.*.*(..) )"/> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" /> </aop:config> <!-- 注解方式配置事務--> <!-- <tx:annotation-driven transaction-manager="transactionManager" /> --> <!-- 攔截器方式配置事務--> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> <!-- 自動掃描定時任務 --> <task:annotation-driven/> <!-- spring自動創建代理,植入切面,proxy-target-class屬性,默認為false,表示使用jdk動態代理織入增強,當配為<aop:aspectj-autoproxy poxy-target-class="true"/>時,表示使用CGLib動態代理技術織入增強。不過即使proxy-target-class設置為false,如果目標類沒有聲明接口,則spring將自動使用CGLib動態代理。 --> <aop:aspectj-autoproxy proxy-target-class="true"/> </beans> <!-- 本地測試需要在Tomcat的context.xml的 <Context> 標簽中加入如下配置: <Resource name="jdbc/myName1" scope="Shareable" type="javax.sql.DataSource" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" url="jdbc:oracle:thin:@10.10.10.10:1521:xxdb1" driverClassName ="oracle.jdbc.driver.OracleDriver" username="usesr1" password="pwd1" /> <Resource name="jdbc/myName2" scope="Shareable" type="javax.sql.DataSource" factory="org.apache.tomcat.jdbc.pool.DataSourceFactory" url="jdbc:oracle:thin:@10.10.10.10:1521:xxdb2" driverClassName ="oracle.jdbc.driver.OracleDriver" username="user2" password="pwd2" /> -->
動態數據源類【其中RoutingDataSource 和上面xml中的RoutingDataSource 對應】:
package com.xxx.utils.dataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class RoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceHolder.getDataSourceType(); } }
它的作用是通過獲取我們自定義的數據源類型持有工具類DataSourceHolder中存儲的數據源類型來動態試用真正的數據源
--------
通過AOP獲取方法上的注解實現動態切換數據源
(其中@Order(1)作用:
Spring中的事務是通過aop來實現的,當我們自己寫aop攔截的時候,會遇到跟spring的事務aop執行的先后順序問題,比如說動態切換數據源的問題,如果事務在前,數據源切換在后,會導致數據源切換失效,所以就用到了Order(排序)這個關鍵字.)
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Repository;
@Order(1) @Aspect @Repository public class DataSourceAspect { @Pointcut("execution(* com.xxx.service.impl.*.*(..))") private void anyMethod() {} @AfterReturning(value = "anyMethod()", returning = "result") public void afterReturning(JoinPoint joinPoint,Object result){ DataSourceHolder.clearDataSourceType(); } @Before(value="anyMethod()") public void before(JoinPoint joinPoint){
//通過切點對象獲取當前切點所在的方法對象 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); //如果方法體上使用了DataSource注解 if (method.isAnnotationPresent(DataSource.class)) { //獲取該方法上的注解名 DataSource datasource = method.getAnnotation(DataSource.class); //將方法體上的注解的值賦予給DataSourceHolder數據源持有類 DataSourceHolder.setDataSourceType(datasource.value()); } } }
試用時只需要在被切方法上加上自定義注解,並且在里面配上不同的目標數據源即可。