Spring事务配置及事务的传播性与隔离级别详解

简介: Spring事务配置及事务的传播性与隔离级别详解

1.Spring事务配置


通过注解配置


1.在Spring配置文件引入<tx:>

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/aop 
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
           http://www.springframework.org/schema/tx
         http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">

2.配置基于注解的声明式事务管理,如果没有使用Hibernate框架,则将class替成"org.springframework.jdbc.datasource.DataSourceTransactionManager"


<!-- 配置SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="configLocation">
      <value>classpath:hibernate.cfg.xml</value>
    </property>
</bean>
<!-- 配置声明式事务管理器-->  
    <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">  
        <property name="sessionFactory" ref="sessionFactory"></property>  
    </bean>  
    <!-- 注解驱动-->  
    <tx:annotation-driven transaction-manager="txManager"/>

3.在要使用事务管理的类或者方法上增加代码@Transactional,Spring官方团队建议不要在接口使用。在类上使用@Transactional,类中的所有public方法都将使用事务



@Transactional
public class Txtest implements TestService { }

在public方法上使用@Transactional,则该方法使用事务;非public方法使用@Transactional不会报错,但也不会使用事务,相当于“白做”。


@Transactional
public List<Object> getAll() {
    return null;
}


如果在类上使用@Transactional,但是类中的某个方法不想使用事务,则可以使用



@Transactional
public class Txtest implements TestService {   
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public List<Object> getAll() {
        return null;
    }   
}


使用tx标签配置的拦截器


1.配置sessionFactory



<!-- 配置SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="configLocation">
      <value>classpath:hibernate.cfg.xml</value>
    </property>
</bean>

2.配置事务管理器



<!-- 事务配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="sessionFactory" ref="sessionFactory" />
</bean>

3.配置事务的传播性



<!-- 使用声明方式配置事务 -->
<tx:advice id="TxAdvice" transaction-manager="transactionManager">
     <tx:attributes>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
        <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
        <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
           <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
    </tx:attributes>
</tx:advice>


4.配置参与事务的类



<aop:config>
  <aop:pointcut id="allServiceMethod" expression="execution(* com.chillax.*.service.*.*(..))"/>
  <aop:advisor pointcut-ref="allServiceMethod" advice-ref="TxAdvice" />
</aop:config>

2.事务的传播性



1)@Transactional(propagation=Propagation.REQUIRED):默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行,所以这个级别通常能满足处理大多数的业务场景。



2)@Transactional(propagation=PROPAGATION.SUPPORTS):从字面意思就知道,supports(支持),该传播级别的特点是,如果上下文存在事务,则支持当前事务,加入到事务执行,如果没有事务,则使用非事务的方式执行。所以说,并非所有的包在transactionTemplate.execute中的代码都会有事务支持。这个通常是用来处理那些并非原子性的非核心业务逻辑操作,应用场景较少。


3)@Transactional(propagation=PROPAGATION.MANDATORY):该级别的事务要求上下文中必须要存在事务,否则就会抛出异常!配置该方式的传播级别是有效的控制上下文调用代码遗漏添加事务控制的保证手段。比如一段代码不能单独被调用执行,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别。


4)@Transactional(propagation=PROPAGATION.REQUIRES_NEW):从字面即可知道,每次都要一个新的事务,该传播级别的特点是,每次都会新建一个事务,并且同时将上下文中的事务挂起,当新建事务执行完成以后,上下文事务再恢复执行。

这是一个很有用的传播级别,举一个应用场景:现在有一个发送100个红包的操作,在发送之前,要做一些系统的初始化、验证、数据记录操作,然后发送100封红包,然后再记录发送日志,发送日志要求100%的准确,如果日志不准确,那么整个父事务逻辑需要回滚。

怎么处理整个业务需求呢?就是通过这个PROPAGATION.REQUIRES_NEW 级别的事务传播控制就可以完成。发送红包的子事务不会直接影响到父事务的提交和回滚。


5)@Transactional(propagation=PROPAGATION.NOT_SUPPORTED) :这个也可以从字面得知,not supported(不支持),当前级别的特点是,如果上下文中存在事务,

则挂起事务,执行当前逻辑,结束后恢复上下文的事务。

这个级别有什么好处?可以帮助你将事务极可能的缩小。我们知道一个事务越大,它存在的风险也就越多。所以在处理事务的过程中,要保证尽可能的缩小范围。比如一段代码,是每次逻辑操作都必须调用的,比如循环1000次的某个非核心业务逻辑操作。这样的代码如果包在事务中,势必造成事务太大,导致出现一些难以考虑周全的异常情况。所以这个事务这个级别的传播级别就派上用场了,用当前级别的事务模板抱起来就可以了。


6)@Transactional(propagation=PROPAGATION.NEVER):该事务更严格,上面一个事务传播级别只是不支持而已,有事务就挂起,而PROPAGATION_NEVER传播级别要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行!


