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 表:未新增数据

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