博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring 数据源动态切换 与dubbo服务
阅读量:6993 次
发布时间:2019-06-27

本文共 3855 字,大约阅读时间需要 12 分钟。

hot3.png

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

}

转载于:https://my.oschina.net/mrXhuangyang/blog/500743

你可能感兴趣的文章
架构师速成6-初中 分类: 架构师速成 2015-0...
查看>>
最新---java多线程下载文件
查看>>
【二】调通单机版的thrift-C++版本
查看>>
让javascript加载速度倍增的方法(解决JS加载速度慢的问题)
查看>>
ASP.NET MVC 主要的四种过滤器和三种具体实现类
查看>>
Python中的正则表达式
查看>>
由于扩展配置问题而无法提供您请求的页面。如果该页面是脚本,请添加处理程序。如果应下载文件,请添加 MIME 映射...
查看>>
(转)结构体中使用string所引发的问题
查看>>
Linux查看网卡流量(转)
查看>>
论文修改(1)
查看>>
[javaEE] web应用的目录结构&配置虚拟主机
查看>>
[PHP] 数据结构-反转链表PHP实现
查看>>
MySQL 如何利用一条语句实现类似于if-else条件语句的判断
查看>>
jQuery和Zepto冲突问题【解决】
查看>>
machinekey生成工具 v1.0 官方最新版
查看>>
http server v0.1_mime.c
查看>>
open files
查看>>
MVC ——RouteTable.Routes的使用
查看>>
玩转X-CTR100 | STM32F4 l X-Assistant串口助手控制功能
查看>>
TCP/IP学习笔记1--概述,分组交换协议
查看>>