1、背景
最近项目里需要添加事务回滚处理,采用了spring多数据源,继承了AbstractRoutingDataSource来实现多数据源配置,之前其他人配置的事务不起作用(手动摊手),只能自己重新配置,记录下踩过的坑。目前只能在同一个数据源中进行回滚,暂不支持一个service层里面实现多个数据源回滚。
由于涉及到数据源切换,利用自定义注解,然后通过切面动态切换数据源,如下所示
1.1自定义注解
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value(); }
1.2 Dao持久层进行数据源切换
2、实现方式
由于需要在service层实现事务处理,而事务处理必须在切换数据源之后,目前是在Dao层切换数据源,所以事务会失效。如果全部切换到service层进行数据源切换,修改的地方很多(无语啊),自己只能重新一个切面,并在service层切换数据源,代码及配置如下:
2.1 多数据源配置
<!--数据源-链接数据库的基本信息,这里直接写,不放到*.properties资源文件中 --> <context:property-placeholder location="classpath:application.properties"></context:property-placeholder> <bean id="dataSource1" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${ora1_jdbc.driver}" /> <property name="url" value="${ora1_jdbc.url}" /> <property name="username" value="${ora1_jdbc.username}"/> <property name="password" value="${ora1_jdbc.password}"/> </bean> <bean id="dataSource2" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${ora2_jdbc.driver}" /> <property name="url" value="${ora2_jdbc.url}" /> <property name="username" value="${ora2_jdbc.username}"/> <property name="password" value="${ora2_jdbc.password}" /> </bean> <bean id="dataSource" class="com.essbase.util.dbSourcesConfig.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 指定lookupKey和与之对应的数据源 --> <entry key="dataSource1" value-ref="dataSource1"></entry> <entry key="dataSource2" value-ref="dataSource2"></entry> </map> </property> <!-- 指定默认的数据源 --> <property name="defaultTargetDataSource" ref="dataSource1"></property> </bean>
2.2 aop配置
1 <!-- 扫描包Service实现类 --> 2 <context:component-scan base-package="com.essbase.service"></context:component-scan> 3 <bean id="dynamicDataSourceAop" class="com.essbase.util.dbSourcesConfig.DynamicDataSourceAop"/> 4 <aop:config> 5 <aop:pointcut expression="execution(* com.essbase.dao..*.*(..))||execution(* com.essbase.service..*.*(..))" id="cut1"/> 6 <!--事务切面--> 7 <aop:pointcut expression="execution(* com.essbase.service..*.*(..))" id="cut2"/> 8 <aop:advisor advice-ref="dynamicDataSourceAop" pointcut-ref="cut1" order="1"/> 9 <aop:advisor advice-ref="txAdvice" pointcut-ref="cut2" order="2" /> 10 </aop:config> 11 <!--事务配置--> 12 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 13 <property name="dataSource" ref="dataSource" /> 14 </bean> 15 <tx:advice id="txAdvice" transaction-manager="transactionManager"></tx:advice>
DynamicDataSourceAop类是扫描dao层和service层进行数据源切换,这里有一个坑,在扫描service层时,一直进不去切面类,经过排查原来是在spring.xml中没有扫描到service包(默认扫描本xml中的配置,写到其他xml中会导致扫描不到),
添加<context:component-scan base-package="com.essbase.service"></context:component-scan>后正常。
1 public class DynamicDataSourceAop implements MethodBeforeAdvice, AfterReturningAdvice { 2 3 @Override 4 public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { 5 if(method.isAnnotationPresent(DataSource.class)) { 6 DynamicDataSource.clearDataSource(); 7 System.out.println("**********************************数据源已移除*************************************"); 8 } 9 } 10 11 @Override 12 public void before(Method method, Object[] objects, Object o) throws Throwable { 13 if(method.isAnnotationPresent(DataSource.class)){ 14 DataSource dataSource = method.getAnnotation(DataSource.class); 15 DynamicDataSource.setDataSource(dataSource.value()); 16 System.out.println("*******************************数据源切换至:"+DynamicDataSource.getDatasource()+"**************************************"); 17 } 18 19 } 20 }
1 public class DynamicDataSource extends AbstractRoutingDataSource{ 2 /** 3 * 数据源标识,保存在线程变量中,避免多线程操作数据源时互相干扰 4 */ 5 private static final ThreadLocal<String> key = new ThreadLocal<String>(); 6 7 @Override 8 protected Object determineCurrentLookupKey() { 9 // 从自定义的位置获取数据源标识 10 return key.get(); 11 } 12 /** 13 * 设置数据源 14 * @param dataSource 数据源名称 15 */ 16 public static void setDataSource(String dataSource){ 17 key.set(dataSource); 18 } 19 /** 20 * 获取数据源 21 * @return 22 */ 23 public static String getDatasource() { 24 return key.get(); 25 } 26 27 /** 28 * 清除数据源 29 */ 30 public static void clearDataSource(){ 31 key.remove(); 32 } 33 }
在这里要注意
<aop:advisor advice-ref="txAdvice" pointcut-ref="cut2" order="2" /> 事务切面的order值要大于数据源切面,order值越小权重越高
2.3 service层
1 //service层实现类 2 @Transactional() 3 public void test() { 4 5 }
//service接口类 @DataSource("dataSource2") List<TbmFrdayEntity> test();
通过以上配置就可以实现多数据源事务回滚(目前只能在同一个数据源中进行回滚,暂不支持一个service层里面实现多个数据源回滚)