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(); }}