spring @Transactional的理解

2024-08-21 21:48

本文主要是介绍spring @Transactional的理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • @Transactional 注解的属性信息
  • @Transactional 只能应用到 public 方法才有效
  • 避免 Spring 的 AOP 的自调用问题
  • 事务传播行为
  • 嵌套事务
    • 1. PROPAGATION_REQUIRES_NEW :
    • 2. PROPAGATION_NESTED :
  • 列子理解 REQUIRED、REQUIRES_NEW、NESTED
    • 1、不管 testTractration、testTractration1、testTractration2 哪边报错全部回滚,默认使用REQUIRED
    • 2、testTractration1 被隔离,==其他==错误不影响==其commit==,REQUIRES_NEW
    • 3、testTractration2 被隔离,==其==错误不影响==其它commit==,REQUIRES_NEW
    • 4、所有的成功commit,testTractration1才成功,否则回滚(联合成功);testTractration1失败回滚,不影响其他,其他正常commit,NESTED
  • 数据库的四种隔离级别
    • READ UNCIMMITTED(未提交读)(脏读,不可重复读,幻读)("共享写锁")
    • READ COMMITTED(提交读)( 不可重复读,幻读 )("共享读锁"和"排他写锁")
    • REPEATABLE READ(可重复读)(幻读 )("共享读锁"和"排他写锁")
    • SERIALIZABLE(可串行化)(锁表)
    • 不可重复读,幻读区别
    • 共享锁
    • 排他锁
  • spring @transactional 和synchronized同时使用的问题
  • 参考链接
    • 用法
    • 数据库隔离
    • synchronized
    • 数据库的四种隔离级别的实现方式
    • mysql共享锁与排他锁
    • 悲观锁,乐观锁,行锁,表锁,页锁,共享锁,排他锁

@Transactional 注解的属性信息

属性名说明
name当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation事务的传播行为,默认值为 REQUIRED。
isolation事务的隔离度,默认值采用 DEFAULT
timeout事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for抛出 no-rollback-for 指定的异常类型,不回滚事务。

@Transactional 只能应用到 public 方法才有效

避免 Spring 的 AOP 的自调用问题

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。

@Service
public class OrderService {private void insert() {insertOrder();}@Transactionalpublic void insertOrder() {}
}

insertOrder 尽管有@Transactional 注解,但它被内部方法 insert 调用,事务被忽略,出现异常事务不会发生回滚。

上面的两个问题@Transactional 注解只应用到 public 方法和自调用问题,是由于使用 Spring AOP 代理造成的。为解决这两个问题,使用 AspectJ 取代 Spring AOP 代理。

<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</bean
class="org.springframework.transaction.aspectj.AnnotationTransactionAspect"
factory-method="aspectOf">
<property name="transactionManager" ref="transactionManager" />
</bean>

同时在 Maven 的 pom 文件中加入 spring-aspects 和 aspectjrt 的 dependency 以及 aspectj-maven-plugin。

<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.3.2.RELEASE</version>
</dependency>
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version>
</dependency>
<plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.9</version><configuration><showWeaveInfo>true</showWeaveInfo><aspectLibraries><aspectLibrary><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></aspectLibrary></aspectLibraries></configuration><executions><execution><goals><goal>compile</goal><goal>test-compile</goal></goals></execution></executions>
</plugin>

事务传播行为

  • PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务,就加入到这个事务中。这是最常见的选择。

  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行

  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

  • PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。

  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

  • PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。

  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

嵌套事务

带有事务的方法调用其他事务的方法,此时执行的情况取决配置的事务的传播属性

1. PROPAGATION_REQUIRES_NEW :

启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.

2. PROPAGATION_NESTED :

如果外部事务 commit, 嵌套事务也会被 commit;如果外部事务 roll back, 嵌套事务也会被 roll back 。开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交

列子理解 REQUIRED、REQUIRES_NEW、NESTED

    public void testTractration() {}public void testTractration1() {}public void testTractration2() {}

