事务嵌套和局部回滚的问题,很是费解。
本文将做一个详细的测试,加强对Spring的@Transactional 理解和使用
1、两个单独不干扰事务
@RequestMapping("/test")
public void test() {
LoveFile test1 = new LoveFile();
test1.setFileUuid(get32Uuid());
test1.setFileName("test1");
loveFileService.test1(test1);
LoveFile test2 = new LoveFile();
test2.setFileUuid(null);
test2.setFileName("test2");
loveFileService.test2(test2);
}
@Override
@Transactional
public void test1(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
@Override
@Transactional
public void test2(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
由于我给第二个test2插入主键为空,报错。因此库里只有一条
总结:两个单独事务互不干扰。错了就是错了,很好理解
2、普通嵌套事务
@RequestMapping("/test")
@Transactional
public void test() {
LoveFile test1 = new LoveFile();
test1.setFileUuid(get32Uuid());
test1.setFileName("test1");
loveFileService.test1(test1);
LoveFile test2 = new LoveFile();
test2.setFileUuid(null);
test2.setFileName("test2");
loveFileService.test2(test2);
}
子事务不变,删除刚才的数据后,测试。
库里空空。
总结:加在外层的事务起了作用,在test2报错时回滚了test1
3、嵌套事务、三个子事务,中间一个加(propagation = Propagation.REQUIRES_NEW)
Propagation取值:
REQUIRED(默认值):在有transaction状态下执行;如当前没有transaction,则创建新的transaction;
SUPPORTS:如当前有transaction,则在transaction状态下执行;如果当前没有transaction,在无transaction状态下执行;
MANDATORY:必须在有transaction状态下执行,如果当前没有transaction,则抛出异常IllegalTransactionStateException;
REQUIRES_NEW:创建新的transaction并执行;如果当前已有transaction,则将当前transaction挂起;
NOT_SUPPORTED:在无transaction状态下执行;如果当前已有transaction,则将当前transaction挂起;
NEVER:在无transaction状态下执行;如果当前已有transaction,则抛出异常IllegalTransactionStateException。
本文重点研究 REQUIRES_NEW
@RequestMapping("/test")
@Transactional
public void test() {
LoveFile test1 = new LoveFile();
test1.setFileUuid(get32Uuid());
test1.setFileName("test1");
loveFileService.test1(test1);
LoveFile test3 = new LoveFile();
test3.setFileUuid(get32Uuid());
test3.setFileName("test3");
loveFileService.test3(test3);
LoveFile test2 = new LoveFile();
test2.setFileUuid(null);
test2.setFileName("test2");
loveFileService.test2(test2);
}
@Override
@Transactional
public void test1(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
@Override
@Transactional
public void test2(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void test3(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
REQUIRES_NEW官方文档解释:
Create a new transaction, and suspend the current transaction if one exists.
意思是,创建一个新事务,如果当前存在事务,将这个事务挂起。
也就是说test3,是独立于外层事务的单独的事务,他已经挂起的外层事务,他要把自己的问题处理完再去管别人。
因此结果是
总结:不给子事务加(propagation = Propagation.REQUIRES_NEW),相当于加入了外层事务。加了则当做新的子事务。
test2报错了,影响了test1 ,却不能影响test3,已经说明了此问题
4、嵌套事务、三个子事务,中间一个加(propagation = Propagation.REQUIRES_NEW),偏偏出错了
@RequestMapping("/test")
@Transactional
public void test() {
LoveFile test1 = new LoveFile();
test1.setFileUuid(get32Uuid());
test1.setFileName("test1");
loveFileService.test1(test1);
LoveFile test3 = new LoveFile();
test3.setFileUuid(null);
test3.setFileName("test3");
loveFileService.test3(test3);
LoveFile test2 = new LoveFile();
test2.setFileUuid(get32Uuid());
test2.setFileName("test2");
loveFileService.test2(test2);
}
我把test3主键为空,因此他会有SQLException。来研究下,他会不会影响整体。
总结: 虽然他是开辟的新事物,但是出错了,还是会牵连整体的
至此,(propagation = Propagation.REQUIRES_NEW) 已经解释清楚了。
我觉得子事务,不加这个东西的话不叫子事务。不加的话直接加入了外层事务。那还要个单单 @Transactional何用?
建议大家对子事务都加上这条件。
rollbackFor官方文档解释:
在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚
5、rollbackFor了一个异常,却会报另外异常
@RequestMapping("/test")
@Transactional
public void test() {
LoveFile test1 = new LoveFile();
test1.setFileUuid(get32Uuid());
test1.setFileName("test1");
loveFileService.test1(test1);
LoveFile test3 = new LoveFile();
test3.setFileUuid(null);
test3.setFileName("test3");
loveFileService.test3(test3);
LoveFile test2 = new LoveFile();
test2.setFileUuid(get32Uuid());
test2.setFileName("test2");
loveFileService.test2(test2);
}
@Override
@Transactional
public void test1(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
@Override
@Transactional
public void test2(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
@Override
@Transactional(rollbackFor=SQLException.class,propagation = Propagation.REQUIRES_NEW)
public void test3(LoveFile loveFile) {
int a = 1/0;
// mapper.insertSelective(loveFile);
}
这个test3,一定会报 by zero,他会报错,我来测试下,他会不会回滚,影响他人。
如此是影响了。
因为外层事务没有声明异常,他看到一个内部报by zero 了,而自己是RuntimeException,包含在内。因此自己回滚了。
test1 是直接加入整体的。没加出数据,就说明了此问题。
我其实是像测试,test3是否回滚,这个测试案例不合适。
@RequestMapping("/test")
@Transactional
public void test() {
LoveFile test1 = new LoveFile();
test1.setFileUuid(get32Uuid());
test1.setFileName("test1");
loveFileService.test1(test1);
LoveFile test3 = new LoveFile();
test3.setFileUuid(get32Uuid());
test3.setFileName("test3");
loveFileService.test3(test3);
LoveFile test2 = new LoveFile();
test2.setFileUuid(get32Uuid());
test2.setFileName("test2");
loveFileService.test2(test2);
}
@Override
@Transactional
public void test1(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
@Override
@Transactional
public void test2(LoveFile loveFile) {
mapper.insertSelective(loveFile);
}
@Override
@Transactional(rollbackFor=SQLException.class,propagation = Propagation.REQUIRES_NEW)
public void test3(LoveFile loveFile) {
mapper.insertSelective(loveFile);
int a = 1/0;
}
我给了test3 正常的主键,让他添加成功,却在后面 by zero .因为我认为他不会回滚,这次应该是test3加进去了,test1和test2被外层事务回滚。
但是结果是:
我又做了一些测试,发现rollbackFor=SQLException.class 这样的声明一个特定的异常没有任何效果。
rollbackFor 用途不大,就是声明rollbackFor=Exception.class 时比 RuntimeException 广一些。
其他时用途不大。
如果想让特定异常不回滚,还不如用 try catch。只要异常被catch住,不被方法知道,就不会出现error ,也就不会回滚。