一文把Spring事务讲的明明白白

简介: 从本篇文章开始,我们将进入事务相关的专题,我们将从Spring事务管理,什么是分布式事务,分布式事务的解决方案等几个角度,分多篇文章进行讲述,欢迎大家关注。虽然事务已经是一个被讲烂的话题了,但是越是如此我们越要去讲,去总结,去沉淀,讲的人越多说明这块知识对我们越重要,但凡是开发事务是必会的。本篇文章我们就来介绍一下Spring事务。

1. 事务相关知识回顾

事务的四大(ACID)特性:

  • 原子性(Atomicity) :要么都成功要么都失败。
  • 一致性(Consistency) :AID都是数据库处理的特征,一致性是强调应用系统从一个正确的状态到另一个正确的状态,可以说AID是为了保证C。
  • 隔离性(Isolation) :多个事务同时处理相同数据,事务间会互相隔离,不会互相影响。
  • 持久性(Durability) :事务完成,会将结果持久化到磁盘。

对于事务来说,我们最常举的例子就是转账的案例,我们的账户有1000元,小明的账户有1000元,我们给小明转了100块钱,这是我们的钱扣了剩下900元。但是服务出现故障,小明没有收到100块钱的转账,这时我们两个人的账户就剩1900,本来两个人应该是有2000元的,转账这件事情发生之后,我们两个人金额总和发生了变化,这时就前后不一致了,这就是没有事务的情况会发生的。如果我们加上事务,当小明没有收到转账时,会事务回滚,将恢复到转账之前的状态,及我们各持有1000元,我们的总额也没有变化,这样就是一致性的体现,也是事务的目的。

数据库如何开启事务:

# 方式一
# 事务开启
START TRANSACTION;
     # 事务内操作
# 事务提交
commit;
​
# 方式二
# 关闭事务自动提交
set autocommit=0
     # 事务内操作
# 事务提交
commit;
# 查看当前自动提交状态的命令
show VARIABLES like 'autocommit'; 

使用JDBC 控制事务

Connection conn = DriverManager.getConnection();
try {  
    conn.setAutoCommit(false);  //将自动提交设置为false  
    
    // 执行CRUD操作 
    
    conn.commit();      //当两个操作成功后手动提交  
} catch (Exception e) {  
    conn.rollback();    //一旦其中一个操作出错都将回滚,所有操作都不成功
    e.printStackTrace();  
} finally {
    conn.colse();
}

2. Spring中事务的管理方式

Spring支持编程式事务管理以及声明式事务管理两种方式,一般情况下,我们都是使用声明式事务。

2.1 编程式事务管理

编程式事务管理是侵入性事务管理,通过使用TransactionTemplate或者使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。这种事务方式对代码侵入性大,与业务紧紧耦合在一起,容易出错,但是可控性强,能够更加细粒度的管理事务。

2.2 声明式事务管理

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,通过代理,在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。声明式事务的最大劣势就是它对事务的粒度最小是控制在方法级别的,而编程式事务的粒度能够到代码块。

3. Spring中的事务管理

Spring事务的本质其实就是数据库对事务的支持。

Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器。

Spring事务.jpg

从这个类图上我们就能分析出Spring的事务管理器是如何设计实现的。

4. 事务管理器的五大设置条件

事务管理器会依据我们应用中设置的事务隔离级别,事务传播机制,超时,是否只读,回滚机制等五大条件进行事务的管理,他们相当于事务管理的入参。

4.1 Spring中的事务隔离级别

事务隔离级别是因为并发事务引起的,对应事务的隔离性,也就是说事务的隔离性是有强弱之分的,我们可以自己去设置。

事务并发可能引起的问题:

  • 脏读(Dirty reads) 脏读发生在一个事务读取了另一个事务修改但尚未提交的数据时。如果修改在稍后被回滚了,那么第一个事务获取的数据就是无效的。
  • 不可重复读(Nonrepeatable read) 不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
  • 幻读(Phantom read) 幻读与不可重复读类似。它发生在事务T1读取了几行数据,接着另一个并发事务T2插入了一些数据时。在随后的查询中,事务T1就会发现多了一些原本不存在的记录,就很梦幻。

Spring 支持的隔离级别:

  • ISOLATION_DEFAULT 使用数据库默认的隔离级别
  • ISOLATION_READ_UNCOMMITTED 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生,MySQL默认的隔离级别
  • ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是效率最低的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的,一般我们不会使用如此高的隔离级别。

