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

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2