Spring事务详细传播属性解释
Spring事务(Transaction)的传播(propagation)属性以及隔离(isolation)级别
SpringBoot异常处理回滚事务详解(自动回滚、手动回滚、部分回滚)
声明式事务和编程式事务
事务传播行为测试
1. Spring事务的传播行为
1. 7种传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
2. 传播属性示例
2.1 PROPAGATION_REQUIRED
我们为User1Service和User2Service相应方法加上Propagation.REQUIRED
属性。
User1Service方法:
@Service
public class User1ServiceImpl implements User1Service {//省略其他..@Override@Transactional(propagation = Propagation.REQUIRED)public void addRequired(User1 user){user1Mapper.insert(user);}
}
User2Service方法:
@Service
public class User2ServiceImpl implements User2Service {//省略其他...@Override@Transactional(propagation = Propagation.REQUIRED)public void addRequired(User2 user){user2Mapper.insert(user);}@Override@Transactional(propagation = Propagation.REQUIRED)public void addRequiredException(User2 user){user2Mapper.insert(user);throw new RuntimeException();}
}
1.1 场景一
此场景外围方法没有开启事务。
验证方法1:
// 张三 和 李四成功保存。
// 外层方法抛出异常但不受异常管理,user1Service 和 user2Service 在各自的事务内正常提交
@Overridepublic void notransaction_exception_required_required(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequired(user2);throw new RuntimeException();}
验证方法2:
// 张三成功,李四失败// 外层方法不受异常管理,user1Service 和 user2Service 在各自的事务内提交;user1Service成功提交、user2Service回滚@Overridepublic void notransaction_required_required_exception(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequiredException(user2);}
分别执行验证方法,结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”、“李四”均插入。 | 外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。 |
2 | “张三”插入,“李四”未插入。 | 外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。 |
结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRED
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
1.2 场景二
外围方法开启事务,这个是使用率比较高的场景。
验证方法1:
// 张三、李四均未插入。
// Propagation.REQUIRED 传播属性的特点外层有事务就加入外层事务
// 由于user1Service、user2Service都是Propagation.REQUIRED级别,所以此时所有操作都在一个事务内;外围方法抛出异常全部回滚
@Override@Transactional(propagation = Propagation.REQUIRED)public void transaction_exception_required_required(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequired(user2);throw new RuntimeException();}
验证方法2:
// 张三、李四均未插入。
// Propagation.REQUIRED 传播属性的特点外层有事务就加入外层事务
// 由于user1Service、user2Service都是Propagation.REQUIRED级别,所以此时所有操作都在一个事务内;user2Service抛出异常全部回滚
@Override@Transactional(propagation = Propagation.REQUIRED)public void transaction_required_required_exception(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequiredException(user2);}
验证方法3:
// 会回滚且 报错 Transaction rolled back because it has been marked as rollback-only
// 在整个事务中 ,在user2Service已经抛出的异常此时事务被标记为rollBack;在外层方法处理异常时事务被标记为commit,出现矛盾保存Transaction rolled back because it has been marked as rollback-only
@Transactional@Overridepublic void transaction_required_required_exception_try(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1);User2 user2=new User2();user2.setName("李四");try {user2Service.addRequiredException(user2);} catch (Exception e) {System.out.println("方法回滚");}}
验证方法4:
- 修改addRequiredException 为 addRequiredExceptionAndCatch
@Override@Transactional(propagation = Propagation.REQUIRED)public void addRequiredExceptionAndCatch(User2 user){user2Mapper.insert(user);try {throw new RuntimeException();} catch (Exception e) {}}
- 测试
// 事务提交 张三、李四全部插入;插入李四的异常被catch外层无法感知提交事务。
// 总而言之只要在事务处理过程中存在直接抛异常的动作一定会回滚
@Transactional@Overridepublic void transaction_required_required_exception_try(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequiredExceptionAndCatch(user2);}
分别执行验证方法,结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”、“李四”均未插入。 | 外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法也要回滚。 |
2 | “张三”、“李四”均未插入。 | 外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,外围方法感知异常致使整体事务回滚。 |
3 | “张三”、“李四”均未插入。 | 外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚。 |
4 | “张三”、“李四”均插入。 | 外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常被catch,不能被外围方法感知,整个事务提交。 |
结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.REQUIRED
修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED
修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。
2.2 PROPAGATION_REQUIRES_NEW
我们为User1Service和User2Service相应方法加上Propagation.REQUIRES_NEW
属性。
User1Service方法:
@Service
public class User1ServiceImpl implements User1Service {//省略其他...@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void addRequiresNew(User1 user){user1Mapper.insert(user);}@Override@Transactional(propagation = Propagation.REQUIRED)public void addRequired(User1 user){user1Mapper.insert(user);}
}
User2Service方法:
@Service
public class User2ServiceImpl implements User2Service {//省略其他...@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void addRequiresNew(User2 user){user2Mapper.insert(user);}@Override@Transactional(propagation = Propagation.REQUIRES_NEW)public void addRequiresNewException(User2 user){user2Mapper.insert(user);throw new RuntimeException();}
}
2.1 场景一
外围方法没有开启事务。
验证方法1:
@Overridepublic void notransaction_exception_requiresNew_requiresNew(){User1 user1=new User1();user1.setName("张三");user1Service.addRequiresNew(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequiresNew(user2);throw new RuntimeException();}
验证方法2:
@Overridepublic void notransaction_requiresNew_requiresNew_exception(){User1 user1=new User1();user1.setName("张三");user1Service.addRequiresNew(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequiresNewException(user2);}
分别执行验证方法,结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”插入,“李四”插入。 | 外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。 |
2 | “张三”插入,“李四”未插入 | 外围方法没有开启事务,插入“张三”方法和插入“李四”方法分别开启自己的事务,插入“李四”方法抛出异常回滚,其他事务不受影响。 |
结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
2.2 场景二
外围方法开启事务。
验证方法1:
// 张三未插入,李四、王五插入
// addRequired(user1) 的传播属性是Propagation.REQUIRED外围有事务就使用外围的事务一同回滚
// addRequiresNew(user2) 的传播属性为Propagation.REQUIRED_NEW在自己新建的独立事务中可以提交;
@Override@Transactional(propagation = Propagation.REQUIRED)public void transaction_exception_required_requiresNew_requiresNew(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1) 的传播属性是;User2 user2=new User2();user2.setName("李四");user2Service.addRequiresNew(user2);User2 user3=new User2();user3.setName("王五");user2Service.addRequiresNew(user3);throw new RuntimeException();}
验证方法2:
// 张三、王五未插入,李四插入
// 外围方法开启事务,插入“张三”方法和外围方法一个事务,
// 插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入 “王五”方法的事务被回滚,异常继续抛出被外围方法感知,
// 外围方法事务亦被回滚,故插入“张三”方法也被回滚。
@Override@Transactional(propagation = Propagation.REQUIRED)public void transaction_required_requiresNew_requiresNew_exception(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequiresNew(user2);User2 user3=new User2();user3.setName("王五");user2Service.addRequiresNewException(user3);}
验证方法3:
// 张三、李四插入,王五未插入。
// 外围方法开启事务,插入“张三”方法和外围方法一个事务
// 插入“李四”方法、插入“王五”方法分别在独立的新建事务中。
// 插入“王五”方法抛出异常,首先插入“王五”方法的事务被回滚,异常被catch不会被外围方法感知,外围方法事务不回滚,故插入“张三”方法插入成功。
@Override@Transactional(propagation = Propagation.REQUIRED)public void transaction_required_requiresNew_requiresNew_exception_try(){User1 user1=new User1();user1.setName("张三");user1Service.addRequired(user1);User2 user2=new User2();user2.setName("李四");user2Service.addRequiresNew(user2);User2 user3=new User2();user3.setName("王五");try {user2Service.addRequiresNewException(user3);} catch (Exception e) {System.out.println("回滚");}}
分别执行验证方法,结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”未插入,“李四”插入,“王五”插入。 | 外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中,外围方法抛出异常只回滚和外围方法同一事务的方法,故插入“张三”的方法回滚。 |
2 | “张三”未插入,“李四”插入,“王五”未插入。 | 外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入 “王五”方法的事务被回滚,异常继续抛出被外围方法感知,外围方法事务亦被回滚,故插入“张三”方法也被回滚。 |
3 | “张三”插入,“李四”插入,“王五”未插入。 | 外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入“王五”方法的事务被回滚,异常被catch不会被外围方法感知,外围方法事务不回滚,故插入“张三”方法插入成功。 |
结论:在外围方法开启事务的情况下Propagation.REQUIRES_NEW
修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。
2.3.PROPAGATION_NESTED
我们为User1Service和User2Service相应方法加上Propagation.NESTED
属性。
User1Service方法:
@Service
public class User1ServiceImpl implements User1Service {//省略其他...@Override@Transactional(propagation = Propagation.NESTED)public void addNested(User1 user){user1Mapper.insert(user);}
}
User2Service方法:
@Service
public class User2ServiceImpl implements User2Service {//省略其他...@Override@Transactional(propagation = Propagation.NESTED)public void addNested(User2 user){user2Mapper.insert(user);}@Override@Transactional(propagation = Propagation.NESTED)public void addNestedException(User2 user){user2Mapper.insert(user);throw new RuntimeException();}
}
3.1 场景一
此场景外围方法没有开启事务。
验证方法1:
@Overridepublic void notransaction_exception_nested_nested(){User1 user1=new User1();user1.setName("张三");user1Service.addNested(user1);User2 user2=new User2();user2.setName("李四");user2Service.addNested(user2);throw new RuntimeException();}
验证方法2:
@Overridepublic void notransaction_nested_nested_exception(){User1 user1=new User1();user1.setName("张三");user1Service.addNested(user1);User2 user2=new User2();user2.setName("李四");user2Service.addNestedException(user2);}
分别执行验证方法,结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”、“李四”均插入。 | 外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。 |
2 | “张三”插入,“李四”未插入。 | 外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。 |
结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.NESTED
和Propagation.REQUIRED
作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
3.2 场景二
外围方法开启事务。
验证方法1:
// 张三、李四均未插入。
// 外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚。
@Transactional@Overridepublic void transaction_exception_nested_nested(){User1 user1=new User1();user1.setName("张三");user1Service.addNested(user1);User2 user2=new User2();user2.setName("李四");user2Service.addNested(user2);throw new RuntimeException();}
验证方法2:
// 张三、李四均未插入。
// 外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,且外围方法感知异常致使整体事务回滚。
@Transactional@Overridepublic void transaction_nested_nested_exception(){User1 user1=new User1();user1.setName("张三");user1Service.addNested(user1);User2 user2=new User2();user2.setName("李四");user2Service.addNestedException(user2);}
验证方法3:
// 张三插入、李四未插入。
// 外围方法开启事务,内部事务为外围事务的子事务,插入“李四”内部方法抛出异常,可以单独对子事务回滚。
@Transactional@Overridepublic void transaction_nested_nested_exception_try(){User1 user1=new User1();user1.setName("张三");user1Service.addNested(user1);User2 user2=new User2();user2.setName("李四");try {user2Service.addNestedException(user2);} catch (Exception e) {System.out.println("方法回滚");}}
分别执行验证方法,结果:
验证方法序号 | 数据库结果 | 结果分析 |
---|---|---|
1 | “张三”、“李四”均未插入。 | 外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚。 |
2 | “张三”、“李四”均未插入。 | 外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,且外围方法感知异常致使整体事务回滚。 |
3 | “张三”插入、“李四”未插入。 | 外围方法开启事务,内部事务为外围事务的子事务,插入“李四”内部方法抛出异常,可以单独对子事务回滚。 |
结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.NESTED
修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务
2.4 PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务就以非事务的方法执行
// 外围方法无事务, otherClassMethod()也以无事务的方式执行;事务提交 13/14/15/16全被删除
@Overridepublic void transactionalPropagationOfSupport() {userMapper.deleteById(15);userMapper.deleteById(16);orderService.otherClassMethod();}@Override@Transactional(propagation = Propagation.SUPPORTS)public User otherClassMethod() {userMapper.deleteById(13);userMapper.deleteById(14);System.out.println(1/0);return null;}
// 外围方法有事务, otherClassMethod()以事务的方式执行;全部回滚@Transactional
@Overridepublic void transactionalPropagationOfSupport() {userMapper.deleteById(15);userMapper.deleteById(16);orderService.otherClassMethod();}@Override@Transactional(propagation = Propagation.SUPPORTS)public User otherClassMethod() {userMapper.deleteById(13);userMapper.deleteById(14);System.out.println(1/0);return null;}
3. REQUIRED,REQUIRES_NEW,NESTED异同
由“1.2 场景二”和“3.2 场景二”对比,我们可知:
NESTED和REQUIRED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。
由“2.2 场景二”和“3.2 场景二”对比,我们可知:
NESTED和REQUIRES_NEW都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。
-2 声明式事务和编程式事务
-2.1 Spring事务支持
Spring 支持两种事务方式,分别是编程式事务和声明式事务,后者最常见,通常情况下只需要一个 **@Transactional **就搞定了(代码侵入性降到了最低)。
- 2.2 编程式事务
编程式事务是指将事务管理代码嵌入嵌入到业务代码中,来控制事务的提交和回滚。比如说,使用 TransactionTemplate 来管理事务:
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {try {// .... 业务代码} catch (Exception e){//回滚transactionStatus.setRollbackOnly();}}});
}
再比如说,使用 TransactionManager 来管理事务:
@Autowired
private PlatformTransactionManager transactionManager;public void testTransaction() {TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());try {// .... 业务代码transactionManager.commit(status);} catch (Exception e) {transactionManager.rollback(status);}
}
就编程式事务管理而言,Spring 更推荐使用 TransactionTemplate。
在编程式事务中,必须在每个业务操作中包含额外的事务管理代码,就导致代码看起来非常的臃肿,但对理解 Spring 的事务管理模型非常有帮助。
- 2.2 声明式事务
声明式事务将事务管理代码从业务方法中抽离了出来,以声明式的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更易用、更好用。
当然了,要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中的AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。
-2.3 事务管理模型
Spring 将事务管理的核心抽象为一个事务管理器(TransactionManager),它的源码只有一个简单的接口定义,属于一个标记接口:
public interface TransactionManager {}
该接口有两个子接口,分别是编程式事务接口 ReactiveTransactionManager 和声明式事务接口 PlatformTransactionManager。我们来重点说说 PlatformTransactionManager,该接口定义了 3 个接口方法:
interface PlatformTransactionManager extends TransactionManager{// 根据事务定义获取事务状态TransactionStatus getTransaction(TransactionDefinition definition)throws TransactionException;// 提交事务void commit(TransactionStatus status) throws TransactionException;// 事务回滚void rollback(TransactionStatus status) throws TransactionException;
}
通过 PlatformTransactionManager 这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
参数 TransactionDefinition 和 @Transactional 注解是对应的,比如说 @Transactional 注解中定义的事务传播行为、隔离级别、事务超时时间、事务是否只读等属性,在 TransactionDefinition 都可以找得到。
返回类型 TransactionStatus 主要用来存储当前事务的一些状态和数据,比如说事务资源(connection)、回滚状态等。
TransactionDefinition如下:
public interface TransactionDefinition {// 事务的传播行为default int getPropagationBehavior() {return PROPAGATION_REQUIRED;}// 事务的隔离级别default int getIsolationLevel() {return ISOLATION_DEFAULT;}// 事务超时时间default int getTimeout() {return TIMEOUT_DEFAULT;}// 事务是否只读default boolean isReadOnly() {return false;}
}
Transactional注解如下:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {Propagation propagation() default Propagation.REQUIRED;Isolation isolation() default Isolation.DEFAULT;int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;boolean readOnly() default false;}
-
@Transactional 注解中的 propagation 对应 TransactionDefinition 中的 getPropagationBehavior,默认值为 Propagation.REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)。
-
@Transactional 注解中的 isolation 对应 TransactionDefinition 中的 getIsolationLevel,默认值为 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)。
-
@Transactional 注解中的 timeout 对应 TransactionDefinition 中的 getTimeout,默认值为TransactionDefinition.TIMEOUT_DEFAULT。
-
@Transactional 注解中的 readOnly 对应 TransactionDefinition 中的 isReadOnly,默认值为 false。
2. 手动回滚事务(编程式事务)
2.1 TransactionAspectSupport
TransactionAspectSupport是Spring提供的事务切面支持类。
public void updateUser2(Integer id, String name) {// 操作未回滚userMapper.deleteUser(2);try {// 操作回滚userMapper.updateUser(id, name);System.out.println(1 / 0);} catch (Exception e) {e.printStackTrace();// 手动回滚事务,并抛出异常TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}}
2.2 PlatformTransactionManager
@Autowired// (PlatformTransactionManager和DataSourceTransactionManager都是TransactionManager的子类使用方法一样)private PlatformTransactionManager platformTransactionManager;@Autowiredprivate TransactionDefinition transactionDefinition; public void updateUser1(Integer id, String name) {// 操作未回滚userMapper.deleteUser(2);TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);try {// 操作回滚userMapper.updateUser(id, name);System.out.println(1 / 0);platformTransactionManager.commit(transaction);} catch (Exception e) {e.printStackTrace();platformTransactionManager.rollback(transaction);}}
2.3 回滚部分异常
使用【Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 】设置回滚点。
使用【TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);】回滚到savePoint。
public void updateUser3(Integer id, String name) {// 操作未回滚userMapper.deleteUser(2);// 设置回滚点,回滚点以下的数据库操作回滚Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();try {// 操作回滚userMapper.updateUser(id, name);System.out.println(1 / 0);} catch (Exception e) {e.printStackTrace();// 手动回滚事务,并抛出异常TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savepoint);}}
3. 异常失效
3.1 try和@Transactional
有事务注解的方法 调用 不同类的无事务注解的方法;
// 以下代码如果“ throw e ”被注释,执行结果,事务会提交;id=13\14\15\16的用户都会被删除
// 以下代码如果“ throw e ”不被注释,执行结果,事务会回滚;id=13\14\15\16的用户都不会被删除
// 总而言之只要异常被catch,事务就会提交;
@Transactional@Overridepublic void transactionalAndTry() {try {userMapper.deleteById(13);userMapper.deleteById(14);System.out.println(1 / 0);} catch (Exception e) {e.printStackTrace();
// throw e;}orderService.otherClassMethod();}@Overridepublic User otherClassMethod() {userMapper.deleteById(15);userMapper.deleteById(16);return null;}// =================以下写法和上面是一样的==========
@Transactional@Overridepublic void transactionalAndTry() {userMapper.deleteById(13);userMapper.deleteById(14);try {orderService.otherClassMethod(); } catch (Exception e) {e.printStackTrace();
// throw e;}}@Overridepublic User otherClassMethod() {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);return null;}// =================以下写法和上面是一样的==========
@Transactional@Overridepublic void transactionalAndTry() {userMapper.deleteById(13);userMapper.deleteById(14);orderService.otherClassMethod(); }@Overridepublic User otherClassMethod() {try {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);} catch (Exception e) {e.printStackTrace();
// throw e;} return null;}// =================以下写法和上面是一样的 transactionalAndTry() 无法监测到otherClassMethod() catch的异常==========
@Transactional@Overridepublic void transactionalAndTry() {userMapper.deleteById(13);userMapper.deleteById(14);orderService.otherClassMethod(); }@Override@Transactionalpublic User otherClassMethod() {try {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);} catch (Exception e) {e.printStackTrace();
// throw e;} return null;}// =================以下写法和上面是**不一样** transactionalAndTry() 监测到otherClassMethod() catch的异常;全部回滚==========
@Transactional@Overridepublic void transactionalAndTry() {userMapper.deleteById(13);userMapper.deleteById(14);try{orderService.otherClassMethod(); }carch(Exception e){e.printStackTrace();}}@Override@Transactionalpublic User otherClassMethod() {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);return null;}
无事务注解的方法 调用 不同类的有事务注解的方法;
- 有事务注解的方法被事务管理,无事务注解的方法不受事务约束
// 以下代码id=13\14一定不会被事务管理 如果“ throw e ”被注释,执行结果,事务会提交;id=\15\16的用户都会被删除
// 以下代码id=13\14一定不会被事务管理 如果“ throw e ”不被注释,执行结果,事务会回滚;id=\15\16的用户不会被删除 @Overridepublic void transactionalAndTry() {userMapper.deleteById(13);try {userMapper.deleteById(14);} catch (Exception e) {e.printStackTrace();}orderService.deleteUserById();}@Transactional@Overridepublic User deleteUserById() {userMapper.deleteById(15);System.out.println(1 / 0);try {userMapper.deleteById(16);} catch (Exception e) {e.printStackTrace();// throw e;}return null;}
方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加
throw new RuntimeException(); 语句,以便让aop捕获异常再去回滚
方案2:在service层方法的catch语句中进行手动回滚,这样上层就无需去处理异常。
3.2 自调用导致事务失效
问题描述及原因
在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,否则会造成自调用问题。
若同一类中的 没有@Transactional 注解的方法 内部调用 有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。见 示例代码展示。
// 2个方法在同一个类;但事务注解在外层方法,事务生效
@Transactional@Overridepublic void transactionalSameClass() {userMapper.deleteById(13);userMapper.deleteById(14);this.deleteUserById();}public void deleteUserById() {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);}
// 2个方法在同一个类;但事务注解在内层方法,事务失效
// id=13\14\15\16的用户都会被删除 @Overridepublic void transactionalSameClass() {userMapper.deleteById(13);userMapper.deleteById(14);this.deleteUserById();}@Transactionalpublic void deleteUserById() {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);}
自调用失效原因:
spring里事务是用注解配置的,当一个方法没有接口,单单只是一个内部方法时,事务的注解是不起作用的,需要回滚时就会报错。出现这个问题的根本原因是:@Transactional 的实现原理是AOP,AOP的实现原理是动态代理,而自调用时并不存在代理对象的调用,也就不会产生基于AOP 的事务回滚操作虽然可以直接从容器中获取代理对象,但这样有侵入之嫌,不推荐。
3.3 @Transactional 应用在非 public 修饰的方法上
3.4 @Transactional 注解属性 rollbackFor 设置
默认只对非检查型异常(RuntimeException及其子类 或者是 Error)回滚。让所有异常都会让事务启动可以将 @Transactional配置为 @Transactional(rollbackFor = Exception.class)
4. 事务错误使用
4.1 事务的嵌套
事务嵌套导致 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
// 以下2个方法在不同的类
@Transactional@Overridepublic void transactionalAndTry() {userMapper.deleteById(13);userMapper.deleteById(14);try {orderService.deleteUserById();} catch (Exception e) {e.printStackTrace();System.out.println("user服务异常");
// throw e;}}@Transactional@Overridepublic User deleteUserById() {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);return null;}
transactionalAndTry() =>以下称为A方法 ; 方法中调用了deleteUserById() => 以下称为B方法.
上述代码可以触发回滚异常的报错
两个方法都加了事务注解,并且两个方法都会受到到事务管理的拦截器增强,并且事务传播的方式都是默认的,也就是REQUIRED,当已经存在事务的时候就加入事务,没有就创建事务。这里A和B都受事务控制,并且是处于同一个事务的。
A调用B,A中抓了B的异常,当B发生异常的时候,B的操作应该回滚,但是A吃了异常,A方法中没有产生异常,所以A的操作又应该提交,二者是相互矛盾的。
spring的事务关联拦截器在抓到B的异常后就会标记rollback-only为true,当A执行完准备提交后,发现rollback-only为true,也会回滚,并抛出异常告诉调用者。
-
处理方式:且不可直接把方法B的事务去掉,因为方法B可能被其他controller直接调用,即使没调用把项目中的事务去掉都是危险的。
解决还是根据业务处理,具体是要回滚还是处理异常是A和B保持一致的回滚和提交标记。