惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
腾讯CDC
博客园_首页
T
Tailwind CSS Blog
月光博客
月光博客
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
SecWiki News
SecWiki News
美团技术团队
P
Privacy International News Feed
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
D
DataBreaches.Net
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cisco Blogs
S
Schneier on Security
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
Forbes - Security
Forbes - Security
D
Docker
T
Tenable Blog
S
Secure Thoughts
雷峰网
雷峰网
S
Security @ Cisco Blogs
T
The Exploit Database - CXSecurity.com
The Cloudflare Blog
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

Hsu Yeung 的博客

三圣花市 | Hsu Yeung 的博客 网站支持 Live Photo 图片展示 | Hsu Yeung 的博客 梦 | Hsu Yeung 的博客 周末午餐 | Hsu Yeung 的博客 张家界 | Hsu Yeung 的博客 话剧《寻她芳踪·张爱玲》 | Hsu Yeung 的博客 博客自动生成文章目录 | Hsu Yeung 的博客 灭螂行动! | Hsu Yeung 的博客 张靓颖成都演唱会 | Hsu Yeung 的博客 最近对博客做的一些微调总结 | Hsu Yeung 的博客 Windows 上安装 Lua | Hsu Yeung 的博客 网站支持展示 B 站 iframe 视频 SpringBoot 中使用 RedisTemplate 存取整数值的坑 | Hsu Yeung 的博客 SQL JOIN 中 ON 与 WHERE 的区别 值得记录的一些工作近况 | Hsu Yeung 的博客 周传雄成都演唱会 | Hsu Yeung 的博客 并行操作导致获取数据库连接超时 | Hsu Yeung 的博客 风信子 | Hsu Yeung 的博客 Linux 安装并配置 Nginx | Hsu Yeung 的博客 使用 logrotate 切割 nginx 日志 郁金香土培结果 | Hsu Yeung 的博客 MySQL LEFT JOIN 右表有多条数据但只取最新的一条 | Hsu Yeung 的博客 获取数据库锁等待超时问题 | Hsu Yeung 的博客 从零开始挑选相机 | Hsu Yeung 的博客 郁金香 | Hsu Yeung 的博客 我的第一束花 | Hsu Yeung 的博客 快乐的一周 | Hsu Yeung 的博客 重庆,来了 | Hsu Yeung 的博客 散步 | Hsu Yeung 的博客 Git 工作区、暂存区、版本库之间的关系 | Hsu Yeung 的博客 Collectors.toMap() 操作 value 为 null 的情况 AOP 记录请求参数时序列化异常问题 | Hsu Yeung 的博客 第一次参加演唱会 | Hsu Yeung 的博客 MySQL FIND_IN_SET 函数的使用 | Hsu Yeung 的博客 自动拆箱机制导致编译失败 | Hsu Yeung 的博客 最近 | Hsu Yeung 的博客 夜爬龙泉山 | Hsu Yeung 的博客 在 Windows 上安装多个版本 JDK 并切换使用的版本 安装 Windows Terminal 和 gsudo 记录 意外的奖励 | Hsu Yeung 的博客 记第一次修热水器 | Hsu Yeung 的博客 在 Windows 电脑上调试 iPhone Safari 浏览器的网页 给网站加上 RSS | Hsu Yeung 的博客 打雷了 | Hsu Yeung 的博客 豉汁蒸排骨 | Hsu Yeung 的博客 我的 2022 年 | Hsu Yeung 的博客 如何使用 Git 给代码打 tag | Hsu Yeung 的博客 记一次有趣的上班经历 | Hsu Yeung 的博客 我记得 | Hsu Yeung 的博客 Spring 新增或修改请求 header 参数 | Hsu Yeung 的博客 在家自制“周黑鸭” | Hsu Yeung 的博客 晚餐 | Hsu Yeung 的博客 如何解决逻辑删除与数据库唯一约束冲突 | Hsu Yeung 的博客 [转]漫画赏析:Linux 内核到底长啥样 | Hsu Yeung 的博客 数据库逻辑设计之三大范式通俗理解 | Hsu Yeung 的博客 计算机中的大小端是指什么 | Hsu Yeung 的博客 MySQL 学习笔记 | Hsu Yeung 的博客 [转]《玉子市场》的人物魅力 | Hsu Yeung 的博客 Linux 安装 Redis | Hsu Yeung 的博客 Linux 安装 MySQL | Hsu Yeung 的博客 Linux 安装 JDK | Hsu Yeung 的博客 [转]Thymeleaf 表达式工具类 | Hsu Yeung 的博客 Shell 基础入门介绍 | Hsu Yeung 的博客 Linux 中的软链接和硬链接 | Hsu Yeung 的博客 MySQL 将某些字段的值排在最前或最后 | Hsu Yeung 的博客 数据结构与算法学习笔记 | Hsu Yeung 的博客 Visual Studio Code 如何编写运行 C 程序?
Spring 事务传播机制 | Hsu Yeung 的博客
2022-12-12 · via Hsu Yeung 的博客

