【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only(上)

简介: 【小家java】Spring事务嵌套引发的血案---Transaction rolled back because it has been marked as rollback-only(上)

相关阅读

Netflix OSS套件一站式学习驿站(Eureka、Hystrix、Ribbon、Feign、Zuul…)

【小家java】java8新特性(简述十大新特性) 饱受赞誉

【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本

【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(一)

【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二)

【小家Spring】源码分析Spring的事务拦截器:TransactionInterceptor和事务管理器:PlatformTransactionManager


每篇一句

技多不压身。知识只有质量,没有重量,压不死人的。

1、概述


想必大家一想到事务,就想到ACID,或者也会想到CAP。但笔者今天不讨论这个,哈哈~本文将从应用层面稍带一点源码,来解释一下我们平时使用事务遇到的一个问题但让很多人又很棘手的问题:Transaction rolled back because it has been marked as rollback-only,中文翻译为:事务已回滚,因为它被标记成了只回滚。囧,中文翻译出来反倒更不好理解了,本文就针对此种事务异常做一个具体分析:


看此篇博文之前,建议先阅读:【小家java】Spring事务不生效的原因大解读


2、栗子


我们如果使用了spring来管理我们的事务,将会使事务的管理变得异常的简单,比如如下方法就有事务:

@Transactional
@Override
public boolean create(User user) {
    int i = userMapper.insert(user);
    System.out.println(1 / 0); //此处抛出异常,事务回滚,因此insert不会生效
    return i == 1;
}

这应该是我们平时使用的一个缩影。但本文不对事务的基础使用做讨论,只讨论异常情况。但本文可以给读者导航到我的另外一篇博文,介绍了事务不生效的N种可能性:【小家java】spring事务不生效的原因大解读


看下面这个例子,将是我们今天讲述的主题:


@Transactional
@Override
public boolean create(User user) {
    int i = userMapper.insert(user);
    personService.addPerson(user);
    return i == 1;
}

//下面是personService的addPerson方法,也是有事务的

@Transactional
@Override
 public boolean addPerson(User user) {
     System.out.println(1 / 0);
     return false;
 }


这种写法是我们最为普通的写法,显然是可以回滚的。但是如果上面这么写:

@Transactional
 @Override
  public boolean create(User user) {
      int i = userMapper.insert(user);
      try {
          personService.addPerson(user);
      } catch (Exception e) {
          System.out.println("不断程序,用来输出日志~");
      }
      return i == 1;
  }

这里我们把别的service方法try住,不希望它阻断我们的程序继续执行。表面上看合乎情理没毛病,but:



这里需要注意:如果我是这么写:

@Transactional
    @Override
    public boolean addPerson(User user) {
        userMapper.updateByIdSelective(user);
        try {
            editById(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    @Transactional
    @Override
    public boolean editById(User user) {
        System.out.println(1 / 0);
        return false;
    }

也是不会产生上面所述的那个rollback-only异常的:

@Transactional
    @Override
    public boolean addPerson(User user) {
        try {
            editById(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    @Transactional
    @Override
    public boolean editById(User user) {
        userMapper.updateByIdSelective(user);
        System.out.println(1 / 0);
        return false;
    }

但是,我们的updateByIdSelective持久化是生效了的。分析如下:


1.为什么update持久化生效?


因为addPerson有事务,所以editById理论上也有事务应该回滚才对,但是由于上层方法给catch住了,所以是没有回滚的,所以持久化生效。


2.为何没发生roolback-only的异常呢?


原因是因为editById的事务是沿用的addPerson的事务。所以其实上仍然是只有一个事务的,所以catch住不允许回滚也是没有任何问题的,因为事务本身是属于addPerson的,而不属于editById。


但是我们这么来玩:


@Transactional
    @Override
    public boolean addPerson(User user) {
        try {
            personService.editById(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    @Transactional
    @Override
    public boolean editById(User user) {
        userMapper.updateByIdSelective(user);
        System.out.println(1 / 0);
        return false;
    }

就毫无疑问会抛出如下异常:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only


但这么玩,去掉addPerson方法的事务,只保留editById的事务呢?

@Override
    public boolean addPerson(User user) {
        try {
            personService.editById(user);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    @Transactional
    @Override
    public boolean editById(User user) {
        userMapper.updateByIdSelective(user);
        System.out.println(1 / 0);
        return false;
    }

发现rollback-only异常是永远不会出来的。


因此我们可以得出结论,rollback-only异常,是发生在异常本身才有可能出现,发生在子方法内部是不会出现的。因此这种现象最多是发生在事务嵌套里。


相关文章
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1409 3
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
监控 Java 数据库
从零学 Dropwizard:手把手搭轻量 Java 微服务,告别 Spring 臃肿
Dropwizard 整合 Jetty、Jersey 等成熟组件,开箱即用,无需复杂配置。轻量高效,启动快,资源占用少,内置监控、健康检查与安全防护,搭配 Docker 部署便捷,是构建生产级 Java 微服务的极简利器。
1274 117
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
人工智能 Java API
构建基于Java的AI智能体:使用LangChain4j与Spring AI实现RAG应用
当大模型需要处理私有、实时的数据时,检索增强生成(RAG)技术成为了核心解决方案。本文深入探讨如何在Java生态中构建具备RAG能力的AI智能体。我们将介绍新兴的Spring AI项目与成熟的LangChain4j框架,详细演示如何从零开始构建一个能够查询私有知识库的智能问答系统。内容涵盖文档加载与分块、向量数据库集成、语义检索以及与大模型的最终合成,并提供完整的代码实现,为Java开发者开启构建复杂AI智能体的大门。
6377 58
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
1019 8
|
前端开发 Java 开发者
Java新手指南:在Spring MVC中使用查询字符串与参数
通过结合实际的需求和业务逻辑,开发者可以灵活地利用这些机制,为用户提供更丰富而高效的Web应用体验。
319 15
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1368 1
|
前端开发 Java 数据库
Java面试题 - Spring
Java面试题 - Spring
387 0
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
600 0