Spring+SpringMVC实现AOP动态多数据源切换

项目中需要配置多个数据源,在此记录一下遇到的小问题。
首先在配置文件中配置数据源:

<bean id="dataSource_Default" class="com.alibaba.druid.pool.DruidDataSource"
		init-method="init" destroy-method="close">
		<!-- 基本属性 url、user、password -->
		<property name="url" value="${datasource.url}" />
		<property name="username" value="${datasource.username}" />
		<property name="password" value="${datasource.password}" />
		<property name="driverClassName" value="${datasource.driverClassName}" />
			<!-- 配置初始化大小、最小、最大 -->
			<property name="initialSize" value="1" />
			<property name="minIdle" value="1" />
			<property name="maxActive" value="20" />
			<!-- 配置获取连接等待超时的时间 -->
			<property name="maxWait" value="60000" />
			<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
			<property name="timeBetweenEvictionRunsMillis" value="60000" />
			<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
			<property name="minEvictableIdleTimeMillis" value="300000" />
			<property name="validationQuery" value="SELECT 'x'" />
			<property name="testWhileIdle" value="true" />
			<property name="testOnBorrow" value="false" />
			<property name="testOnReturn" value="false" />

			<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
			<property name="poolPreparedStatements" value="true" />
			<property name="maxPoolPreparedStatementPerConnectionSize"
				value="40" />

			<!-- 配置监控统计拦截的filters -->
			<property name="filters" value="stat" />
	</bean>
	<bean id="dataSource_Mysql" class="com.alibaba.druid.pool.DruidDataSource"
		init-method="init" destroy-method="close">
		<!-- 基本属性 url、user、password -->
		<property name="url" value="${mysql.datasource.url}" />
		<property name="username" value="${mysql.datasource.username}" />
		<property name="password" value="${mysq.datasource.password}" />
		<property name="driverClassName" value="${mysq.datasource.driverClassName}" />
			
			<!-- 配置初始化大小、最小、最大 -->
			<property name="initialSize" value="1" />
			<property name="minIdle" value="1" />
			<property name="maxActive" value="20" />

			<!-- 配置获取连接等待超时的时间 -->
			<property name="maxWait" value="60000" />

			<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
			<property name="timeBetweenEvictionRunsMillis" value="60000" />

			<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
			<property name="minEvictableIdleTimeMillis" value="300000" />

			<property name="validationQuery" value="SELECT 'x'" />
			<property name="testWhileIdle" value="true" />
			<property name="testOnBorrow" value="false" />
			<property name="testOnReturn" value="false" />

			<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
			<property name="poolPreparedStatements" value="true" />
			<property name="maxPoolPreparedStatementPerConnectionSize"
				value="40" />

			<!-- 配置监控统计拦截的filters -->
			<property name="filters" value="stat" />
	</bean>

配置动态数据管理对象:

<bean id="dataSource" class="com.hnjl.core.db.datasource.DynamicDataSource">
		<property name="targetDataSources">
			<map>
				<entry key="dataSource_Default" value-ref="dataSource_Default" />
				<entry key="dataSource_Mysql" value-ref="dataSource_Mysql" />
			</map>
		</property>
		<property name="defaultTargetDataSource" ref="dataSource_Default" /> 
	</bean>

动态数据源管理类DynamicDataSource的内容如下:

public class DynamicDataSource extends AbstractRoutingDataSource {

	/**
	 * 取得当前使用那个数据源。
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		return DataSourceContextHolder .getDataSource();  
	}
	
	@Override  
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {  
        super.setTargetDataSources(targetDataSources);  
        //重点  
        super.afterPropertiesSet();  
    }
    }
public class DataSourceContextHolder {
	 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();  
	  
	    public static void setDbType(String dbType) {  
	        contextHolder.set(dbType);  
	    }  
	  
	    public static String getDbType() {  
	        return ((String) contextHolder.get());  
	    }  
	  
	    public static void clearDbType() {  
	        contextHolder.remove();  
	    }  
}

接下来使用Spring AOP实现动态切换:

@Aspect
@Component
public class DataSourceInterceptor {

    @Pointcut("execution(* com.junlong.justiceDepartment.service.sftFlyzjgjbxx.SftFlyzjgjbxxService.*(..))")
    public void dataSourceMysql(){

    }
    @Before(value = "dataSourceMysql()")
    public void befor() throws Exception {
        DbContextHolder.setDataSource("dataSource_Mysql");
        System.out.println("befor:"+DbContextHolder.getDataSource());
    }
    @After(value = "dataSourceMysql()")
    public void after(){
        DbContextHolder.setDefaultDataSource();
    }
}

到此配置就算完成了,但是启动项目你会发现AOP织入不成功,前置通知根本就没有执行。
这是由于Spring与SpringMVC是2个不同的父子容器, @Aspect如果被spring容器加载的话,而@Controller注解的这些类的实例化以及注入却是由SpringMVC来完成。 @Aspect如果被spring容器加载的时候,可能Spring MVC容器还未初始化, Controller类还未初始化,所以无法正常织入。
所以调整如下:

@Aspect
public class DataSourceInterceptor {

去掉@Component注解,然后把 aop:aspectj-autoproxy 移入springmvc配置文件中,并定义bean,如下:

	<!--开启动态代理-->
	<aop:aspectj-autoproxy proxy-target-class="true"/>
	<bean id="DataSourceInterceptor" class="com.hnjl.core.db.datasource.DataSourceInterceptor"/>

到这一步发现前置通知执行了,实现了DataSouceHolder的切换,只不过数据源没有切换,这是因为事务环境的隔离性。
Spring中的事务是通过AOP来实现的,所以当我们自己写AOP拦截的时候,就会碰到事务和自定义AOP执行的先后顺序问题,如本文的动态切换数据源的问题。

这里我们使用Spring Order来定义AOP先后顺序,顺带稍微讲一下这个Spring Order。这里借用Ordered接口里的一句话Higher values are interpreted as lower priority。也就是值越高,优先级越低。反过来就是值越低,优先级越高。
但是这里有个问题,上句话说的越高越低和越低越高指的是在执行方法的前的AOP操作,AOP拦截是按照这个顺序,在执行方法后的AOP操作,反之顺序。

还有就是Spring数据源真正切换的关键是AbstractRoutingDataSource的determineTargetDataSource() 方法,此方法是在建立数据库连接时触发。
而事务是在connection层面管理的,启用事务后,一个事务内部的connection是复用的,所以就算AOP切了数据源字符串,但是数据源并不会被真正修改。

因为事务回滚和本文的@AfterThrowing都是在执行方法后,而且事务回滚顺序要在@AfterThrowing前面完成,否则数据源连接池会被事务锁住。按照上面的分析,这个事务回滚的Order值要大于这个自定义AOP中Order的值。

所以这里DataSourceInterceptor修改如下:

@Aspect
@Order(-100)//保证执行顺序
@Component
public class DataSourceInterceptor {

到这里动态切换数据源就算完成了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值