Spring事务失效问题分析及解决方案

 更新时间:2020年01月14日 10:12:33   作者:街头卖艺的肖邦  
这篇文章主要介绍了Spring事务失效问题分析及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

这篇文章主要介绍了Spring事务失效问题分析及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

隔离级别

在 TransactionDefinition.java 接口中,定义了“四种”的隔离级别枚举:

/**
 * 【Spring 独有】使用后端数据库默认的隔离级别
 *
 * MySQL 默认采用的 REPEATABLE_READ隔离级别
 * Oracle 默认采用的 READ_COMMITTED隔离级别
 */
int ISOLATION_DEFAULT = -1;
 
/**
 * 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
 */
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
 
/**
 * 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
 */
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
/**
 * 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
 */
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
/**
 * 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
 *
 * 但是这将严重影响程序的性能。通常情况下也不会用到该级别。
 */
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

事务的传播级别

事务的传播行为,指的是当前带有事务配置的方法,需要怎么处理事务;例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行;

需要注意,事务的传播级别,并不是数据库事务规范中的名词,而是 Spring 自身所定义的。通过事务的传播级别,Spring 才知道如何处理事务,是创建一个新事务呢,还是继续使用当前的事务;

在 TransactionDefinition.java 接口中,定义了三类七种传播级别:

// ========== 支持当前事务的情况 ==========
 
/**
 * 如果当前存在事务,则使用该事务。
 * 如果当前没有事务,则创建一个新的事务。
 */
int PROPAGATION_REQUIRED = 0;
/**
 * 如果当前存在事务,则使用该事务。
 * 如果当前没有事务,则以非事务的方式继续运行。
 */
int PROPAGATION_SUPPORTS = 1;
/**
 * 如果当前存在事务,则使用该事务。
 * 如果当前没有事务,则抛出异常。
 */
int PROPAGATION_MANDATORY = 2;
 
// ========== 不支持当前事务的情况 ==========
 
/**
 * 创建一个新的事务。
 * 如果当前存在事务,则把当前事务挂起。
 */
int PROPAGATION_REQUIRES_NEW = 3;
/**
 * 以非事务方式运行。
 * 如果当前存在事务,则把当前事务挂起。
 */
int PROPAGATION_NOT_SUPPORTED = 4;
/**
 * 以非事务方式运行。
 * 如果当前存在事务,则抛出异常。
 */
int PROPAGATION_NEVER = 5;
 
// ========== 其他情况 ==========
 
/**
 * 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。
 * 如果当前没有事务,则等价于 {@link TransactionDefinition#PROPAGATION_REQUIRED}
 */
int PROPAGATION_NESTED = 6;

@Transaction

@Transaction 注解是Spring的tx模块提供的,使用AOP实现的事务控制,即底层为动态代理;

  @Transaction 可以作用于接口、接口方法、类以及类方法上;当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,也可以在方法级别使用该标注来覆盖类级别的定义;

  事务失效

  1.异常类型错误,默认是RuntimException才会回滚

  2.异常被catch后没有抛出,需要抛异常才能回滚

  3.是否发生自身调用的问题

  4.注解所在位置是否为public修饰

  5.数据源没有配置事务管理器

  6.不支持事务的引擎,如MyIsam

  下面是一个事务失效的例子

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
  @Autowired
  private OrderMapper orderMapper;
 
  @Transactional
  @Override
  public void parent() {
    try {
      this.child();
 
    } catch (Exception e) {
      log.error("插入异常", e);
    }
 
 
    Order order = new Order();
    order.setOrderNo("parent");
    order.setStatus("0");
    order.setTitle("parent");
    order.setAmount("1000");
    orderMapper.insert(order);
  }
 
 
  @Transactional
  @Override
  public void child() {
 
    Order order = new Order();
    order.setOrderNo("child");
    order.setStatus("0");
    order.setTitle("child");
    order.setAmount("2000");
    orderMapper.insert(order);
 
    throw new RuntimeException();
  }
}

当调用parent方法时,会调用child方法,但执行完成后插入的记录有两条,一条是parent的,一条是child的;

