Spring 事务

简介: Spring 事务

一、事务简介

事务:就是将一组操作封装成为一个整体执行单元,要么全部执行,要么都不执行。

假如事务执行了一半发生了错误就会对已经执行的部分进行回滚操作。

常见的应用场景就是转账事务,转账过程中发生错误就会全部回滚到事务最初的状态。

二、在Spring中实现事务

在Spring中实现事务有两种方式:

  1. 编程式事务(通过写代码操作事务);
  2. 声明式事务(通过注解开启和提交事务)。

编程式事务

在编程式事务中包含如下三个重要步骤:

  1. 获取事务;
  2. 提交事务;
  3. 回滚事务;

在编程式事务中,需要用到以下的两个对象:

  • DataSourceTractionManager;
  • TractionDefinition;

实现一个事务实现添加用户到数据库:

首先定义Mapper接口,在该接口中定义增加用户的方法。

@Mapper
public interface UserMapper {
    int add(UserInfo userInfo) ;//添加用户返回受影响的行数
}

在resources文件夹下新建mapper文件夹生成UserMapper.xml文件,其中新增用户的标签如下:

    <insert id="add">
        insert into userinfo(username,password) values (#{username},#{password})
    </insert>

然后在service包下创建UserService类:

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;
    public int add(UserInfo userInfo){
        return userMapper.add(userInfo);
    }
 
}

controller包下的UserController类的内容如下:

@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @Autowired
    private DataSourceTransactionManager transactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;
    @RequestMapping("/add")
    public int add(UserInfo userInfo){
        //先对用户名和密码进行非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
           || !StringUtils.hasLength(userInfo.getPassword()) ) {
            return 0;
        }
        //获取事务
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
        int result = userService.add(userInfo);
        System.out.println(result);
        //回滚事务
        transactionManager.rollback(transaction);
        return result;
    }
 
}

那么进行访问add:

此处的返回值为1,表示新增了1行,但是对事物进行了回滚操作,就会发现数据库中的内容不变。如果使用transactionManager.commit(transaction)就会对事务进行提交,就会在数据库中看到新插入的内容。

声明式事务

声明式事务相较于编程式事务简单了许多,只需要在用到事务的方法之前添加@Transational注解就可以实现开启事务和提交事务,如果方法执行过程中发生了异常,会自动进行回滚操作。

使用@Transactional注解修饰的方法必须是public,否则无效,并且当@Transactional修饰类时,会对类中所有的public方法有效。

@Transactional注解中可以使用如下的参数:

关于事务的隔离级别: 事务

使用声明式事务实现增加一个用户:

 @Transactional
    @RequestMapping("/add2")
    public int add2(UserInfo userInfo){
        //先对用户名和密码进行非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
                || !StringUtils.hasLength(userInfo.getPassword()) ) {
            return 0;
        }
        int result = userService.add(userInfo);
        return result;
    }

访问add2:

并且在数据库中也增加了该用户:

那么中间出现异常,再次测试:

    @Transactional
    @RequestMapping("/add2")
    public int add2(UserInfo userInfo){
        //先对用户名和密码进行非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
                || !StringUtils.hasLength(userInfo.getPassword()) ) {
            return 0;
        }
        int result = userService.add(userInfo);
        int a = 99 / 0;
        return result;
    }

代码中的异常发生在添加之后,在数据中查看:

并没有添加成功,此时事务进行了回滚操作。

在使用@Transactional注解的方法中如果使用try-catch处理了异常之后,此时发生异常就不会进行回滚,就需要在catch语句中手动进行回滚操作:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

也可以直接抛出异常,不使用try-catch进行处理了。

@Transactional注解的实现原理

@Transactional注解是基于AOP思想实现的,而AOP又是基于动态代理实现的。

该注解在执行业务之前,会通过代理开启事务,在执行成功之后提交事务,或者遇到异常进行事务回滚。

三、事务的传播机制

事务的传播机制:多个事务相互调用时,事务之间是如何传递的。

事务的传播机制与多事务并发的区别:

事务传播机制的类别


REQUIRED:默认的事务传播级别,如果当前存在事务,就加入该事务,如果当前没有事务就创建一个新的事务(有房子就可以结婚,没房子买房才能结婚)


SUPPORTS:如果当前存在事务,就加入该事务,如果当前没有事务就以非事务的方式运行(有房子就可以结婚,没房子租房也可以结婚)


MANDATORY:如果当前存在事务,就加入该事务,如果当前没有事务就抛出异常(有房子就可以结婚,没房子就分手)


REQUIRES_NEW:创建一个新事务,如果当前存在事务就把当前事务挂起。(不管之前有没有房子都必须买房)


NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务就降档前事务挂起(不管之前有没有房子都必须租房子住)


NEVER:以非事务的方式运行,如果当前存在事务就抛出异常(如果之前有房子就分手)


NESTED:如果当前存在事务就创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务就创建一个新的事务(如果之前有房子,就把他作为备用再买一套房,如果之前没房,也要买房)

场景演示:

使用REQUIRED的隔离级别:

UserController中的代码:

