spring 采用AOP实现多数据源切换
一、首先创建4个bean
DataSource
package com.timelyreport.common.multipleDataSources;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
HandleDataSource
package com.timelyreport.common.multipleDataSources;
public class HandleDataSource {
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
public static void setDataSource(String dataSource){
holder.set(dataSource);
}
public static String getDataSource(){
return holder.get();
}
}
ChooseDataSource
package com.timelyreport.common.multipleDataSources;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class ChooseDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
String dataSource = HandleDataSource.getDataSource();
HandleDataSource.holder.remove();
return dataSource;
}
}
DataSourceAspect
package com.timelyreport.common.multipleDataSources;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
public class DataSourceAspect {
public void pointCut(){};
/**
* 在事务开始之前插入到service上,通过反射获取注解上的数据库
* @param point
*/
@Before(value="pointCut()")
public void before(JoinPoint point){
//拦截的实体类
Object target = point.getTarget();
//拦截的方法名称
String methodName = point.getSignature().getName();
//拦截的放参数类型
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = target.getClass().getMethod(methodName, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);
HandleDataSource.setDataSource(data.value());
System.out.println("切换数据库:"+data.value());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
此处注意HandleDataSource.holder.remove();这段代码,如果没有这段代码,你会发现默认数据切换到另外一个数据时可以的,但是在切回来,就不行了,始终都是第二个数据库。使用此段代码就是为了解除绑定,不影响下一个方法的调用。
二、pom文件配置
<profile>
<!-- 开发环境配置 -->
<id>dev</id>
<!-- 默认编译环境 -->
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<maven.base.url>http://127.0.0.1:8080/360TimelyReport</maven.base.url>
<maven.dayReportFilePath>E:/360/report</maven.dayReportFilePath>
<maven.cycle>30</maven.cycle>
<!-- 数据源配置 -->
<maven.db.type>mysql</maven.db.type>
<maven.db.driver>com.mysql.jdbc.Driver</maven.db.driver>
<maven.db.url>
<![CDATA[jdbc:mysql://127.0.0.1:3306/360_timely_report?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true]]>
</maven.db.url>
<maven.db.userName>root</maven.db.userName>
<maven.db.password></maven.db.password>
<maven.db171.url>
<![CDATA[jdbc:mysql://192.168.0.171:3306/dianjing_data?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false]]>
</maven.db171.url>
<maven.db171.userName>root</maven.db171.userName>
<maven.db171.password></maven.db171.password>
<maven.db173.url>
<![CDATA[jdbc:mysql://192.168.0.173:3306/dianjing_data?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&failOverReadOnly=false]]>
</maven.db173.url>
<maven.db173.userName>root</maven.db173.userName>
<maven.db173.password></maven.db173.password>
</properties>
</profile>
三、属性文件配置
jdbc.type=${maven.db.type}
jdbc.driver=${maven.db.driver}
jdbc.url=${maven.db.url}
jdbc.username=${maven.db.userName}
jdbc.password=${maven.db.password}
jdbc171.url=${maven.db171.url}
jdbc171.username=${maven.db171.userName}
jdbc171.password=${maven.db171.password}
jdbc173.url=${maven.db173.url}
jdbc173.username=${maven.db173.userName}
jdbc173.password=${maven.db173.password}
四、xml配置
<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 数据源配置, 使用 BoneCP 数据库连接池 -->
<bean id="dataSourceDefault" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
<property name="driverClassName" value="${jdbc.driver}" />
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.pool.init}" />
<property name="minIdle" value="${jdbc.pool.minIdle}" />
<property name="maxActive" value="${jdbc.pool.maxActive}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${maxWait}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${jdbc.testSql}" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
<!-- 数据源配置, 使用 BoneCP 数据库连接池 171-->
<bean id="dataSource171" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
<property name="driverClassName" value="${jdbc.driver}" />
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc171.url}" />
<property name="username" value="${jdbc171.username}" />
<property name="password" value="${jdbc171.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.pool.init}" />
<property name="minIdle" value="${jdbc.pool.minIdle}" />
<property name="maxActive" value="${jdbc.pool.maxActive}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${maxWait}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${jdbc.testSql}" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
<!-- 数据源配置, 使用 BoneCP 数据库连接池 173-->
<bean id="dataSource173" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass -->
<property name="driverClassName" value="${jdbc.driver}" />
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc173.url}" />
<property name="username" value="${jdbc173.username}" />
<property name="password" value="${jdbc173.password}" />
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${jdbc.pool.init}" />
<property name="minIdle" value="${jdbc.pool.minIdle}" />
<property name="maxActive" value="${jdbc.pool.maxActive}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${maxWait}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
<property name="validationQuery" value="${jdbc.testSql}" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="stat" />
</bean>
<bean id="dataSource" class="com.timelyreport.common.multipleDataSources.ChooseDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSourceDefault" key="dataSourceDefault"></entry>
<entry value-ref="dataSource171" key="dataSource171"></entry>
<entry value-ref="dataSource173" key="dataSource173"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceDefault">
</property>
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="com.timelyreport.common.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描mapping.xml文件 -->
<!-- <property name="mapperLocations" value="/META-INF/mappings/*/*.xml"></property> -->
<property name="mapperLocations" value="classpath:mapping/**/*.xml"></property>
<property name="configLocation" value="classpath:spring-bean/mybatis-interceptor/mybatis-interceptor-config.xml"></property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.timelyreport.**.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="ins*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="down*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:advisor pointcut="execution(* com.timelyreport.modules.*.service.*.*(..))" advice-ref="txAdvice" />
</aop:config>
<tx:annotation-driven transaction-manager="transactionManager" />
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="dataSourceAspect" class="com.timelyreport.common.multipleDataSources.DataSourceAspect" />
<aop:config>
<aop:aspect id="c" ref="dataSourceAspect">
<aop:pointcut id="tx" expression="execution(* com.timelyreport.modules.*.service.*.*(..))"/>
<aop:before pointcut-ref="tx" method="before"/>
</aop:aspect>
</aop:config>
</beans>
1、此段代码
<bean id="dataSource" class="com.timelyreport.common.multipleDataSources.ChooseDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSourceDefault" key="dataSourceDefault"></entry>
<entry value-ref="dataSource171" key="dataSource171"></entry>
<entry value-ref="dataSource173" key="dataSource173"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceDefault">
</property>
</bean>
是往bean里面注入了默认数据连接,并且添加xml配置中的连接(如上面样例:dataSource171,dataSource173),供以后获取使用。
2、创建切面,在方法调用之前使用
<bean id="dataSourceAspect" class="com.timelyreport.common.multipleDataSources.DataSourceAspect" />
<aop:config>
<aop:aspect id="c" ref="dataSourceAspect">
<aop:pointcut id="tx" expression="execution(* com.timelyreport.modules.*.service.*.*(..))"/>
<aop:before pointcut-ref="tx" method="before"/>
</aop:aspect>
</aop:config>
3、总结,这样在调用service时,就会获取service上的方法的注解,来获取此方法应该使用什么样的数据连接。
五、样例代码
当调用get173DayData方法时,就会调用173数据库,当调用get171DayData方法时,就会调用171数据库