浅谈Spring的事务隔离级别与传播性

简介: Q:在一个批量任务执行的过程中,调用多个子任务时,如果有一些子任务发生异常,只是回滚那些出现异常的任务,而不是整个批量任务,请问在Spring中事务需要如何配置才能实现这一功能呢?

隔离级别


隔离性(Isolation)作为事务特性的一个关键特性,它要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,在数据库层面都是使用锁来实现。


事务的隔离级别从低到高有以下四种:


  • READ UNCOMMITTED(未提交读):这是最低的隔离级别,其含义是允许一个事务读取另外一个事务没有提交的数据。READ UNCOMMITTED是一种危险的隔离级别,在实际开发中基本不会使用,主要是由于它会带来脏读问题。

image.png


脏读对于要求数据一致性的应用来说是致命的,目前主流的数据库的隔离级别都不会设置成READ UNCOMMITTED。不过脏读虽然看起来毫无用处,但是它主要优点是并发能力高,适合那些对数据一致性没有要求而追求高并发的场景。


  • READ COMMITTED(读写提交): 它是指一个事务只能读取另外一个事务已经提交的数据,不能读取未提交的数据。READ COMMITTED会带来不可重复读的问题:

image.png


不可重复读和脏读的区别是:脏读读取到的是未提交的数据,而不可重复读读到的确实已经提交的数据,但是违反了数据库事务一致性的要求。


一般来说,不可重复读的问题是可以接受的,因为其读到的是已经提交的数据,本身并不会带来很大的问题。因此,很多数据库如(ORACLE,SQL SERVER)将其默认隔离级别设置为READ COMMITTED,允许不可重复读的现象。


  • REPEATABLE READ (可重复读):可重复读的目标是为了克服READ COMMITED中出现的不可重复读,它指在同一个事务内的查询都是与事务开始时刻一致,以上表为例,在REPEATABLE READ隔离级别下它会发生如下变化:


image.png


REPEATABLE READ虽然解决了不可重复读问题,但是他又会带来幻读问题,幻读是指,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。

image.png


事务B在第2步第一次读取id=99的记录时,读到的记录为空,说明不存在id=99的记录。随后,事务A在第3步插入了一条id=99的记录并提交。事务B在第5步再次读取id=99的记录时,读到的记录仍然为空,但是,事务B在第6步试图更新这条不存在的记录时,竟然成功了,并且,事务B在第8步再次读取id=99的记录时,记录出现了。


  • SERIALIZABLE(串行化):数据库最高的隔离级别,它要求所有的SQL都会按照顺序执行,这样可以克服上述所有隔离出现的各种问题,能够完全包住数据的一致性。


Spring中配置隔离级别


在Spring项目中配置隔离级别只需要做如下操作


@Transactional(isolation=Isolation.SERIALIZABLE)
publicintinsertUser(Useruser){
returnuserDao.insertUser(user);
}


上面的代码中我们使用了串行化的隔离级别来包住数据的一致性,这使它将阻塞其他的事务进行并发,所以它只能运用在那些低并发而又需要保证数据一致性的场景下。


隔离级别字典:


DEFAULT(-1),  ##数据库默认级别READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);


传播行为


在Spring中,当一个方法调用另外一个方法时,可以让事务采取不同的策略工作,如新建事务或者挂起当前事务等,这便是事务的传播行为。


定义


在Spring的事务机制中对数据库存在7种传播行为,通过枚举类Propagation定义。


publicenumPropagation {
/*** 需要事务,默认传播性行为。* 如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法*/REQUIRED(0),
/*** 支持事务,如果当前存在事务,就沿用当前事务,* 如果不存在,则继续采用无事务的方式运行子方法*/SUPPORTS(1),
/*** 必须使用事务,如果当前没有事务,抛出异常* 如果存在当前事务,就沿用当前事务*/MANDATORY(2),
/*** 无论当前事务是否存在,都会创建新事务允许方法* 这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立*/REQUIRES_NEW(3),
/*** 不支持事务,当前存在事务时,将挂起事务,运行方法*/NOT_SUPPORTED(4),
/*** 不支持事务,如果当前方法存在事务,将抛出异常,否则继续使用无事务机制运行*/NEVER(5),
/*** 在当前方法调用子方法时,如果子方法发生异常* 只回滚子方法执行过的SQL,而不回滚当前方法的事务*/NESTED(6);
  ......
}