4.2 Spring中的事务传播机制

我们就是通过Spring中的事务传播机制来控制,当前事务的范围,及哪些方法在事务的范围之内,
  • PROPAGATION_REQUIRED 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会开启一个新的事务
  • PROPAGATION_SUPPORTS 表示当前方法不需要开启事务,但是如果当前存在事务的话,那么该方法会在这个事务中运行
  • PROPAGATION_MANDATORY 表示该方法强制在事务中运行,如果当前事务不存在,则会抛出一个异常
  • PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
  • PROPAGATION_NOT_SUPPORTED 表示该方法不需要运行在事务中。如果存在当前事务,则在该方法执行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager。
  • PROPAGATION_NEVER 表示当前方法不能运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
  • PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

4.3 Sping中的事务超时

事务的超时设置是为了解决什么问题呢?

在数据库中,如果一个事务长时间执行,这样的事务会占用不必要的数据库资源,还可能会锁定数据库的部分资源,这样在生产环境是非常危险的。这时就可以声明一个事务在特定秒数后自动回滚,不必等它自己结束。

事务超时时间的设置

由于超时时间在一个事务开启的时候创建的,因此,只有对于那些具有启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、ROPAGATION_NESTED),声明事务超时才有意义。

4.4 Spring中的事务只读

如果一个事务只对数据库进行读操作,数据库可以利用事务的只读特性来进行一些特定的优化。我们可以通过将事务声明为只读,让数据库对我们的事务操作进行优化。

4.5 Spring中的事务回滚规则

回滚规则,就是程序发生了什么会造成回滚,这里我们可以进行设置RuntimeException或者Error。

默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚

我们可以声明事务在遇到特定的异常进行回滚。同样,我们也可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

5. @Transactional 注解式事务声明方式原理介绍

@Transactional 使我们最长用的一种声明事务的方式,所以本篇文章我们只讲这种方式

5.1 @Transactional 注解使用

@Transactional 可以用在方法上也可以用在类上
  1. 事务的传播性: @Transactional(propagation=Propagation.REQUIRED)
  2. 事务的隔离级别: @Transactional(isolation = Isolation.READ_UNCOMMITTED)
  3. 只读: @Transactional(readOnly=true)

    true表示只读,false则表示可读写,默认值为false。

  4. 事务的超时性: @Transactional(timeout=30)
  5. 回滚: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

5.2 @Transactional 原理

注解式声明事务是基于AOP实现的,AOP又是基于动态代理实现的,不熟悉动态代理的同学,需要先了解一下动态代理,本文就不过多讲解动态代理的内容了。

  • AOP 会扫描到 @Transactional 注解,获取注解信息,创建代理对象
  • 根据注解信息,AOP 会在开启事务
  • 执行AOP的前置增强
  • 调用目标对象的目标方法
  • 获取目标方法的返回
  • 执行AOP的后置增强
  • 提交事务,或者触发回滚

正是由于Spring的这种实现方式,就引出了事务失效的场景。

5.3 事务失效的场景

八大事务失效场景是非常重要的,我们在开发中要极力避免,容易出现的已加粗。

  1. 数据库引擎不支持事务(Spring的事务本质上是数据库事务)
  2. 没有被Sping管理(Spring只会处理Spring中的Bean,不会处理没有注册的Bean)
  3. 方法不是public (代理模式的原因,开启AspectJ可以试试)
  4. 自身调用(因为自身调用没有经过Spring的代理类,所以事务是不生效的)
  5. 数据源没有配置事务管理器
  6. 事务传播形式定义了不支持事务
  7. 异常被try catch掉,也就是没有异常,没有异常就无法触发正常的回滚
  8. 异常类型,默认是RuntimeException,如果抛出了其他异常也是无法正常回滚的
目录
相关文章
|
3月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
12天前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
26 1
Spring高手之路24——事务类型及传播行为实战指南
|
4月前
|
Java 关系型数据库 MySQL
Spring 事务失效场景总结
Spring 事务失效场景总结
64 4
|
5天前
|
XML Java 数据库连接
Spring中的事务是如何实现的
Spring中的事务管理机制通过一系列强大的功能和灵活的配置选项,为开发者提供了高效且可靠的事务处理手段。无论是通过注解还是AOP配置,Spring都能轻松实现复杂的事务管理需求。掌握这些工具和最佳实践,能
13 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`表示必须在现有事务中运行,否则抛出异常;
142 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企业应用至关重要。
50 0