1. 前言

上周五项目服务器查看错误日志的时候,发现一个之前从没见过的错误:

异常日志
异常日志

根据日志找到相关代码,一番排查后才发现是事务嵌套导致的问题。简单来说就是有两个传播机制均为 REQUIRED 的事务 A 和 B,其中 A 事务中调用了 B 事务,但是外部是个循环,不能因为一次循环的失败导致整个循环失败,因此在调用的 B 事务被 try catch 包裹,导致内层的 B 事务抛出的异常在外层 A 事务这里被 try catch 吃掉了,异常无法继续往外抛,也就没法触发 Spring 的事务回滚而导致了上图的错误。

今天又复习了一遍事务的传播机制,写这篇文章来记录一下,加深记忆。

2. 七种事务传播机制

2.1. REQUIRED

默认的事务传播机制。支持事务。如果当前方法执行的时候已经在一个事务中了,则直接加入到当前事务中,否则就新开一个事务

例一:内层抛出异常,外层 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        try {
            contentService.saveContent(contentEntity);
        } catch (Exception e) {
            log.error("外层捕获异常");
        }
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:
抛出如下异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚。


例二:内层抛出异常,外层不 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚。


例三:内层正常,外层抛异常

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        int i = 1 / 0;
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        baseMapper.insert(contentEntity);
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚。

2.2. SUPPORTS

支持事务。如果当前有事务就直接加入当前事务,没有就直接执行,不开启事务

例一:内层抛出异常,外层 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        try {
            contentService.saveContent(contentEntity);
        } catch (Exception e) {
            log.error("外层捕获异常");
        }
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:
抛出如下异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚,但是会抛出 rollback-only 的异常。


例二:内层抛出异常,外层不 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚。


例三:内层正常,外层抛异常

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        int i = 1 / 0;
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        baseMapper.insert(contentEntity);
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚。


例四:内层报错,外层正常无事务

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Override
    public void createArticle() {
        baseMapper.insert(ArticleEntity.builder()
                .contentId(1L)
                .build());
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:新增一条数据
  • article 表:新增一条数据

结论:外层无事务,报错前的代码成功提交;由于外层无事务,所以内层也没创建新事物,因此内层报错前的代码也提交了。

2.3. MANDATORY

支持事务。如果当前方法已经在一个事务中,则加入到当前的事务中,否则抛出异常

例一:内层有事务抛出异常,外层有事务 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        try {
            contentService.saveContent(contentEntity);
        } catch (Exception e) {
            log.error("外层捕获异常");
        }
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:
抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
  • content 表:未新增数据
  • article 表:未新增数据

结论:外层有事务,内层方法调用时直接加入当前事务,内层报错,内外层事务均回滚,但是会抛出 rollback-only 的异常。


例二:内层有事务抛出异常,外层有事务不 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚。


例三:内层有事务正常,外层有事务抛异常

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        int i = 1 / 0;
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        baseMapper.insert(contentEntity);
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚。


例四:内层有事务正常,外层无事务正常

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Override
    public void createArticle() {
        baseMapper.insert(ArticleEntity.builder()
                .contentId(1L)
                .build());
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.MANDATORY)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        baseMapper.insert(contentEntity);
    }
}

执行结果:

  • 控制台:
抛出异常:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
  • content 表:未新增数据
  • article 表:新增一条数据

结论:由于外层方法没有事务,所以内层事务尝试加入外层事务失败抛出异常,而外层方法报错前的代码正常执行。

2.4. REQUIRES_NEW

支持事务。每次都会创建一个新的事务,如果当前已经在一个事务中,则会将当前事务挂起等新事物执行完成后再恢复

例一:内层抛出异常,外层 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        try {
            contentService.saveContent(contentEntity);
        } catch (Exception e) {
            log.error("外层捕获异常");
        }
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:新增一条数据

