Java中Spring对事务的支持详解

 更新时间:2023年07月11日 11:04:08   作者:蓝云飘飘2366  
这篇文章主要介绍了Java中Spring对事务的支持详解,Spring对事务的支持有两种方式,一是自己编写事务,精确控制事务的边界,二是采用声明事务的方式,使用AOP来完成,需要的朋友可以参考下

事务属性

  • 事务传播行为;--Propagation(本文重点解读事务传播行为)
  • 事务隔离级别;--Isolation
  • 事务超时;--timeout
  • 只读事务;--readOnly
  • 设置出现哪些异常回滚事务;--rollbackFor
  • 设置出现哪些异常不回滚事务;--noRollbackFor

事务的传播行为

一、什么是事务的传播行为

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

注意:以下事务传播属性都是打在内部方法b()方法上的事务注解

二、7种事务传播行为

Spring 可以通过 @Transactional 注解的 propagation 属性来设置不同的传播行为策略。

Spring 为此提供了一个枚举类 Propagation,源码如下:

package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
   /**
    * 需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,
    * 否则新建一个事务运行内部方法
    */
   REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
   /**
    * 支持事务,如果当前存在事务,就沿用当前事务,
    * 如果不存在,则继续采用无事务的方式运行内部方法
    */
   SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
   /**
    * 必须使用事务,如果当前没有事务,则会抛出异常,
    * 如果存在当前事务,则沿用当前事务
    */
   MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
   /**
    * 无论当前事务是否存在,都会创建新事务运行方法,
    * 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
    */
   REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
   /**
    * 不支持事务,当前存在事务时,将挂起事务,运行方法
    */
   NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
   /**
    * 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
    */
   NEVER(TransactionDefinition.PROPAGATION_NEVER),
   /**
    * 在当前方法调用内部方法时,如果内部方法发生异常,
    * 只回滚内部方法执行过的 SQL ,而不回滚当前方法的事务
    */
   NESTED(TransactionDefinition.PROPAGATION_NESTED);
......
}

接下来我们通过对其中三种最常用的(REQUIRED、REQUIRES_NEW、NESTED)策略进行对比来更深入的理解。以下测试均在外部方法开启事务的情况下进行,因为在外部没有事务的情况下,三者都会新建事务,效果一样。 

1. REQUIRED

当内部方法的事务传播行为设置为 REQUIRED 时,内部方法会加入外部方法的事务。我们在 UserServiceImpl 中添加如下方法:  

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
   @Autowired
   private UserMapper mapper;
   @Override
   @Transactional(propagation = Propagation.REQUIRED)
   public void addWithRequired(User user) {
       mapper.insert(user);
  }
   @Override
   @Transactional(propagation = Propagation.REQUIRED)
   public void addWithRequiredAndException(User user) {
       mapper.insert(user);
       throw new RuntimeException();
  }
}

创建 TransactionServiceImpl 类,并添加如下方法:

@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
    @Autowired
    private UserService userService;
    @Override
    public void noTransaction_required_required_externalException() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequired(xiaoJing);
        throw new RuntimeException();
    }
    @Override
    public void noTransaction_required_requiredException() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredAndException(xiaoJing);
    }
    @Override
    @Transactional
    public void transaction_required_required_externalException() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequired(xiaoJing);
        throw new RuntimeException();
    }
    @Override
    @Transactional
    public void transaction_required_requiredException() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredAndException(xiaoJing);
    }
    @Override
    @Transactional
    public void transaction_required_requiredException_try() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        try {
            userService.addWithRequiredAndException(xiaoJing);
        } catch (Exception e) {
            log.error("发生异常,事务回滚!");
        }
    }
}

结果分析如下表所示:

前面四种情况都比较好理解,很多人不能理解最后一种情况:我都 try-catch 了你还想怎样?这里的关键点在于所有方法都处于同一个事务中,此时「小镜」的插入方法发生异常,那么这个方法所在的事务就会被 Spring 设置为 rollback 状态。因为异常被 catch 了,所以外部方法执行完要进行 commit 操作,这时却发现当前事务已经处于 rollback 状态了,虽然它不知道哪里出了问题,但也只能听从指挥,回滚所有操作了。

PS:由于外部方法不开启事务的情况,在每种传播行为下结果都是类似的,所以后面不再给出示例。

2. REQUIRES_NEW

当内部方法的传播行为设置为 REQUIRES_NEW 时,内部方法会先将外部方法的事务挂起,然后开启一个新的事务 。在 UserServiceImpl 中添加如下方法(UserServiceImpl 类中上面的方法还在哦):

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    ...
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addWithRequiredNew(User user) {
        mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addWithRequiredNewAndException(User user) {
        mapper.insert(user);
        throw new RuntimeException();
    }
}

在 TransactionServiceImpl 中添加如下方法:

@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
    ...
    @Override
    @Transactional
    public void transaction_required_requiredNew_externalException() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredNew(xiaoJing);
        throw new RuntimeException();
    }
    @Override
    @Transactional
    public void transaction_required_requiredNew_requiredNewException() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        User shuiJing = new User().setName("水镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredNew(xiaoJing);
        userService.addWithRequiredNewAndException(shuiJing);
    }
    @Override
    @Transactional
    public void transaction_required_requiredNewException_try() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        User shuiJing = new User().setName("水镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithRequiredNew(xiaoJing);
        try {
            userService.addWithRequiredNewAndException(shuiJing);
        } catch (Exception e) {
            log.error("发生异常,事务回滚!");
        }
    }
}

