spring中AOP方式实现动态切换数据源

总述

项目中有时会遇到多数据源的情况,即在一个service中操作两个或两个以上的数据库,这时就需要及时而准确地实现数据源的切换。
通常,可以设置默认数据源,在service中需要切换数据源的地方指定新的数据源,这种方式虽然可行,但存在两个劣势:
(1)过多指定新数据源的做法会影响代码清晰度,降低代码的可读性;
(2)如果service已经开启事务,则数据源切换失败。
除了上述方法,还可以通过spring的AOP动态切换数据源。

配置多数据源

此处案例在spring和mybatis整合的环境中予以说明。
1.在spring-datasource.xml中配置多个数据源
<!-- firstDataSource -->
<bean id="firstDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
	<property name="url" value="${firstDatasource.url}" />
	<property name="username" value="${firstDatasource.name}" />
	<property name="password" value="${firstDatasource.password}" />
	<property name="filters" value="log4j" />
	<property name="maxActive" value="${firstDatasource.maxActive}" />
	<property name="initialSize" value="${firstDatasource.initialiSize}" />
	<property name="maxWait" value="60000" />
	<property name="minIdle" value="1" />
	<property name="timeBetweenEvictionRunsMillis" value="3000" />
	<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" />
	<property name="poolPreparedStatements" value="false" />
	<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
</bean>

<!-- secondDataSource -->
<bean id="secondDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
	<property name="url" value="${secondDatasource.url}" />
	<property name="username" value="${secondDatasource.name}" />
	<property name="password" value="${secondDatasource.password}" />
	<property name="filters" value="log4j" />
	<property name="maxActive" value="${secondDatasource.maxActive}" />
	<property name="initialSize" value="${secondDatasource.initialiSize}" />
	<property name="maxWait" value="60000" />
	<property name="minIdle" value="1" />
	<property name="timeBetweenEvictionRunsMillis" value="3000" />
	<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" />
	<property name="poolPreparedStatements" value="false" />
	<property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
</bean>
2.创建类DatabaseContextHolder,指定默认数据源为firstDataSource
public class DatabaseContextHolder {

	private static final ThreadLocal<String> DATASOURCE_TYPE = new ThreadLocal<String>() {
		@Override
		protected String initialValue() {
			return "firstDataSource";
		}
	};

	public static void setDbType(String dataSourceType) {
		DATASOURCE_TYPE.set(dataSourceType);
	}

	public static String getDbType() {
		return DATASOURCE_TYPE.get();
	}

}
3.创建一个继承AbstractRoutingDataSource的类DynamicDataSource,并实现determineCurrentLookupKey方法
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		return DatabaseContextHolder.getDbType();
	}

}
4.在spring-datasource.xml中配置数据源,class属性值为上一步中所创建的类DynamicDataSource的路径
<bean id="dataSource" class="cn.test.model.common.datasource.DynamicDataSource">
	<property name="defaultTargetDataSource" ref="firstDataSource" />		
	<property name="targetDataSources">
		<map key-type="java.lang.String">
			<entry key="firstDataSource" value-ref="firstDataSource" />
			<entry key="secondDataSource" value-ref="secondDataSource" />
		</map>
	</property>
</bean>
5.创建类DataSourceInterceptor,并于spring配置文件中增加AOP配置:<aop:aspectj-autoproxy expose-proxy="true"/>
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(0)
public class DataSourceInterceptor {

	@Pointcut("execution(* cn.test.model..service.impl.*FirstServiceImpl.*(..))")
	public void firstDataSource() {
	};
	
	@Pointcut("execution(* cn.test.model..service.impl.*SecondServiceImpl.*(..))")
	public void secondDataSource() {
	};

	@Before("firstDataSource()")
	public void beforeFirst(JoinPoint jp) {
		DatabaseContextHolder.setDbType("firstDataSource");
	}
	
	@Before("secondDataSource()")
	public void beforeSecond(JoinPoint jp) {
		DatabaseContextHolder.setDbType("secondDataSource");
	}

}

配置事务

把事务配置在service的实现类上
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
<tx:advice id="txAdvice" transaction-manager="transactionManager">
	<tx:attributes>
		<tx:method name="find*" propagation="REQUIRED" read-only="true" />
		<tx:method name="query*" propagation="REQUIRED" read-only="true" />
		<tx:method name="list*" propagation="REQUIRED" read-only="true" />
		<tx:method name="search*" propagation="REQUIRED" read-only="true" />
		<tx:method name="get*" propagation="REQUIRED" read-only="true" />
		<tx:method name="save*" propagation="REQUIRED" />
		<tx:method name="add*" propagation="REQUIRED" />
		<tx:method name="update*" propagation="REQUIRED" />
		<tx:method name="delete*" propagation="REQUIRED" />
		<tx:method name="create*" propagation="REQUIRED" />
		<tx:method name="check*" propagation="REQUIRED" />
		<tx:method name="sync*" propagation="REQUIRED" />
		<tx:method name="execute*" propagation="REQUIRED" />
		<tx:method name="*" propagation="SUPPORTS" />
	</tx:attributes>
</tx:advice>

<aop:config>
	<aop:pointcut id="interceptorPointCuts" expression="execution(* cn.test.model..service.impl.*ServiceImpl.*(..))" />
	<aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointCuts" order="1" />
</aop:config>
数据源的切换和事务的开启都通过AOP来实现,二者的切点尽量配置在同一级别(类)上;需要注意的是,数据源的切换必须先于事务的开启。

注意事项

代码采用servic间相互调用的方式。当*FirstServiceImpl中的某个方法saveData()调用*SecondService的方法时,首先被切换数据源的切面拦截修改数据源,然后开启事务。
这时可能会出现的问题是,当*SecondService的方法执行完回到*FirstServiceImpl时,如果*FirstServiceImpl接下来还需要继续操作firstDataSource,数据源就会出现错误。
这是因为此刻并未触发修改数据源的切面,数据源仍然停留在secondDataSource,即使是调用*FirstServiceImpl本身的方法也是如此。
如何解决这个问题呢?可以把需要继续执行的代码拆出来放到一个新的service中,但这样会破坏代码的整体结构;更好的办法是把后续代码提取为*FirstService的一个接口,并在*FirstServiceImpl中实现,然后使用org.springframework.aop.framework.AopContext在*FirstServiceImpl中调用该接口,形如:
((DataFirstService)AopContext.currentProxy()).updateData();
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xxpsw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值