结论:内层事务由于是新的一个事务,抛出异常事务被回滚;外层事务 try-catch 后不再往外抛出异常,因此事务不回滚,而是正常提交事务。


例二:内层抛出异常,外层不 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均回滚。


例三:内层正常,外层抛异常

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        int i = 1 / 0;
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        baseMapper.insert(contentEntity);
    }
}

执行结果:

  • 控制台:正常
  • content 表:新增一条数据
  • article 表:未新增数据

结论:内层事务正常提交,外层事务抛出异常触发回滚。

2.5. NOT_SUPPORTED

不支持事务。如果当前存在事务,则挂起当前事务,等方法执行完毕后,事务恢复进行

例一:内层抛出异常,外层 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        try {
            contentService.saveContent(contentEntity);
        } catch (Exception e) {
            log.error("外层捕获异常");
        }
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:新增一条数据
  • article 表:新增一条数据

结论:内层无事务,因此报错前的代码被提交;内层方法执行过程中,外层事务被挂起,抛出异常后虽然外层事务恢复,但是外层被 try-catch 了,因此外层代码也提交了并未回滚。


例二:内层抛出异常,外层不 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:新增一条数据
  • article 表:未新增数据

结论:内层不支持事务,因此异常前的代码提交了;外层事务接收到内层方法抛出的异常,因此外层事务回滚。


例三:内层正常,外层抛异常

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        int i = 1 / 0;
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        baseMapper.insert(contentEntity);
    }
}

执行结果:

  • 控制台:正常
  • content 表:新增一条数据
  • article 表:未新增数据

结论:内层不支持事务正常提交,外层抛出异常触发事务回滚。

2.6. NEVER

不支持事务。如果当前存在事务则抛出异常

例一:内层抛出异常,外层正常有事务

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        baseMapper.insert(ArticleEntity.builder()
                .contentId(1L)
                .build());
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:
抛出异常:
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
  • content 表:未新增数据
  • article 表:未新增数据

结论:外层存在事务,因此内层不允许在有事务的环境下执行,直接抛出异常,因此内层方法未执行。外层方法接收到异常出发事务回滚。


例二:内层抛出异常,外层正常无事务

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Override
    public void createArticle() {
        baseMapper.insert(ArticleEntity.builder()
                .contentId(1L)
                .build());
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:新增一条数据
  • article 表:新增一条数据

结论:内层不支持事务,因此异常前的代码提交了;外层没有事务因此异常前的代码执行了。

2.7. NESTED

嵌套事务。如果当前已经在一个事务中,则嵌套在已有的事务中执行,内层事务执行完成后,还需要等着外层事务执行完成一起提交。如果当前没有事务,则直接开启一个新事务

例一:内层抛出异常,外层 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        try {
            contentService.saveContent(contentEntity);
        } catch (Exception e) {
            log.error("外层捕获异常");
        }
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:新增一条数据

结论:内层加入外层事务,抛出异常触发事务回滚。外层 try-catch 捕获了异常,因此外层代码正常提交。


例二:内层抛出异常,外层不 try-catch

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createArticle() {
        baseMapper.insert(ArticleEntity.builder()
                .contentId(1L)
                .build());
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        int res = baseMapper.insert(contentEntity);
        if (res != 0) {
            throw new BizException("内层故意抛出异常");
        }
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内外层事务均因为异常触发事务回滚。


例三:内层正常,外层抛异常

外层:

@Slf4j
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, ArticleEntity> implements IArticleService {
    @Resource
    private IContentService contentService;
    
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    @Override
    public void createArticle() {
        ContentEntity contentEntity = ContentEntity.builder()
                .mdContent("# 一级标题")
                .htmlContent("<h1>一级标题</h1>")
                .build();
        contentService.saveContent(contentEntity);
        int i = 1 / 0;
        baseMapper.insert(ArticleEntity.builder()
                .contentId(contentEntity.getId())
                .build());
    }
}

内层:

@Slf4j
@Service
public class ContentServiceImpl extends ServiceImpl<ContentMapper, ContentEntity> implements IContentService {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    @Override
    public void saveContent(ContentEntity contentEntity) {
        baseMapper.insert(contentEntity);
    }
}

执行结果:

  • 控制台:正常
  • content 表:未新增数据
  • article 表:未新增数据

结论:内层事务虽然正常执行完成,但是由于它属于外层事务的子事务,需要等待外层事务的提交,结果外层抛出异常触发事务回滚,导致内层事务也无法提交被回滚。