@Transactional( propagation = Propagation.REQUIRED)
    @RequestMapping("/add3")
    public int add3(UserInfo userInfo){
        //先对用户名和密码进行非空校验
        if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername())
                || !StringUtils.hasLength(userInfo.getPassword()) ) {
            return 0;
        }
        int result = userService.add(userInfo);
        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setTitle("test");
        articleInfo.setContent("uihjhj");
        articleInfo.setUid(userInfo.getId());
        articleInfo.setRcount(result);
        articleService.add(articleInfo);
        try {
            int m = 99/0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }

UserService的代码:

  @Transactional( propagation = Propagation.REQUIRED)
    public int add(UserInfo userInfo){
        return userMapper.add(userInfo);
    }

ArticleService中的代码:

@Transactional( propagation = Propagation.NESTED)
    public int add(ArticleInfo articleInfo){
        int result = articleMapper.add(articleInfo);
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }

日志显示已经插入数据成功:

但是访问数据库:

并没有将想要新增的数据添加进去, 因为REQUIRED隔离级别要求如果当前存在事务,就加入该事务,所以发现异常,事务整个进行回滚。

使用REQUIRED_NEW隔离级别处理同样的代码,日志显示:

数据库显示:

REQUIRED_NEW隔离级别表示如果之前存在事务就将其挂起,然后创建新的事务,所以这两个事务互不干扰,因此插入用户成功。

使用NOT_SUPPORTED注解表示以非事务方式运行也会插入成功,日志显示:

数据库查询:

使用NESTED隔离级别也会插入成功,NESTED隔离级别表示之前的事务作为备用,嵌套事务之所以能够实现部分事务的回滚,是因为事务中有一个保存点,嵌套事务进入之后就相当于新建了一个保存点,回滚时只会回滚到当前的保存点,之前的事务不受影响。 

目录
相关文章
|
3月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
11天前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
24 1
Spring高手之路24——事务类型及传播行为实战指南
|
4月前
|
Java 关系型数据库 MySQL
Spring 事务失效场景总结
Spring 事务失效场景总结
63 4
|
4天前
|
XML Java 数据库连接
Spring中的事务是如何实现的
Spring中的事务管理机制通过一系列强大的功能和灵活的配置选项,为开发者提供了高效且可靠的事务处理手段。无论是通过注解还是AOP配置,Spring都能轻松实现复杂的事务管理需求。掌握这些工具和最佳实践,能
12 3
|
2月前
|
Java 数据库连接 数据库
spring复习05,spring整合mybatis,声明式事务
这篇文章详细介绍了如何在Spring框架中整合MyBatis以及如何配置声明式事务。主要内容包括:在Maven项目中添加依赖、创建实体类和Mapper接口、配置MyBatis核心配置文件和映射文件、配置数据源、创建sqlSessionFactory和sqlSessionTemplate、实现Mapper接口、配置声明式事务以及测试使用。此外,还解释了声明式事务的传播行为、隔离级别、只读提示和事务超时期间等概念。
spring复习05,spring整合mybatis,声明式事务
|
2月前
|
Java 测试技术 数据库
Spring事务传播机制(最全示例)
在使用Spring框架进行开发时,`service`层的方法通常带有事务。本文详细探讨了Spring事务在多个方法间的传播机制,主要包括7种传播类型:`REQUIRED`、`SUPPORTS`、`MANDATORY`、`REQUIRES_NEW`、`NOT_SUPPORTED`、`NEVER` 和 `NESTED`。通过示例代码和数据库插入测试,逐一展示了每种类型的运作方式。例如,`REQUIRED`表示如果当前存在事务则加入该事务,否则创建新事务;`SUPPORTS`表示如果当前存在事务则加入,否则以非事务方式执行;`MANDATORY`表示必须在现有事务中运行,否则抛出异常;
140 4
Spring事务传播机制(最全示例)
|
1月前
|
Java 关系型数据库 MySQL
Spring事务失效,我总结了这7个主要原因
本文详细探讨了Spring事务在日常开发中常见的七个失效原因,包括数据库不支持事务、类不受Spring管理、事务方法非public、异常被捕获、`rollbackFor`属性配置错误、方法内部调用事务方法及事务传播属性使用不当。通过具体示例和源码分析,帮助开发者更好地理解和应用Spring事务机制,避免线上事故。适合所有使用Spring进行业务开发的工程师参考。
29 2
|
1月前
|
Java 程序员 Spring
Spring事务的1道面试题
每次聊起Spring事务,好像很熟悉,又好像很陌生。本篇通过一道面试题和一些实践,来拆解几个Spring事务的常见坑点。
Spring事务的1道面试题
|
2月前
|
Java Spring
Spring 事务传播机制是什么?
Spring 事务传播机制是什么?
22 4
|
1月前
|
监控 Java 数据库
Spring事务中的@Transactional注解剖析
通过上述分析,可以看到 `@Transactional`注解在Spring框架中扮演着关键角色,它简化了事务管理的复杂度,让开发者能够更加专注于业务逻辑本身。合理运用并理解其背后的机制,对于构建稳定、高效的Java企业应用至关重要。
44 0