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()); } } }
试用时只需要在被切方法上加上自定义注解,并且在里面配上不同的目标数据源即可。