结果分析如下表所示:

3. NESTED

当内部方法的传播行为设置为 NESTED 时,内部方法会开启一个新的嵌套事务(子事务)。在 UserServiceImpl 中添加如下方法:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    ...
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addWithNested(User user) {
        mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addWithNestedAndException(User user) {
        mapper.insert(user);
        throw new RuntimeException();
    }
}

在 TransactionServiceImpl 中添加如下方法:  

@Slf4j
@Service
public class TransactionServiceImpl implements TransactionService {
    ...
    @Override
    @Transactional
    public void transaction_nested_nested_externalException() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithNested(xiaoShui);
        userService.addWithNested(xiaoJing);
        throw new RuntimeException();
    }
    @Override
    @Transactional
    public void transaction_nested_nestedException() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        userService.addWithNested(xiaoShui);
        userService.addWithNestedAndException(xiaoJing);
    }
    @Override
    @Transactional
    public void transaction_nested_nestedException_try() {
        User xiaoShui = new User().setName("小水");
        User xiaoJing = new User().setName("小镜");
        User shuiJing = new User().setName("水镜");
        userService.addWithRequired(xiaoShui);
        userService.addWithNested(xiaoJing);
        try {
            userService.addWithNestedAndException(shuiJing);
        } catch (Exception e) {
            log.error("发生异常,事务回滚!",e);
        }
    }
}

结果分析如下表所示:

每个 NESTED 事务执行前会将当前操作保存下来,叫做 savepoint (保存点),如果当前 NESTED 事务执行失败,则回滚到之前的保存点,保存点使得子事务的回滚不对主事务造成影响。NESTED 事务在外部事务提交以后自己才会提交。

4. 总结

REQUIRES_NEW 最为简单,不管当前有无事务,它都会开启一个全新事务,既不影响外部事务,也不会影响其他内部事务,真正的井水不犯河水,坚定而独立。

REQUIRED 在没有外部事务的情况下,会开启一个独立的新事务,且不会对其他同级事务造成影响;而当存在外部事务的情况下,则会与外部事务同生共死。

NESTED 在没有外部事务的情况下与 REQUIRED 效果相同;而当存在外部事务的情况下,当外部事务回滚时,它会创建一个嵌套事务(子事务)。外部事务回滚时,子事务会跟着回滚;但子事务的回滚不会对外部事务和其他同级事务造成影响。

三、事务的传播行为(理解记忆)

到此这篇关于Java中Spring对事务的支持详解的文章就介绍到这了,更多相关Spring对事务的支持内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java跳出多重嵌套循环代码实例

    Java跳出多重嵌套循环代码实例

    这篇文章主要介绍了Java跳出多重嵌套循环,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-03-03
  • 使用maven war包打包去除jar包瘦身

    使用maven war包打包去除jar包瘦身

    这篇文章主要介绍了使用maven war包打包去除jar包瘦身操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Java 线程池详解及实例代码

    Java 线程池详解及实例代码

    这篇文章主要介绍了Java 线程池的相关资料,并符实例代码,帮助大家学习参考,需要的朋友可以参考下
    2016-09-09
  • Spring Boot 的创建和运行示例代码详解

    Spring Boot 的创建和运行示例代码详解

    Spring Boot 的诞生是为了简化Spring程序的开发,今天给大家介绍下Spring Boot 的创建和运行,主要包括Spring Boot基本概念和springboot优点,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2022-07-07
  • 重新实现hashCode()方法

    重新实现hashCode()方法

    hashCode()是Java中的一个重要方法,用于计算对象的哈希码。本文介绍了如何重新实现hashCode()方法,包括使用对象的属性计算哈希码、使用字符串拼接计算哈希码、使用随机数计算哈希码等方法。同时,还介绍了如何避免哈希冲突,提高哈希表的效率。
    2023-04-04
  • Java Eclipse进行断点调试的方法

    Java Eclipse进行断点调试的方法

    本篇文章主要介绍了Java Eclipse进行断点调试的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • 浅谈Java自定义注解和运行时靠反射获取注解

    浅谈Java自定义注解和运行时靠反射获取注解

    下面小编就为大家带来一篇浅谈Java自定义注解和运行时靠反射获取注解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-11-11
  • JAVA中实现原生的 socket 通信机制原理

    JAVA中实现原生的 socket 通信机制原理

    本篇文章主要介绍了JAVA中实现原生的 socket 通信机制原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • Java中将一个列表拆分为多个较小列表的三种不同方法

    Java中将一个列表拆分为多个较小列表的三种不同方法

    有时候我们需要将大集合按指定的数量分割成若干个小集合,这篇文章主要给大家介绍了关于Java中将一个列表拆分为多个较小列表的三种不同方法,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-09-09
  • springcloud教程之zuul路由网关的实现

    springcloud教程之zuul路由网关的实现

    这篇文章主要介绍了springcloud教程之zuul路由网关的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02

最新评论