这是因为上面的parent方法调用的child方法出现问题,@Transaction 是基于AOP的方式进行事务控制的(CglibAopProxy.java进行方法拦截,TransactionInterceptor.java进行代理对象调用执行方法),需要使用的是代理对象调用方法,上面的代码使用的还是this,即当前实例化对象,因此执行this.child(),方法不能被拦截增强;

将上面的parent方法修改如下

@Autowired
private ApplicationContext context;
 
private OrderService orderService;
 
@PostConstruct
public void init() {
  orderService = context.getBean(OrderService.class);
}
 
 
@Transactional
@Override
public void parent() {
  try {
    //获取代理对象,通过代理对象调用child()
    orderService.child();
 
   //获取代理对象
    //OrderService orderService = (OrderService) AopContext.currentProxy();
    //orderService.child();
  } catch (Exception e) {
    log.error("插入异常", e);
  }
  Order order = new Order();
  order.setOrderNo("parent");
  order.setStatus("0");
  order.setTitle("parent");
  order.setAmount("1000");
  orderMapper.insert(order);
}

执行parent方法,当出现异常的时候,事务会进行回滚;

如果想实现当调用parent方法时,调用child方法发生异常,只回滚child方法插入的数据,parent方法插入的数据不回滚,修改如下

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void child() {
 
  Order order = new Order();
  order.setOrderNo("child");
  order.setStatus("0");
  order.setTitle("child");
  order.setAmount("2000");
  orderMapper.insert(order);
 
  throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)

如果当前存在事务,则挂起事务并开启一个新事务执行,新事务执行完毕后,唤醒之前的挂起的事务,则继续执行;如果当前不存在事务,则新建一个事务;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • java实现哈夫曼压缩的实例

    java实现哈夫曼压缩的实例

    这篇文章主要介绍了java实现哈夫曼压缩的实例的相关资料,需要的朋友可以参考下
    2017-07-07
  • SpringBoot项目打包发布到外部tomcat(出现各种异常的解决)

    SpringBoot项目打包发布到外部tomcat(出现各种异常的解决)

    这篇文章主要介绍了SpringBoot项目打包发布到外部tomcat(出现各种异常的解决),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • Kafka多节点分布式集群搭建实现过程详解

    Kafka多节点分布式集群搭建实现过程详解

    这篇文章主要介绍了Kafka多节点分布式集群搭建实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • java中hibernate二级缓存详解

    java中hibernate二级缓存详解

    本篇文章中主要介绍了java中hibernate二级缓存详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • Spring MVC中使用Google kaptcha验证码的方法详解

    Spring MVC中使用Google kaptcha验证码的方法详解

    kaptcha 是一个非常实用的验证码生成工具。有了它,你可以生成各种样式的验证码,因为它是可配置的,下面这篇文章主要给大家介绍了关于Spring MVC中使用Google kaptcha验证码的方法,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-10-10
  • Spring Boot整合流控组件Sentinel的场景分析

    Spring Boot整合流控组件Sentinel的场景分析

    Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑,这篇文章主要介绍了Spring Boot整合流控组件Sentinel的过程解析,需要的朋友可以参考下
    2021-12-12
  • 实例讲解java的纯数字加密解密

    实例讲解java的纯数字加密解密

    本文给大家分享的是一个java纯数字加密解密技术,加密和解密本身就是一对共生体,缺一不可,需要的朋友可以参考下
    2015-07-07
  • Java中实现线程的三种方式及对比_动力节点Java学院整理

    Java中实现线程的三种方式及对比_动力节点Java学院整理

    本文给大家分享了java实现线程的三种方式,非常不错,具有参考借鉴价值,需要的朋友参考下吧
    2017-05-05
  • Java多线程三种主要实现方式解析

    Java多线程三种主要实现方式解析

    这篇文章主要介绍了Java多线程三种主要实现方式解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • SpringSecurity 默认表单登录页展示流程源码

    SpringSecurity 默认表单登录页展示流程源码

    本篇主要讲解 SpringSecurity提供的默认表单登录页 它是如何展示流程,本文图文并茂给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-01-01

最新评论