7)@Transactional(propagation=PROPAGATION.NESTED):字面也可知道,nested,嵌套级别事务。该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

那么什么是嵌套事务呢?


嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于那个save point,看几个问题就明白了。


如果子事务回滚,会发生什么?


如果子事务抛出的异常被父事务捕获,则父事务仍然可以提交;如果没有捕获,则父事务也将回滚。



如果父事务回滚,会发生什么?


父事务回滚,子事务也会跟着回滚!为什么呢,因为父事务结束之前,子事务是不会提交的,我们说子事务是父事务的一部分,正是这个道理。


那么:事务的提交,是什么情况? 是父事务先提交,然后子事务提交,还是子事务先提交,父事务再提交?


答案是第二种情况,还是那句话,子事务是父事务的一部分,由父事务统一提交。



以上是事务的7个传播级别,在日常应用中,通常可以满足各种业务需求,但是除了传播级别,在读取数据库的过程中,如果两个事务并发执行,那么彼此之间的数据是如何影响的呢?


这就需要了解一下事务的另一个特性:事务的隔离级别。



3.事务的隔离级别


1)@Transactional(isolation = Isolation.SERIALIZABLE):最严格的级别,事务串行执行,资源消耗最大;

2)@Transactional(isolation = Isolation.REPEATABLE_READ):保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。

3)@Transactional(isolation = Isolation.READ_COMMITTED):大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,该级别适用于大多数系统。

4)@Transactional(isolation = Isolation.READ_UNCOMMITTED):保证了读取过程中不会读取到非法数据。


1:Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。

2:non-repeatable reads--不可重复读。比如事务A中两处读取同一行数据的total值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A在同一个事务中重复读取的结果不一致,也就是所谓的不可重复读。

3:phantom reads--幻象读数据。这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据被修改了,但是phantom reads所要读的数据的不一致指的是:相同的查询语句执行两次,但第二次返回第一次没有返回的行,则该行是“幻像”行,也就是出现了幻读。比如SELECT * FROM child WHERE id > 100 FOR UPDATE;,此时表中有id=90,id=102两行数据。事务A第一次读取时返回了1行数据,之后事务B往该表插入了一条id=101的数据,事务第二次读取的时候,结果返回了2行数据,则出现了幻读。



不可重复读的重点是修改:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了。


幻读的重点在于新增或者删除:同样的条件, 第1次和第2次读出来的记录数不一样。


而事务的隔离级别会导致读取到非法数据的情况如下表示:

image.png

常用数据库默认事务隔离级别


MYSQL:默认为REPEATABLE_READ

SQLSERVER:默认为READ_COMMITTED

ORACLE:默认为READ_COMMITTED


4.@Transactional注解中常用参数说明


image.png

image.png

注意的几点:

1)@Transactional只能被应用到public方法上,对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.


2)用spring事务管理器,由spring来负责数据库的打开,提交,回滚。默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常})。如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

如下:

@Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚

public void methodName() {

throw new Exception("注释");


}

@Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚

public ItimDaoImpl getItemDaoImpl() {

throw new RuntimeException("注释");

}


3)@Transactional注解应该只被应用到public可见度的方法上。如果你在 protected、private或者package-visible的方法上使用@Transactional注解它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。


4)@Transactional注解可以被应用于接口定义和接口方法、类定义和类的public方法上。然而,请注意仅仅@Transactional注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别@Transactional注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是 <tx:annotation-driven/>元素的出现 开启 了事务行为。


5)Spring团队的建议是你在具体的类(或类的方法)上使用@Transactional注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用 @Transactional 注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用 @Transactional 注解。



相关文章
|
8天前
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
132 73
|
5天前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
28 13
|
8天前
|
Java Spring
【Spring配置相关】启动类为Current File,如何更改
问题场景:当我们切换类的界面的时候,重新启动的按钮是灰色的,不能使用,并且只有一个Current File 项目,下面介绍两种方法来解决这个问题。
|
8天前
|
Java Spring
【Spring配置】idea编码格式导致注解汉字无法保存
问题一:对于同一个项目,我们在使用idea的过程中,使用汉字注解完后,再打开该项目,汉字变成乱码问题二:本来a项目中,汉字注解调试好了,没有乱码了,但是创建出来的新的项目,写的注解又成乱码了。
|
8天前
|
Java Spring
【Spring配置】创建yml文件和properties或yml文件没有绿叶
本文主要针对,一个项目中怎么创建yml和properties两种不同文件,进行配置,和启动类没有绿叶标识进行解决。
|
14天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
51 6
|
8月前
|
监控 Java 数据库
Spring事务相关配置、案例:转账业务追加日志及事务传播行为
Spring事务相关配置、案例:转账业务追加日志及事务传播行为
87 0
|
存储 Java 数据库
Spring事务和事务传播机制
Spring事务和事务传播机制
101 0
|
1月前
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
2月前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
60 1
Spring高手之路24——事务类型及传播行为实战指南