网站构建器,十大网站,erp系统登录入口,wordpress 修改主题Spring事务详细传播属性解释
Spring事务(Transaction)的传播(propagation)属性以及隔离(isolation)级别
SpringBoot异常处理回滚事务详解(自动回滚、手动回滚、部分回滚#xff09;
声明式事务和编程式事务 事务传播行为测试
1. Spring事务的传播行为
1. 7种传播行为
事…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 {//省略其他..OverrideTransactional(propagation Propagation.REQUIRED)public void addRequired(User1 user){user1Mapper.insert(user);}
}User2Service方法
Service
public class User2ServiceImpl implements User2Service {//省略其他...OverrideTransactional(propagation Propagation.REQUIRED)public void addRequired(User2 user){user2Mapper.insert(user);}OverrideTransactional(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 user1new User1();user1.setName(张三);user1Service.addRequired(user1);User2 user2new User2();user2.setName(李四);user2Service.addRequired(user2);throw new RuntimeException();}验证方法2 // 张三成功李四失败// 外层方法不受异常管理user1Service 和 user2Service 在各自的事务内提交;user1Service成功提交、user2Service回滚Overridepublic void notransaction_required_required_exception(){User1 user1new User1();user1.setName(张三);user1Service.addRequired(user1);User2 user2new User2();user2.setName(李四);user2Service.addRequiredException(user2);}分别执行验证方法结果
验证方法序号数据库结果结果分析1“张三”、“李四”均插入。外围方法未开启事务插入“张三”、“李四”方法在自己的事务中独立运行外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。2“张三”插入“李四”未插入。外围方法没有事务插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法插入“张三”方法不受影响。
结论通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务且开启的事务相互独立互不干扰。
1.2 场景二
外围方法开启事务这个是使用率比较高的场景。
验证方法1
// 张三、李四均未插入。
// Propagation.REQUIRED 传播属性的特点外层有事务就加入外层事务
// 由于user1Service、user2Service都是Propagation.REQUIRED级别所以此时所有操作都在一个事务内外围方法抛出异常全部回滚
OverrideTransactional(propagation Propagation.REQUIRED)public void transaction_exception_required_required(){User1 user1new User1();user1.setName(张三);user1Service.addRequired(user1);User2 user2new User2();user2.setName(李四);user2Service.addRequired(user2);throw new RuntimeException();}验证方法2
// 张三、李四均未插入。
// Propagation.REQUIRED 传播属性的特点外层有事务就加入外层事务
// 由于user1Service、user2Service都是Propagation.REQUIRED级别所以此时所有操作都在一个事务内user2Service抛出异常全部回滚
OverrideTransactional(propagation Propagation.REQUIRED)public void transaction_required_required_exception(){User1 user1new User1();user1.setName(张三);user1Service.addRequired(user1);User2 user2new 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
TransactionalOverridepublic void transaction_required_required_exception_try(){User1 user1new User1();user1.setName(张三);user1Service.addRequired(user1);User2 user2new User2();user2.setName(李四);try {user2Service.addRequiredException(user2);} catch (Exception e) {System.out.println(方法回滚);}}验证方法4
修改addRequiredException 为 addRequiredExceptionAndCatch OverrideTransactional(propagation Propagation.REQUIRED)public void addRequiredExceptionAndCatch(User2 user){user2Mapper.insert(user);try {throw new RuntimeException();} catch (Exception e) {}}测试
// 事务提交 张三、李四全部插入插入李四的异常被catch外层无法感知提交事务。
// 总而言之只要在事务处理过程中存在直接抛异常的动作一定会回滚
TransactionalOverridepublic void transaction_required_required_exception_try(){User1 user1new User1();user1.setName(张三);user1Service.addRequired(user1);User2 user2new 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 {//省略其他...OverrideTransactional(propagation Propagation.REQUIRES_NEW)public void addRequiresNew(User1 user){user1Mapper.insert(user);}OverrideTransactional(propagation Propagation.REQUIRED)public void addRequired(User1 user){user1Mapper.insert(user);}
}User2Service方法
Service
public class User2ServiceImpl implements User2Service {//省略其他...OverrideTransactional(propagation Propagation.REQUIRES_NEW)public void addRequiresNew(User2 user){user2Mapper.insert(user);}OverrideTransactional(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 user1new User1();user1.setName(张三);user1Service.addRequiresNew(user1);User2 user2new User2();user2.setName(李四);user2Service.addRequiresNew(user2);throw new RuntimeException();}验证方法2 Overridepublic void notransaction_requiresNew_requiresNew_exception(){User1 user1new User1();user1.setName(张三);user1Service.addRequiresNew(user1);User2 user2new User2();user2.setName(李四);user2Service.addRequiresNewException(user2);}分别执行验证方法结果
验证方法序号数据库结果结果分析1“张三”插入“李四”插入。外围方法没有事务插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。2“张三”插入“李四”未插入外围方法没有开启事务插入“张三”方法和插入“李四”方法分别开启自己的事务插入“李四”方法抛出异常回滚其他事务不受影响。
结论通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务且开启的事务相互独立互不干扰。
2.2 场景二
外围方法开启事务。
验证方法1
// 张三未插入李四、王五插入
// addRequired(user1) 的传播属性是Propagation.REQUIRED外围有事务就使用外围的事务一同回滚
// addRequiresNew(user2) 的传播属性为Propagation.REQUIRED_NEW在自己新建的独立事务中可以提交
OverrideTransactional(propagation Propagation.REQUIRED)public void transaction_exception_required_requiresNew_requiresNew(){User1 user1new User1();user1.setName(张三);user1Service.addRequired(user1) 的传播属性是;User2 user2new User2();user2.setName(李四);user2Service.addRequiresNew(user2);User2 user3new User2();user3.setName(王五);user2Service.addRequiresNew(user3);throw new RuntimeException();}验证方法2
// 张三、王五未插入李四插入
// 外围方法开启事务插入“张三”方法和外围方法一个事务
// 插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常首先插入 “王五”方法的事务被回滚异常继续抛出被外围方法感知
// 外围方法事务亦被回滚故插入“张三”方法也被回滚。
OverrideTransactional(propagation Propagation.REQUIRED)public void transaction_required_requiresNew_requiresNew_exception(){User1 user1new User1();user1.setName(张三);user1Service.addRequired(user1);User2 user2new User2();user2.setName(李四);user2Service.addRequiresNew(user2);User2 user3new User2();user3.setName(王五);user2Service.addRequiresNewException(user3);}验证方法3
// 张三、李四插入王五未插入。
// 外围方法开启事务插入“张三”方法和外围方法一个事务
// 插入“李四”方法、插入“王五”方法分别在独立的新建事务中。
// 插入“王五”方法抛出异常首先插入“王五”方法的事务被回滚异常被catch不会被外围方法感知外围方法事务不回滚故插入“张三”方法插入成功。
OverrideTransactional(propagation Propagation.REQUIRED)public void transaction_required_requiresNew_requiresNew_exception_try(){User1 user1new User1();user1.setName(张三);user1Service.addRequired(user1);User2 user2new User2();user2.setName(李四);user2Service.addRequiresNew(user2);User2 user3new 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 {//省略其他...OverrideTransactional(propagation Propagation.NESTED)public void addNested(User1 user){user1Mapper.insert(user);}
}User2Service方法
Service
public class User2ServiceImpl implements User2Service {//省略其他...OverrideTransactional(propagation Propagation.NESTED)public void addNested(User2 user){user2Mapper.insert(user);}OverrideTransactional(propagation Propagation.NESTED)public void addNestedException(User2 user){user2Mapper.insert(user);throw new RuntimeException();}
}3.1 场景一
此场景外围方法没有开启事务。
验证方法1 Overridepublic void notransaction_exception_nested_nested(){User1 user1new User1();user1.setName(张三);user1Service.addNested(user1);User2 user2new User2();user2.setName(李四);user2Service.addNested(user2);throw new RuntimeException();}验证方法2 Overridepublic void notransaction_nested_nested_exception(){User1 user1new User1();user1.setName(张三);user1Service.addNested(user1);User2 user2new User2();user2.setName(李四);user2Service.addNestedException(user2);}分别执行验证方法结果
验证方法序号数据库结果结果分析1“张三”、“李四”均插入。外围方法未开启事务插入“张三”、“李四”方法在自己的事务中独立运行外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。2“张三”插入“李四”未插入。外围方法没有事务插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法插入“张三”方法不受影响。
结论通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.NESTED和Propagation.REQUIRED作用相同修饰的内部方法都会新开启自己的事务且开启的事务相互独立互不干扰。
3.2 场景二
外围方法开启事务。
验证方法1 // 张三、李四均未插入。
// 外围方法开启事务内部事务为外围事务的子事务外围方法回滚内部方法也要回滚。
TransactionalOverridepublic void transaction_exception_nested_nested(){User1 user1new User1();user1.setName(张三);user1Service.addNested(user1);User2 user2new User2();user2.setName(李四);user2Service.addNested(user2);throw new RuntimeException();}验证方法2 // 张三、李四均未插入。
// 外围方法开启事务内部事务为外围事务的子事务内部方法抛出异常回滚且外围方法感知异常致使整体事务回滚。
TransactionalOverridepublic void transaction_nested_nested_exception(){User1 user1new User1();user1.setName(张三);user1Service.addNested(user1);User2 user2new User2();user2.setName(李四);user2Service.addNestedException(user2);}验证方法3
// 张三插入、李四未插入。
// 外围方法开启事务内部事务为外围事务的子事务插入“李四”内部方法抛出异常可以单独对子事务回滚。
TransactionalOverridepublic void transaction_nested_nested_exception_try(){User1 user1new User1();user1.setName(张三);user1Service.addNested(user1);User2 user2new 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();}OverrideTransactional(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();}OverrideTransactional(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 ”被注释执行结果事务会提交id13\14\15\16的用户都会被删除
// 以下代码如果“ throw e ”不被注释执行结果事务会回滚id13\14\15\16的用户都不会被删除
// 总而言之只要异常被catch事务就会提交
TransactionalOverridepublic 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;}// 以下写法和上面是一样的
TransactionalOverridepublic 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;}// 以下写法和上面是一样的
TransactionalOverridepublic 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的异常
TransactionalOverridepublic void transactionalAndTry() {userMapper.deleteById(13);userMapper.deleteById(14);orderService.otherClassMethod(); }OverrideTransactionalpublic 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的异常;全部回滚
TransactionalOverridepublic void transactionalAndTry() {userMapper.deleteById(13);userMapper.deleteById(14);try{orderService.otherClassMethod(); }carch(Exception e){e.printStackTrace();}}OverrideTransactionalpublic User otherClassMethod() {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);return null;}无事务注解的方法 调用 不同类的有事务注解的方法
有事务注解的方法被事务管理无事务注解的方法不受事务约束
// 以下代码id13\14一定不会被事务管理 如果“ throw e ”被注释执行结果事务会提交id\15\16的用户都会被删除
// 以下代码id13\14一定不会被事务管理 如果“ throw e ”不被注释执行结果事务会回滚id\15\16的用户不会被删除 Overridepublic void transactionalAndTry() {userMapper.deleteById(13);try {userMapper.deleteById(14);} catch (Exception e) {e.printStackTrace();}orderService.deleteUserById();}TransactionalOverridepublic 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个方法在同一个类但事务注解在外层方法事务生效
TransactionalOverridepublic 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个方法在同一个类但事务注解在内层方法事务失效
// id13\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 的实现原理是AOPAOP的实现原理是动态代理而自调用时并不存在代理对象的调用也就不会产生基于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个方法在不同的类
TransactionalOverridepublic void transactionalAndTry() {userMapper.deleteById(13);userMapper.deleteById(14);try {orderService.deleteUserById();} catch (Exception e) {e.printStackTrace();System.out.println(user服务异常);
// throw e;}}TransactionalOverridepublic User deleteUserById() {userMapper.deleteById(15);userMapper.deleteById(16);System.out.println(1 / 0);return null;}
transactionalAndTry() 以下称为A方法 ; 方法中调用了deleteUserById() 以下称为B方法.
上述代码可以触发回滚异常的报错
两个方法都加了事务注解并且两个方法都会受到到事务管理的拦截器增强并且事务传播的方式都是默认的也就是REQUIRED当已经存在事务的时候就加入事务没有就创建事务。这里A和B都受事务控制并且是处于同一个事务的。
A调用BA中抓了B的异常当B发生异常的时候B的操作应该回滚但是A吃了异常A方法中没有产生异常所以A的操作又应该提交二者是相互矛盾的。
spring的事务关联拦截器在抓到B的异常后就会标记rollback-only为true当A执行完准备提交后发现rollback-only为true也会回滚并抛出异常告诉调用者。 处理方式且不可直接把方法B的事务去掉因为方法B可能被其他controller直接调用即使没调用把项目中的事务去掉都是危险的。 解决还是根据业务处理具体是要回滚还是处理异常是A和B保持一致的回滚和提交标记。
5. 分布式事务