日常开发中基本只会使用到REQUIRED(0),REQUIRES_NEW(3),NESTED(6)三种。


NESTEDREQUIRES_NEW是有区别的。NESTED传播行为会沿用当前事务的隔离级别和锁等特性,而REQUIRES_NEW则可以拥有自己独立的隔离级别和锁等特性。


NESTED的实现主要依赖于数据库的保存点(SAVEPOINT)技术,SAVEPOINT记录了一个保存点,可以通过ROLLBACK TO SAVEPOINT来回滚到某个保存点。如果数据库支持保存点技术时就启用保存点技术;如果不支持就会新建一个事务去执行代码,也就相当于REQUIRES_NEW


Transactional自调用失效


如果一个类中自身方法的调用,我们称之为自调用。如一个订单业务实现类OrderServiceImpl中有methodA方法调用了自身类的methodB方法就是自调用,如:


@TransactionalpublicvoidmethodA(){
for (inti=0; i<10; i  ) {
methodB();
    }
}
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRES_NEW)
publicintmethodB(){
    ......
}


在上面方法中不管methodB如何设置隔离级别和传播行为都是不生效的。即自调用失效。


这主要是由于@Transactional的底层实现原理是基于AOP实现,而AOP的原理是动态代理,在自调用的过程中是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,于是就发生了自调用失败的现象。


要克服这个问题,有2种方法:


  • 编写两个Service,用一个Service的methodA去调用另外一个Service的methodB方法,这样就是代理对象的调用,不会有问题;


  • 在同一个Service中,methodA不直接调用methodB,而是先从Spring IOC容器中重新获取代理对象`OrderServiceImpl·,获取到后再去调用methodB。说起来有点乱,还是show you the code。


publicclassOrderServiceImplimplementsOrderService,ApplicationContextAware {
privateApplicationContextapplicationContext=null;
@OverridepublicvoidsetApplicationContext(ApplicationContextapplicationContext) {
this.applicationContext=applicationContext;
    }
@TransactionalpublicvoidmethodA(){
OrderServiceorderService=applicationContext.getBean(OrderService.class);
for (inti=0; i<10; i  ) {
orderService.methodB();
        }
    }
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRES_NEW)
publicintmethodB(){
        ......
    }
}


上面代码中我们先实现了ApplicationContextAware接口,然后通过applicationContext.getBean()获取了OrderService的接口对象。这个时候获取到的是一个代理对象,也就能正常使用AOP的动态代理了。


回到最开始的那个问题,看完这篇文章是不是有答案了呢?

目录
相关文章
|
3月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
5天前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
16 1
Spring高手之路24——事务类型及传播行为实战指南
|
4月前
|
Java 关系型数据库 MySQL
Spring 事务失效场景总结
Spring 事务失效场景总结
61 4
|
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`表示必须在现有事务中运行,否则抛出异常;
121 4
Spring事务传播机制(最全示例)
|
1月前
|
Java 关系型数据库 MySQL
Spring事务失效,我总结了这7个主要原因
本文详细探讨了Spring事务在日常开发中常见的七个失效原因,包括数据库不支持事务、类不受Spring管理、事务方法非public、异常被捕获、`rollbackFor`属性配置错误、方法内部调用事务方法及事务传播属性使用不当。通过具体示例和源码分析,帮助开发者更好地理解和应用Spring事务机制,避免线上事故。适合所有使用Spring进行业务开发的工程师参考。
28 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
|
3月前
|
Java 程序员 数据库连接
女朋友不懂Spring事务原理,今天给她讲清楚了!
该文章讲述了如何解释Spring事务管理的基本原理,特别是针对女朋友在面试中遇到的问题。文章首先通过一个简单的例子引入了传统事务处理的方式,然后详细讨论了Spring事务管理的实现机制。
女朋友不懂Spring事务原理,今天给她讲清楚了!
下一篇
无影云桌面