spring事务传播和隔离机制

Posted jvstarblog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring事务传播和隔离机制相关的知识,希望对你有一定的参考价值。

什么是事务?

事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败!

事务特征ACID:

原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。

一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。

隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。

持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

事务的API介绍

Spring事务管理高层抽象主要包括3个接口:PlatformTransactionManager(事务管理器)、TransactionDefinition(事务定义信息)、TransactionStatus(事务状态)。

1、PlatformTransactionManager(事务管理器):

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JTA、JDBC、Hibernate、JPA等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:

Public interface PlatformTransactionManager()...  
    // 由TransactionDefinition得到TransactionStatus对象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滚
    Void rollback(TransactionStatus status) throws TransactionException;  
     

JDBC事务:

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

Hibernate事务:

如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>声明:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

Java持久化API事务(JPA):

如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。

Java JTA事务:

如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:

<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManagerName" value="java:/TransactionManager" />
</bean>

JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

2、TransactionDefinition(事务定义信息):

上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。 而TransactionDefinition接口内容如下:

public interface TransactionDefinition 
    int getPropagationBehavior(); // 返回事务的传播行为
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
 

(1)定义隔离级别(isolation level):隔离级别定义了一个事务可能受其他并发事务影响的程度。

如果不考虑隔离性,会引发如下安全问题:脏读、不可重复读、幻读

脏读(Dirty reads):一个事物读取了另一个事物改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。

不可重复读(Nonrepeatable read):不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。

幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

不可重复读与幻读的区别

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

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

从总的结果来看, 似乎不可重复读和幻读都表现为两次读取的结果不一致。但如果你从控制的角度来看, 两者的区别就比较大。 
对于前者, 只需要锁住满足条件的记录;对于后者, 要锁住满足条件及其相近的记录。

隔离级别 说明
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

 

 

 

 

 

mysql数据库用的是REPEATABLE_READ隔离级别

Oracle数据库用的是READ_COMMITTED隔离级别

(2)定义事物传播行为(propagation behavior):

在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法事务方法发生嵌套调用时事务如何进行传播。

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

 

 

 

 

 

 

 

 

当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。

几种容易引起误解的组合事务传播行为

当服务接口方法分别使用表1中不同的事务传播行为,且这些接口方法又发生相互调用的情况下,大部分组合都是一目了然,容易理解的。但是,也存在一些容易引起误解的组合事务传播方式。

下面,我们通过两个具体的服务接口的组合调用行为来破解这一难点。这两个服务接口分别是UserService和ForumService, UserSerice有一个addCredits()方法,ForumSerivce#addTopic()方法调用了 UserSerice#addCredits()方法,发生关联性服务方法的调用:

public class ForumService

private UserService userService;

public void addTopic()①调用其它服务接口的方法

//add Topic…

userService.addCredits();②被关联调用的业务方法

嵌套调用的事务方法

对Spring事务传播行为最常见的一个误解是:当服务接口方法发生嵌套调用时,被调用的服务方法只能声明为 PROPAGATION_NESTED。这种观点犯了望文生义的错误,误认为PROPAGATION_NESTED是专为方法嵌套准备的。这种误解遗害不 浅,执有这种误解的开发者错误地认为:应尽量不让Service类的业务方法发生相互的调用,Service类只能调用DAO层的DAO类,以避免产生嵌 套事务。

其实,这种顾虑是完全没有必要的,PROPAGATION_REQUIRED已经清楚地告诉我们:事务的方法会足够“聪明”地判断上下文是否已经存在一个事务中,如果已经存在,就加入到这个事务中,否则创建一个新的事务。

依照上面的例子,假设我们将ForumService#addTopic()和UserSerice#addCredits()方法的事务传播行为都设置为PROPAGATION_REQUIRED,这两个方法将运行于同一个事务中。

为了清楚地说明这点,可以将Log4J的日志设置为DEBUG级别,以观察Spring事务管理器内部的运行情况。下面将两个业务方法都设置为PROPAGATION_REQUIRED,Spring所输出的日志信息如下:

Using transaction object

[org.springframework.jdbc.data[email protected]e3849c]

①为ForumService#addTopic()新建一个事务

