事务的传播行为、声明式事务和编程式事务、异常失效、事务错误使用、分布式事务

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:

  1. 修改addRequiredException 为 addRequiredExceptionAndCatch
    @Override@Transactional(propagation = Propagation.REQUIRED)public void addRequiredExceptionAndCatch(User2 user){user2Mapper.insert(user);try {throw new RuntimeException();} catch (Exception e) {}}
  1. 测试
// 事务提交 张三、李四全部插入;插入李四的异常被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.NESTEDPropagation.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保持一致的回滚和提交标记。

5. 分布式事务

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/136353.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode做题笔记2586. 统计范围内的元音字符串数

给你一个下标从 0 开始的字符串数组 words 和两个整数:left 和 right 。 如果字符串以元音字母开头并以元音字母结尾,那么该字符串就是一个 元音字符串 ,其中元音字母是 a、e、i、o、u 。 返回 words[i] 是元音字符串的数目,其中…

Cordova插件开发三:通过广播实现应用间跨进程通信

文章目录 1.最终效果预览2.数据发送3.插件接受数据4.JS页面中点击获取数据返回1.最终效果预览 场景说明:我们给自来水公司开发了一个h5应用,需要对接第三方厂家支持硬件设备以便于获取到高精度定位数据,之前几篇文件写过,我已经集成过南方测绘RTK和高精度定位模块的设备,厂…

[unity]切换天空盒

序 unity是自带天空盒的: 但有的时候不想用自带的。怎么自定义?如何设置? 官方文档 Unity - Manual: The Lighting window (unity3d.com) 相关窗口的打开方法 天空盒对应的选项 实际操作 从标准材质球到天空盒材质球 新建一个材质球&…

cookie 里面都包含什么属性?

结论先行: Cookie 中除了名称和值外,还有几个比较常见的,例如: Domain 域:指定了 cookie 可以发送到哪些域,只有发送到指定域或其子域的请求才会携带该cookie; Path 路径:指定哪些…

互联网常见职称

1、管理层 CEO – Chief Executive Officer 首席执行官 VP – Vice President 副总裁 HRD – Humen Resource Director 人力资源总监 OD – Operations Director 运营总监 MD – Marketing Director 市场总监 GM – General Manager 总经理 PM – Production Manager 产品…

stm32 Bootloader设计(YModem协议)

stm32 Bootloader设计(YModem协议) Chapter1 stm32 Bootloader设计(YModem协议)YModem协议:STM32 Bootloader软件设计STM32 Bootloader使用方法准备工作stm32 Bootloader修改:stm32目标板程序.bin偏移地址修改: Chapt…

【PG】postgreSQL参数解释二 WRITE-AHEAD LOG部分

目录 1. 设置 wal_level (enum) -- WAL日志的生成级别 -- replica fsync (boolean) -- 更新是否持久化写入到磁盘 -- on synchronous_commit (enum) -- 事务提交的同步性 -- on wal_sync_method (enum) -- WAL 更新到磁盘的方法 -- fdatasync full_page_writes (boolean) …

服务器部署 Nacos 获取不到配置浏览器可以访问

服务器部署 Nacos 获取不到配置浏览器可以访问 📔 千寻简笔记介绍 千寻简笔记已开源,Gitee与GitHub搜索chihiro-notes,包含笔记源文件.md,以及PDF版本方便阅读,且是用了精美主题,阅读体验更佳&#xff0c…

样式问题解决

1.深度样式选择器 1.vue2中 原生css >>> .el-card__header saas\scss ::v-deep .el-card__header less /deep/ .el-card__header 2.vue3中 :deep() { //styles } ::deep() { //styles } 2.修改element.style样式 3.用户代理样式表样式修改 用户代理样式表是浏…

在现实生活中传感器GV-H130/GV-21的使用

今天,收获了传感器GV-H130/GV-21,调试探头的用法,下面就来看看吧!如有不妥欢迎指正!!!! 目录 传感器GV-H130/GV-21外观 传感器调试探头 探头与必要准备工作 传感器数值更改调试 …

HTML使用canvas绘制海报(网络图片)

生成前&#xff1a; 生成后&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>媒体参会嘉宾邀请函生成链接</title><link rel"stylesheet" href"https://cdn.jsdelivr.net/npm/vant2.10…

vr地铁消防虚拟逃生自救系统降低财产及人员伤害

无论是在公共场所还是在家中&#xff0c;火灾都是一种常见的突发事件。这往往会严重影响到人们的财产和生命安全。因此&#xff0c;如何预防火灾和安全逃生就成为了非常重要的话题。这款VR模拟火灾疏散逃生系统&#xff0c;帮助人们了解火灾逃生的技巧以及正确的应对方法。 以传…

记录undefined reference to `SSLv3_client_method‘错误笔记

libcurl.a(libcurl_la-openssl.o): in function ossl_connect_step1: openssl.c:(.text0x3ca8): undefined reference to SSLv3_client_method 我个人环境原因&#xff1a;编译选项指定了某个openssl目录&#xff0c;此目录下有libcrypto.a libssl.a 解决方法&#xff1a;…

【操作系统】考研真题攻克与重点知识点剖析 - 第 2 篇:进程与线程

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

【星海出品】flask 与docker

import os from flask import Flask, request from flask import Response, make_response, jsonify import cv2 import base64 import io import uuid from main import eye ​ app Flask(__name__)​ app.route(/, methods[GET, POST]) # 添加路由blend def upload_file():…

Go语言函数方法

Go语言函数方法 在Go语言中&#xff0c;函数可以关联到自定义类型&#xff0c;从而创建方法。方法是在类型上调用的函数&#xff0c;它们使类型具有方法集。以下是关于Go语言函数方法的一些重要信息&#xff1a; 函数方法的定义&#xff1a; 在Go语言中&#xff0c;方法与函数…

【Agent模型1】MemGPT: Towards LLMs as Operating Systems

论文标题&#xff1a;MemGPT: Towards LLMs as Operating Systems 论文作者&#xff1a;Charles Packer, Vivian Fang, Shishir G. Patil, Kevin Lin, Sarah Wooders, Joseph E. Gonzalez (UC Berkeley) 论文原文&#xff1a;https://arxiv.org/abs/2310.08560 论文出处&#x…

MySQL库的库操作指南

1.创建数据库 一般格式&#xff1a;create database (if not exists) database1_name,database2_name...... 特殊形式&#xff1a; create database charset harset_name collate collate_name 解释&#xff1a; 红色字是用户自己设置的名称charset&#xff1a;指定数据…

Netty入门指南之NIO Buffer详解

作者简介&#xff1a;☕️大家好&#xff0c;我是Aomsir&#xff0c;一个爱折腾的开发者&#xff01; 个人主页&#xff1a;Aomsir_Spring5应用专栏,Netty应用专栏,RPC应用专栏-CSDN博客 当前专栏&#xff1a;Netty应用专栏_Aomsir的博客-CSDN博客 文章目录 参考文献前言ByteBu…

XSS 跨站点脚本漏洞详解

文章目录 漏洞概述XSS漏洞原理xss漏洞危害xss漏洞验证XSS漏洞分类反射型存储型DOM型 固定会话攻击原理简单xss注入复现 XSS 攻防xss构造方法利用标签符号<>事件响应javascript伪协议其他标签 XSS 变形方式xss防御黑白名单策略输入过滤 案例XSS 盲打 漏洞概述 ​ 跨站点脚…