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编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis