Spring七大事务传递机制深入分析实现原理

作者:一路向阳向北 时间:2022-12-21 16:28:37 

Spring事务传递机制原理

首先,我们通过org.springframework.transaction.annotation.Propagation来了解一下spring事务的传播定义:

1. REQUIRED(默认):

Support a current transaction, create a new one if none exists.

支持当前事务,如果没有则创建一个新的

2. SUPPORTS

Support a current transaction, execute non-transactionally if none exists.

支持当前事务,如果没有则不使用事务

3. MANDATORY

Support a current transaction, throw an exception if none exists

支持当前事务,如果没有事务则报错

4. REQUIRED_NEW

Create a new transaction, and suspend the current transaction if one exists.

新建一个事务,同时将当前事务挂起

5. NOT_SUPPORTED

Execute non-transactionally, suspend the current transaction if one exists

以无事务的方式执行,如果当前有事务则将其挂起

6. NEVER

Execute non-transactionally, throw an exception if a transaction exists.

以无事务的方式执行,如果当前有事务则报错

7. NESTED

Execute within a nested transaction if a current transaction exists,behave like PROPAGATION_REQUIRED else

如果当前有事务,则在当前事务内部嵌套一个事务,内部事务的回滚不影响当前事务。如果当前没有事务,就相当于REQUIRED

Note: Actual creation of a nested transaction will only work on specific transaction managers. Out of the box, this only applies to

the JDBC DataSourceTransactionManager when working on a JDBC 3.0 driver.

Some JTA providers might support nested transactions as well.

注意:该定义只能在JDBC3.0驱动下的DataSourceTransactionManager事务管理器中使用,有些JTA事务可能也会支持

接下来我们通过代码验证一下spring事务的传递性,在UserServiceImpl类添加两个方法如下:

@Transactional(propagation = Propagation.NEVER)
public User findById(Long id) {
   User user =  userMapper.findById(id);
   System.out.println("find user:"+user);
   return user;
}
@Transactional
public void transactionTest(int t) {
   findById(t+0L);
}

我们调用transactionTest方法,transactionTest没有配置Propagation,所以默认是REQUIRED,会在当前新建一个事务。transactionTest内部调用findById,由于findById事务传播定义为NEVER,表明它当前不能有事务,按理说这里会抛出异常,但是我们利用junit执行后发现,transactionTest是可以正常执行的。

事实上,如果使用@Transaction方法里嵌套调用的是同一个类的方法,spring代理会忽略嵌套方法的@Transaction配置。但是,如果是其他注入对象的方法,那么@Transaction配置就会生效。我们将上面的transactionTest方法的事务传播定义为NERVER,并新增一个insert操作,即使insert启用了事务并且抛出异常,但是事务不会生效,也不会有回滚的说法,程序会抛出异常但是数据会保存到数据库中:

@Transactional(propagation = Propagation.NEVER)
public void transactionTest(int t) {
   findById(t+0L);
   insertUser("huangxl","abc123");
}
@Transactional
public int insertUser(String name, String password) {
   User user = new User();
   user.setPassword(password);
   user.setUsername(name);
   int insertCount =  userMapper.insertEntity(user);
   if(insertCount == 1 ){
       throw new RuntimeException("test transaction roll back");
   }
   return insertCount;
}

接下来我们来测试不同类之间的方法(事务)调用,以下的测试都是基于junit执行TransactionTestServiceImpl.test()方法

一、Propagation.NERVER的测试

下面我们将UserService注入到TransactionTestServiceImpl中,test方法使用@Transactional,UserService findById事务传播定义不变,还是NERVER。

UserserviceImpl:
@Service
public class TransactionTestServiceImpl implements TransactionTestService {
   @Autowired
   private UserService userService;
   @Override
   @Transactional
   public void test() {
       userService.findById(1L);
   }
}
TransactionTestServiceImpl:
@Service
public class UserServiceImpl implements UserService {
   @Override
   @Transactional(propagation = Propagation.NEVER)
   public User findById(Long id) {
       User user =  userMapper.findById(id);
       System.out.println("find user:"+user);
       return user;
   }
}

由于test默认启用了事务,findById不允许当前有事务,所以我们执行test方法后会发现程序抛出了异常:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’

结论:

NERVER 不允许当前存在事务

二、Propagation.REQUIRED的测试

UserserviceImpl:
@Transactional
public int insertUser(String name, String password) {
   User user = new User();
   user.setPassword(password);
   user.setUsername(name);
   int insertCount =  userMapper.insertEntity(user);
   if(insertCount == 1 ){
       throw new RuntimeException("test transaction roll back");
   }
   return insertCount;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
   try {
       userService.insertUser("abc", "123");
   } catch (Exception e) {
       //do Nothing
   }
   userMapper.updateUserPassWord(1L, "456");
}

我们会发现,即使捕获了userService.insertUser抛出的异常,test还是把insertUser和updateUserPassword操作当成是一个整体,整个事务还是回滚了,程序抛出了下面的异常:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

结论:

REQUIRED子事务会影响当前事务的提交、回滚

三、Propagation.NESTED的测试

UserserviceImpl:
@Transactional(propagation = Propagation.NESTED)
public int insertUser(String name, String password) {
   User user = new User();
   user.setPassword(password);
   user.setUsername(name);
   int insertCount =  userMapper.insertEntity(user);
   if(insertCount == 1 ){
       throw new RuntimeException("test transaction roll back");
   }
   return insertCount;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
   try {
       userService.insertUser("abc", "123");
   } catch (Exception e) {
       //do Nothing
   }
   userMapper.updateUserPassWord(1L, "456");
}

程序正常运行,因为NESTED内部事务回滚不影响外部事务。假如这个时候我们把test的@Transactional去掉再运行test方法,发现insertUser没有插入用户信息,说明当前没有事务的情况下,NESTED会默认创建一个事务,类似于REQUIRED。