1、不管 testTractration、testTractration1、testTractration2 哪边报错全部回滚,默认使用REQUIRED

  @Override@Transactional(rollbackFor = Exception.class)public void testTransactional() {contractServiceService.testTransactional1();contractServiceService.testTransactional2();int i = 1/0; //三处错误都可以全部回滚}@Transactional(rollbackFor = Exception.class)public void testTransactional1() {int i = 1/0;//三处错误都可以全部回滚}@Transactional(rollbackFor = Exception.class)public void testTransactional2() {int i = 1/0;//三处错误都可以全部回滚}

2、testTractration1 被隔离,其他错误不影响其commit,REQUIRES_NEW

 @Override@Transactional(rollbackFor = Exception.class)public void testTransactional() {contractServiceService.testTransactional1();contractServiceService.testTransactional2();}@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)public void testTransactional1() {}@Transactional(rollbackFor = Exception.class)public void testTransactional2() {int i = 1/0;}

3、testTractration2 被隔离,错误不影响其它commit,REQUIRES_NEW

 @Override@Transactional(rollbackFor = Exception.class)public void testTransactional() {contractServiceService.testTransactional1();//防止抛出的异常影响到 testTransactional的事物//并且因为testTransactional1 默认REQUIRED 会使用testTransactional的事物 继续回滚try{contractServiceService.testTransactional2(); }catch(Exception e){}}@Transactional(rollbackFor = Exception.class)public void testTransactional1() {}@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)public void testTransactional2() {int i = 1/0;}

4、所有的成功commit,testTractration1才成功,否则回滚(联合成功);testTractration1失败回滚,不影响其他,其他正常commit,NESTED

    @Override@Transactional(rollbackFor = Exception.class)public void testTransactional() {//防止抛出错误影响testTransactional事物//或者放到testTransactional2下面执行try{contractServiceService.testTransactional1();}catch(Exception e){}contractServiceService.testTransactional2(); int i = 1/0; //出错影响testTransactional1 commit}@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)public void testTransactional1() {//出错不影响testTransactional2 commitint i = 1/0;}@Transactional(rollbackFor = Exception.class)public void testTransactional2() {}

数据库的四种隔离级别

READ UNCIMMITTED(未提交读)(脏读,不可重复读,幻读)(“共享写锁”)

事务中的修改,即使没有提交,其他事务也可以看得到,可能读取脏数据

比如购票系统,里面有三张票,A,B售货员。A卖出去三张票,正在写入系统还没commit,B查询系统会显示没有余票,结果A的commit失败了,结果一张票都没有卖出去。

脏读原因:在读取时是不会加锁的,但在更新数据时,对其加行级共享锁(其它事务不能更改,但可以读取,导致脏读)


READ COMMITTED(提交读)( 不可重复读,幻读 )(“共享读锁"和"排他写锁”)

事务中的修改,只有提交成功,其他事务才能看得到,大多数数据库系统的默认隔离级别。可能出现读取旧数据的现象

比如购票系统,里面有三张票,A,B售货员。A卖出去三张票,正在写入系统还没commit,B查询系统会显示有余票,B也就继续卖出了,结果A继续执行的话,余票就是0,可是还是卖出去了。就会多卖出去票

解决方案:

写数据加行级排他锁,这样写过程是无法读取的,直到事务处理完毕才释放排他锁,给读的数据加行级共享锁,这样读的时候也是无法写的。

不可重复读原因:一旦读完该行就释放共享锁,再查询这行就会读到修改过commit的数据,这种模式下虽然处理了脏读,但是并没有处理丢失更新和不可重复读的问


REPEATABLE READ(可重复读)(幻读 )(“共享读锁"和"排他写锁”)

突然蹦出来的行数据。指的就是某个事务在读取某个范围的数据,但是另一个事务又向这个范围的数据去插入数据,这个事务再读取的时候,数据的行数不一致。

解决方案:

给写的数据加行级排他锁,事务结束释放,给读的数据加行级共享锁,事务结束后释放。这种模式还是没有处理幻读的问题

幻读原因:排他锁 增加或者删除数据的话,这数据查询时候是没有共享锁的,就会导致多了数据或者少了数据


SERIALIZABLE(可串行化)(锁表)

它通过强制事务串行执行(注意是串行),避免了前面的幻读情况,由于他大量加上锁,导致大量的请求超时,因此性能会比较底下,再特别需要数据一致性且并发量不需要那么大的时候才可能考虑这个隔离级别

不可重复读,幻读区别

  • 不可重复读的重点是修改 :
    同样的条件 , 你读取过的数据 ,再次读取出来发现值不一样了
  • 幻读的重点在于 新增或者删除
    同样的条件 ,第 1 次和第 2 次读出来的记录数不一样

共享锁

又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

  1. 一个事务获取了共享锁,在其他查询中也只能加共享锁或不加锁。

排他锁

又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。

  1. 排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁.

  2. mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型

  3. 如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句

  4. 所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。

  5. 排他锁update数据,没有commit,select的数据还是老数据

spring @transactional 和synchronized同时使用的问题

public class Demo{@Transactionalpublic void synchronized build() {}

是无法保证数据的一致性
由于spring@Transactional注解使用的是AOP来实现,会在update方法之前开启事务,之后再加锁,当锁住的代码执行完成后,在提交事务,因此synchronized代码块执行是在事务之内执行的,可以推断在代码块执行完时,事务还未提交,

当一个线程执行完该方法并释放锁后,代理类还没有提交事务前,别的线程是有机会进入到该方法中的

可以在build方法之前加上synchronized,在还没有开事务之间就加锁,那么就可以保证线程同步

public class Demo{@Transactionalpublic void build() {}public void synchronized init() {build();}
}

参考链接

用法

  1. http://sharajava.iteye.com/blog/78270
  2. https://blog.csdn.net/mingyundezuoan/article/details/79017659
  3. https://www.cnblogs.com/cnmenglang/p/6410848.html

数据库隔离

  1. https://www.cnblogs.com/s-b-b/p/5845096.html
  2. https://blog.csdn.net/universsky2015/article/details/77965393
  3. https://blog.csdn.net/eddie_520/article/details/47121571

synchronized

  1. https://blog.csdn.net/u011186019/article/details/52348624
  2. https://blog.csdn.net/luckyxl029/article/details/81282815

数据库的四种隔离级别的实现方式

  1. https://blog.csdn.net/zk3326312/article/details/79457488

mysql共享锁与排他锁

  1. https://www.cnblogs.com/boblogsbo/p/5602122.html

悲观锁,乐观锁,行锁,表锁,页锁,共享锁,排他锁

https://blog.csdn.net/xiangwanpeng/article/details/55106732

这篇关于spring @Transactional的理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094373

相关文章

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

java中long的一些常见用法

《java中long的一些常见用法》在Java中,long是一种基本数据类型,用于表示长整型数值,接下来通过本文给大家介绍java中long的一些常见用法,感兴趣的朋友一起看看吧... 在Java中,long是一种基本数据类型,用于表示长整型数值。它的取值范围比int更大,从-922337203685477

java Long 与long之间的转换流程

《javaLong与long之间的转换流程》Long类提供了一些方法,用于在long和其他数据类型(如String)之间进行转换,本文将详细介绍如何在Java中实现Long和long之间的转换,感... 目录概述流程步骤1:将long转换为Long对象步骤2:将Longhttp://www.cppcns.c

SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程

《SpringBoot集成LiteFlow实现轻量级工作流引擎的详细过程》LiteFlow是一款专注于逻辑驱动流程编排的轻量级框架,它以组件化方式快速构建和执行业务流程,有效解耦复杂业务逻辑,下面给大... 目录一、基础概念1.1 组件(Component)1.2 规则(Rule)1.3 上下文(Conte

SpringBoot服务获取Pod当前IP的两种方案

《SpringBoot服务获取Pod当前IP的两种方案》在Kubernetes集群中,SpringBoot服务获取Pod当前IP的方案主要有两种,通过环境变量注入或通过Java代码动态获取网络接口IP... 目录方案一:通过 Kubernetes Downward API 注入环境变量原理步骤方案二:通过

Springboot整合Redis主从实践

《Springboot整合Redis主从实践》:本文主要介绍Springboot整合Redis主从的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言原配置现配置测试LettuceConnectionFactory.setShareNativeConnect