spring 数据源动态切换 与dubbo服务

1:问题描述,以及分析

       项目用了spring数据源动态切换,服务用的是dubbo。在运行一段时间后程序异常,更新操作没有切换到主库上。这个问题在先调用读操作后再调用写操作会出现。经日志分析原因: 第一:当程序运行一段时间后调用duboo服务时,读操作与写操作有可能会在一个线程里(读操作的事务propagation是supports,写是required),当这种情况出现时MethodBeforeAdvice.before先执行,DataSourceSwitcher.setSlave()被调用,然后DynamicDataSource.determineCurrentLookupKey(此方法调用contextHolder.get获取数据源的key)被调用,此时数据源指向从库也就是只读库。当读操作执行完成后,dubbo可能在同一个线程里执行更新的操作(比如以update,insert开头的服务方法),这时会先执行DynamicDataSource.determineCurrentLookupKey,指向的是读库,然后执行MethodBeforeAdvice.before,DataSourceSwitcher.setMaster()被调用,注意,这时DynamicDataSource.determineCurrentLookupKey不会被再次调用,所以这时数据源仍然指向读库,异常发生了。

      DynamicDataSource.determineCurrentLookupKey 与DataSourceSwitcher.setXXX()方法的执行顺序是导致问题的关键,这个跟事务的advice与动态设置数据源的advice执行顺序有关吧

2:数据源配置

<!--配置事务的传播特性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
  <tx:attributes>
   <!-- 对增、删、改方法进行事务支持 -->
   <tx:method name="add*" propagation="REQUIRED" />
   <tx:method name="create*" propagation="REQUIRED" />
   <tx:method name="save*" propagation="REQUIRED"/>
   <tx:method name="edit*" propagation="REQUIRED" />
   <tx:method name="update*" propagation="REQUIRED" />
   <tx:method name="delete*" propagation="REQUIRED" />
   <tx:method name="remove*" propagation="REQUIRED" />
   <!-- 对查找方法进行只读事务 -->
   <tx:method name="find*" propagation="REQUIRED" read-only="true" />
  <tx:method name="query*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
   <!-- 对其它方法进行只读事务 -->
   <!--<tx:method name="*" propagation="SUPPORTS" read-only="true" />-->
  </tx:attributes>
</tx:advice>

<bean id="dataSource" class="com.flzc.base.aop.DynamicDataSource">
  <property name="targetDataSources">
   <map key-type="java.lang.String">
    <entry key="slave" value-ref="slaveDataSource" />
   </map>
  </property>
  <property name="defaultTargetDataSource" ref="masterDataSource" />
 </bean>

public class DataSourceAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
 
 // service方法执行之前被调用
 public void before(Method method, Object[] args, Object target) throws Throwable {
  System.out.println("切入点: " + target.getClass().getName() + "类中" + method.getName() + "方法");
  if (method.getName().startsWith("add") || method.getName().startsWith("create")
    || method.getName().startsWith("save") || method.getName().startsWith("edit")
    || method.getName().startsWith("update") || method.getName().startsWith("delete")
    || method.getName().startsWith("remove")) {
   System.out.println("切换到: master");
   DataSourceSwitcher.setMaster();
  } else {
   System.out.println("切换到: slave");
   DataSourceSwitcher.setSlave();
  }
 }

 // service方法执行完之后被调用
 public void afterReturning(Object arg0, Method method, Object[] args, Object target) throws Throwable {

         DataSourceSwitcher.setMaster(); // *****  加上这句解决运行数据库切换问题
 }

 // 抛出Exception之后被调用
 public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable {
  DataSourceSwitcher.setSlave();
  System.out.println("出现异常,切换到: slave");
 }

}

 

public class DataSourceSwitcher {
 @SuppressWarnings("rawtypes")
 private static final ThreadLocal contextHolder = new ThreadLocal();

 @SuppressWarnings("unchecked")
 public static void setDataSource(String dataSource) {
  Assert.notNull(dataSource, "dataSource cannot be null");
  contextHolder.set(dataSource);
 }

 public static void setMaster(){
  clearDataSource();
    }
 
 public static void setSlave() {
  setDataSource("slave");
 }
 
 public static String getDataSource() {
  return (String) contextHolder.get();
 }

 public static void clearDataSource() {
  contextHolder.remove();
 }
}

package com.flzc.base.aop;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

----- 问题的关键在这个方法什么时候会调用

 @Override
 protected Object determineCurrentLookupKey() {
  return DataSourceSwitcher.getDataSource();
 }

}

你可能感兴趣的:(spring数据源切换)