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文件配置

spring 采用AOP实现多数据源切换

<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上的方法的注解,来获取此方法应该使用什么样的数据连接。

五、样例代码

spring 采用AOP实现多数据源切换

当调用get173DayData方法时,就会调用173数据库,当调用get171DayData方法时,就会调用171数据库