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層里面實現多個數據源回滾)