• 【Spring事务】事务和事务传播机制


    事务

    事务主要有三种操作:

    1. 开始事务 start transaction
    2. 提交事务 commit
    3. 回滚事务 rollback

    Spring 中事务的实现

    Spring 中的事务操作分为两类:

    1. ⼿动操作事务。
    2. 声明式⾃动提交事务.

    1.⼿动操作事务

    SpringBoot 内置了两个对象,
    DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或 回滚事务的,
    ⽽ TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus,实现代码如下:

    	@Autowired  
        DataSourceTransactionManager transactionManager;  
      
        @Autowired  
        TransactionDefinition definition;  
      
      
        @RequestMapping("/blogSubmit")  
        public String blogSubmit(String title, String content,@SessionAttribute(value = "user",required = false) User user)  {
    	    //通过TransactionDefinition属性来获取TransactionStatus对象  
            TransactionStatus transactionStatus=transactionManager.getTransaction(definition);  
            Blog blog=new Blog();  
            blog.setTitle(title);  
            blog.setContent(content);  
            blog.setUserid(user.getUserId());  
            blog.setCategory("未定");  
            blogService.insert(blog);  
    //        transactionManager.commit(transactionStatus);  
            transactionManager.rollback(transactionStatus);  
            return "redirect:/Blog_List.html";  
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2. Spring 声明式事务(⾃动事务)

    声明式事务的实现很简单,只需要在需要的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动 开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务,具体实现代码如下:

    @RequestMapping("/blogSubmit")  
    @Transactional  
    public String blogSubmit(String title, String content,@SessionAttribute(value = "user",required = false) User user)  {  
        Blog blog=new Blog();  
        blog.setTitle(title);  
        blog.setContent(content);  
        blog.setUserid(user.getUserId());  
        blog.setCategory("未定");  
        blogService.insert(blog);  
        return "redirect:/Blog_List.html";  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当使用@Transactional修饰类的时候,如果执行的时候没有任何问题,就会自动的正常提交,如果执行的时候程序出现了异常,那么就会自动发生回滚.

    @RequestMapping("/blogSubmit")  
    @Transactional  
    public String blogSubmit(String title, String content,@SessionAttribute(value = "user",required = false) User user)  {  
        Blog blog=new Blog();  
        blog.setTitle(title);  
        blog.setContent(content);  
        blog.setUserid(user.getUserId());  
        blog.setCategory("未定");  
        blogService.insert(blog);  
        int a=10/0;  
        return "redirect:/Blog_List.html";  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对于这种问题,程序的代码中出现了异常,所以这个插入博客的这个事务不会正常执行

    所以,使用@transaction注解是非常的好用的,对于正常的没有错误的事务,正常提交;对于出现异常的事务,自动回滚.

    同时,@transaction还提供了许多不同功能的参数:

    1. value和transactionManager
      当有多个事务管理器的时候,我们可以使用该属性来选择哪一个事务管理器

    2. propagation

      可以规定事务的传播机制,默认是Propagation.REQUIRED

    3. isolation

      事务的隔离级别,默认是Isolation.DEFAULT

    Spring中事务的隔离级别一共有以下五种:

    public enum Isolation {  
        DEFAULT(-1),  
        READ_UNCOMMITTED(1),  
        READ_COMMITTED(2),  
        REPEATABLE_READ(4),  
        SERIALIZABLE(8);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. default 默认级别 指Spring的级别和对应的数据库种的级别一致
    2. read uncommit 读不提交
    3. read commit 读提交
    4. repeatable commit 可重复读
    5. Serialization 串行化
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @Transactional(isolation = Isolation.DEFAULT)
    
    • 1
    1. timeout 设置隔离时间,如果超出时间的话,就回滚,默认是-1
    @Transactional(timeout = 1)
    
    • 1
    1. readOnly 指定事务是只读的,默认值是false,如果开启为true,表示这个事务只允许读,不可以修改.

    这样可以避免加锁解锁的时间消耗

    1. rollbackFor和rollbackForClassName

      抛出异常的类型,回滚事务

    2. noRollbackFor和noRollbackForClassName

      抛出异常的类型,不回滚事务

    但是这个@Transaction注解还有一个例外,那就是被捕获的异常不会回滚,因为它任务这个异常已经被解决掉了,所以不需要回滚了,是安全的

    @RequestMapping("/blogSubmit")  
    @Transactional  
    public String blogSubmit(String title, String content,@SessionAttribute(value = "user",required = false) User user)  {  
        Blog blog=new Blog();  
        blog.setTitle(title);  
        blog.setContent(content);  
        blog.setUserid(user.getUserId());  
        blog.setCategory("未定");  
        blogService.insert(blog);  
        try {  
            int a=10/0;  
        }catch (Exception e){  
      
        }  
        return "redirect:/Blog_List.html";  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    对异常进行try catch之后,就不会回滚了

    如何让被捕获的异常也回滚呢?有以下两种方法:

    1. 再throw出去
    try {  
        int a=10/0;  
    }catch (Exception e){  
        throw e;  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()函数
    try {  
        int a=10/0;  
    }catch (Exception e){  
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    @Transaction的原理

    @Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝, @Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇 到的异常,则回滚事务。

    Spring的事务传播机制

    Spring 事务传播机制定义了多个包含了事务的⽅法,相互调⽤时,事务是如何在这些⽅法间进⾏传递 的。

    事务传播机制和事务隔离机制的区别是什么?

    事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的),
    ⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)。

    种类

    Spring 事务传播机制包含以下 7 种:

    1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务
    2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的 ⽅式继续运⾏。
    3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当 前没有事务,则抛出异常。
    4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂 起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开 启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
    5. Propagation.NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
    6. Propagation.NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
    7. Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED

    在这里插入图片描述

    具体演示

    下面以一个例子来观察再不同的传播机制下,事务之间的影响是什么样子的.

    以下代码实现中,先开启事务先成功插⼊⼀条⽤户数据,然后再执⾏⽇志报错,⽽在⽇志报错是发⽣了 异常:

    UserController:

    	@RestController 
    	public class UserController { 
    	   @Resource    
    	   private UserService userService;    
    	   @Resource    
    	   private LogService logService;    
    	   @RequestMapping("/save")    
    	   @Transactional(propagation = Propagation.REQUIRED)    
    	   public Boolean save(User user) {        
    		   userService.save(user);           
    		   logService.saveLog("⽤户插⼊:" + user.getName());
    			return true;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    UserService:

    @Service 
    public class UserService {    
    
    	@Resource    
    	private UserMapper userMapper;     
        @Transactional(propagation = Propagation.REQUIRED)     
    	public int save(User user) {         
             System.out.println("执⾏ save ⽅法.");        
             return userMapper.save(user);   
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    logService:

    @Service 
    public class LogService {    
    
    	@Resource    
    	private LogMapper LogMapper;     
        @Transactional(propagation = Propagation.REQUIRED)     
    	public int save(Log log) {         
             System.out.println("执⾏ save ⽅法.");        
             return LogMapper.save(log);   
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 当传播机制的类型是REQUIRED的时候,表示后来的事务都加入原来的事务.所以,当日志发送异常回滚之后,因为它们已经成一体了,所以用户插入也会失败,也会回滚
    2. 当传播机制的类型是REQUIRED_NEW的时候,因为这个不使用当前事务,而是新创建一个事务,所以日志事务和用户事务都是相互独立的.日志事务发生回滚不会影响用户事务的正常提交
    3. 当传播机制的类型是NESTY的时候,也就是嵌套机制时,当日志发送错误的时候,也只是回滚日志,用户事务正常执行.这是因为嵌套机制在嵌套的时候会有一个连接点,每次回滚之后都会回到相应的连接点,不会全部回滚
    4. 当传播机制的类型是NOT_SUPPORTED的时候,也就是不支持事务的类型.就算是日志出现了异常,但是因为不是以事务的机制运行的,所以就会正常的提交,不会发生回滚
  • 相关阅读:
    Java Annotation Processor注解处理器如何Debug
    ONLYOFFICE 8.1版本桌面编辑器测评
    关于JS进行大文件分片上传碰到的问题
    es6相关面试题
    CefSharp自定义缓存实现
    vue之elementui等表格单元格列合并
    【HttpRunnerManager】搭建接口自动化测试平台实战
    动态内存申请
    Taurus.MVC WebMVC 入门开发教程7:部分视图和页面片段(结束篇)
    Docker 安装使用操作指南
  • 原文地址:https://blog.csdn.net/weixin_51574797/article/details/126733336