如果我们把程序改为下面的情况:

UserserviceImpl:
@Transactional(propagation = Propagation.NESTED)
public int insertUser(String name, String password) {
   User user = new User();
   user.setPassword(password);
   user.setUsername(name);
   int insertCount =  userMapper.insertEntity(user);
   return insertCount;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
   userService.insertUser("abc", "123");
   int updateRow = userMapper.updateUserPassWord(1L, "456");
   if (updateRow == 1) {
       throw new RuntimeException("transational roll back");
   }
}

我们会发现没有插入用户信息,当前事务和子事务全部回滚。

结论:

NESTED子事务回滚不会影响当前事务的提交(catch回滚异常的情况下),但是当前事务回滚会回滚子事务。也就是说只有当前事务提交成功了,子事务才会提交成功。

四、Propagation.REQUIRED_NEW的测试

UserserviceImpl:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertUser(String name, String password) {
   User user = new User();
   user.setPassword(password);
   user.setUsername(name);
   int insertCount =  userMapper.insertEntity(user);
   return insertCount;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
   userService.insertUser("abc", "123");
   int updateRow = userMapper.updateUserPassWord(1L, "456");
   if (updateRow == 1) {
       throw new RuntimeException("transational roll back");
   }
}

运行结果:程序报错,但是有用户信息插入。

将程序改为下面的样子:

UserserviceImpl:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int updateUserPassWorld(Long id, String password) {
   int update =  userMapper.updateUserPassWord(id,password);
   return update;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
   //当前事务
   userMapper.updateUserPassWord(28L, "123456");
   //执行REQUIRES_NEW事务
   userService.updateUserPassWorld(28L, "000000");
  System.out.println("commit");
}

执行程序,发现程序迟迟没有打印字符串commit,发生了死锁。

结论:

REQUIRES_NEW会启用一个新的事务,事务拥有完全独立的能力,它不依赖于当前事务,执行时会挂起当前事务,直到REQUIRES_NEW事务完成提交后才会提交当前事务,如果当前事务与REQUIRES_NEW 存在锁竞争,会导致死锁。

五、NOT_SUPPORTED的测试

UserserviceImpl:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int updateUserPassWorld(Long id, String password) {
   int updateRow =  userMapper.updateUserPassWord(id,password);
if(updateRow ==1 ){
   throw new RuntimeException("roll back test");
}
return updateRow;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
   userService.updateUserPassWorld(28L, "000000");
}

程序运行报错,但是id为28的用户密码还是更新了。

将程序改为下面这个情况:

UserserviceImpl:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int updateUserPassWorld(Long id, String password) {
   int update =  userMapper.updateUserPassWord(id,password);
   return update;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
   //当前事务
   userMapper.updateUserPassWord(28L, "123456");
   //执行REQUIRES_NEW事务
   userService.updateUserPassWorld(28L, "000000");
  System.out.println("commit");
}

执行程序,发现程序迟迟没有打印字符串commit,发生了死锁。

结论:

NOT_SUPPORTED会挂起当前事务,并且NOT_SUPPORTED定义的方法内部不启用显示事务,如果NOT_SUPPORTED和当前事务存在锁竞争,会发生死锁。

六、NOT_SUPPORTED的测试

UserserviceImpl:
@Transactional(propagation = Propagation.MANDATORY)
public int updateUserPassWorld(Long id, String password) {
   int updateRow =  userMapper.updateUserPassWord(id,password);
   return updateRow;
}
TransactionTestServiceImpl:
public void test() {
   userService.updateUserPassWorld(28L, "123456");
}

程序运行错误:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’

结论:

MANDATORY必须包含在事务中,如果事务不存在,则抛出异常

来源:https://blog.csdn.net/weixin_36665875/article/details/129759753

标签:Spring,事务传递,机制
0
投稿

猜你喜欢

  • Java异常--常见方法--自定义异常--增强try(try-with-resources)详解

    2021-10-31 07:17:42
  • unity实现翻页按钮功能

    2021-07-15 03:33:47
  • Java-JFrame窗体美化方式

    2022-08-17 10:07:38
  • springboot结合maven配置不同环境的profile方式

    2022-05-28 12:00:16
  • RabbitMQ消息有效期与死信的处理过程

    2023-11-10 20:29:42
  • JavaWeb文件上传下载功能示例解析

    2021-09-20 08:28:58
  • c#序列化详解示例

    2022-09-12 02:39:00
  • C++初阶教程之类和对象

    2021-07-24 18:53:46
  • Eclipse快速添加get、set方法的操作技巧

    2022-11-20 04:11:59
  • Java List的sort()方法改写compare()实现升序,降序,倒序的案例

    2021-12-30 06:22:58
  • 解析Android中string-array数据源的简单使用

    2022-12-19 10:06:53
  • 使用cmd根据WSDL网址生成java客户端代码的实现

    2022-09-12 11:00:20
  • c#进程之间对象传递方法

    2022-04-22 09:41:10
  • 关于@CacheEvict无法解决分页缓存清除的解决思路

    2023-07-06 20:22:44
  • Java Maven构建工具中mvnd和Gradle谁更快

    2022-02-15 04:39:52
  • 使用JDBC实现数据访问对象层(DAO)代码示例

    2021-11-12 23:33:46
  • Android checkbox的listView具体操作方法

    2023-10-10 06:58:33
  • Java Runtime的使用详解

    2021-10-23 06:46:39
  • MyBatis 多个条件使用Map传递参数进行批量删除方式

    2023-11-29 08:09:01
  • Java集合之Set接口及其实现类精解

    2022-01-23 17:27:44
  • asp之家 软件编程 m.aspxhome.com