Creating new transaction with name [com.baobaotao.service.ForumService.addTopic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

Acquired Connection [[email protected]] for JDBC transaction

Switching JDBC Connection [[email protected]] to manual commit

Bound value [[email protected]] for key [[email protected]] to thread [main]

Initializing transaction synchronization

Getting transaction for [com.baobaotao.service.ForumService.addTopic]

Retrieved value [[email protected]] for key [[email protected]] bound to thread [main]

Using transaction object [org.springframework.jdbc.data[email protected]8b8a47]

UserService#addCredits()简单地加入到已存在的事务中(即①处创建的事务)

Participating in existing transaction

Getting transaction for [com.baobaotao.service.UserService.addCredits]

Completing transaction for [com.baobaotao.service.UserService.addCredits]

Completing transaction for [com.baobaotao.service.ForumService.addTopic]

Triggering beforeCommit synchronization

Triggering beforeCompletion synchronization

Initiating transaction commit

③调用底层Connection#commit()方法提交事务

Committing JDBC transaction on Connection [[email protected]]

Triggering afterCommit synchronization

Triggering afterCompletion synchronization

Clearing transaction synchronization

嵌套事务

将ForumService#addTopic()设置为PROPAGATION_REQUIRED时, UserSerice#addCredits()设置为PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、 PROPAGATION_MANDATORY时,运行的效果都是一致的(当然,如果单独调用addCredits()就另当别论了)。

当addTopic()运行在一个事务下(如设置为PROPAGATION_REQUIRED),而addCredits()设置为 PROPAGATION_NESTED时,如果底层数据源支持保存点,Spring将为内部的addCredits()方法产生的一个内嵌的事务。如果 addCredits()对应的内嵌事务执行失败,事务将回滚到addCredits()方法执行前的点,并不会将整个事务回滚。内嵌事务是内层事务的一 部分,所以只有外层事务提交时,嵌套事务才能一并提交。

嵌套事务不能够提交,它必须通过外层事务来完成提交的动作,外层事务的回滚也会造成内部事务的回滚。


嵌套事务和新事务

PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED也是容易混淆的两个传播行为。
PROPAGATION_REQUIRES_NEW 启动一个新的、和外层事务无关的“内部”事务。该事务拥有自己的独立隔离级别,不依赖于外部事务,独立地提交和回滚。当内部事务开始执行时,外部事务 将被挂起,内务事务结束时,外部事务才继续执行。

由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:
PROPAGATION_REQUIRES_NEW 将创建一个全新的事务,它和外层事务没有任何关系,

PROPAGATION_NESTED 将创建一个依赖于外层事务的子事务,当外层事务提交或回滚时,子事务也会连带提交和回滚。

其它需要注意问题

以下几个问题值得注意:

1) 当业务方法被设置为PROPAGATION_MANDATORY时,它就不能被非事务的业务方法调用。
如将ForumService#addTopic ()设置为PROPAGATION_MANDATORY,如果展现层的Action直接调用addTopic()方法,将引发一个异常。
正确的情况是: addTopic()方法必须被另一个带事务的业务方法调用(如ForumService#otherMethod())。
所以 PROPAGATION_MANDATORY的方法一般都是被其它业务方法间接调用的。

2) 当业务方法被设置为PROPAGATION_NEVER时,它将不能被拥有事务的其它业务方法调用。假设UserService#addCredits ()设置为PROPAGATION_NEVER,当ForumService# addTopic()拥有一个事务时,addCredits()方法将抛出异常。所以PROPAGATION_NEVER方法一般是被直接调用的。

3)当方法被设置为PROPAGATION_NOT_SUPPORTED时,外层业务方法的事务会被挂起,当内部方法运行完成后,外层方法的事务重新运行。如果外层方法没有事务,直接运行,不需要做任何其它的事。

3、TransactionStatus(事务状态):

上面讲到的调用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一个实现,这个接口的内容如下:

public interface TransactionStatus
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
 

 事务的实现方式:

1、编程式事务管理:通过TransactionTemplate手动管理事物,与业务耦合度高,很少使用,略。

(1)在AccountService中使用TransactionTemplate

(2)TransactionTemplate依赖DataSourceTransactionManager

(3)DataSourceTransactionManager依赖DataSource构造

2、声明式事务管理:Spring的声明式事物是通过AOP实现的。

(1)基于AspectJ的XML方式(经常使用)

(2)基于注解的方式(经常使用)

以上是关于spring事务传播和隔离机制的主要内容,如果未能解决你的问题,请参考以下文章

spring事务传播和隔离机制

数据库的特性与隔离级别和spring事务的传播机制和隔离级别

Spring事务之传播机制

数据库事务的四大特性以及事务的隔离级别-与-Spring事务传播机制&隔离级别

Spring事务传播机制

Spring Boot事务和事务传播机制