【译】Spring中@Transcational注解的事务隔离级别与传播行为

访问原文java

前言

在本教程中,咱们将学习@Transactional注解的事务隔离级别和传播行为。git

@Transcational注解是什么?

咱们可使用@Transactional注解为一个方法添加事务支持。github

咱们能够经过它指定设置事务的传播行为、超时时间与回滚条件。此外,咱们还能够经过它指定一个事务管理器。spring

Spring经过建立代理对象或者操纵字节码来实现事务的建立、提交与回滚。sql

在经过代理模式实现事务管理的状况下,对于来自类内部的调用@Transactional会失效。数据库

简单地说,假如咱们有一个叫callMethod的方法并且该方法被@Transactional注解标记过。并发

Spring将使用一些用于事务管理的代码将这个方法进行包装,@Transactional注解的实现可使用下面的伪代码表示:ide

// 若有必要则建立事务
createTransactionIfNecessary();
try {
    // 调用callMethod方法
    callMethod();
    // 调用成功则提交事务
    commitTransactionAfterReturning();
} catch (exception) {
    // 调用失败则回滚事务
    completeTransactionAfterThrowing();
    throw exception;
}
复制代码

怎样使用@Transcational注解

咱们能够在接口、类、方法上面使用这个注解,位于接口、类、方法上的@Transactional注解会按照不一样的优先级发生覆盖,优先级底的将被高的覆盖掉,覆盖的优先级从低到高分别是:性能

  • 接口
  • 超类
  • 接口方法
  • 超类方法
  • 类方法

Spring会把一个类上标注的@Transactional注解应用到该类全部public的方法上,因此咱们不须要再单独给方法上标注@Transactional学习

可是,若是咱们在privateprotected方法上标注@Transcational注解,那么Spring将会忽略这些注解并且不给出任何错误提示。

让咱们从在接口上标注@Transcational注解开始:

@Transactional
public interface TransferService {
    void transfer(String user1, String user2, double val);
}
复制代码

一般状况下,不推荐在接口上标注@Transactional注解,然而在某些接口上使用也是能够接受的,好比:Spring Data的@Repository接口。

咱们能够将这个注解标注在类的定义上,这样能够覆盖接口或超类中标注的@Transactional注解:

@Service
@Transactional
public class TransferServiceImpl implements TransferService {
    @Override
    public void transfer(String user1, String user2, double val) {
        // ...
    }
}
复制代码

如今,让咱们将这个注解直接标注在方法的定义上:

@Transactional
public void transfer(String user1, String user2, double val) {
    // ...
}
复制代码

事务传播行为

事务传播行为定义了咱们业务逻辑的事务边界,Spring根据咱们配置的事务传播行为开始或者中断事务。

Spring经过调用TransactionManager::getTransaction方法而后根据事务的传播行为来获取或者建立一个事务,它支持部分在TransactionManager中定义的事务传播行为,可是还有一些特殊的事务传播行为须要经过一些特殊的 TransactionManager实现去支持。

REQUIRED

REQUIRED是默认的事务传播行为。对于该传播行为而言,Spring会检查当前线程上下文中是否存在一个活跃的事务。

若是不存在活跃的事务则会建立一个新的事务。

若是存在一个活跃的事务则将当前的业务逻辑算入这个活跃的事务范围中:

@Transactional(propagation = Propagation.REQUIRED)
public void requiredExample(String user) { 
    // ... 
}
复制代码

因为REQUIRED是默认的事务传播行为,因此上面的代码能够简写成:

@Transactional
public void requiredExample(String user) { 
    // ... 
}
复制代码

来咱们看一下建立具备REQUIRED传播行为事务的伪代码:

// 检测是否已经位于事务中
 if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事务
    return existing;
}
// 不然不使用事务
return createNewTransaction();
复制代码

SUPPORTS

对于SUPPORTS而言,Spring会先检查线程上下文中是否存在一个活跃的事务,若是存在一个活跃的事务则使用它。若是没有,则以不使用事务的方式去执行supportsExample()方法::

@Transactional(propagation = Propagation.SUPPORTS)
public void supportsExample(String user) { 
    // ... 
}
复制代码

让咱们看看使用SUPPORTS传播行为建立事务的伪代码:

// 检测是否已经位于事务中
if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事务
    return existing;
}
// 不然不使用事务
return emptyTransaction;
复制代码

MANDATORY

当传播行为被设为MANDATORY时,若是Spring没有在线程上下文中找到一个活跃的事务则会抛出一个异常:

@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryExample(String user) { 
    // ... 
}
复制代码

让咱们看一下该选项对应的伪代码表示:

// 检测是否已经位于事务中
if (isExistingTransaction()) 
{
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    // 返回已有的事务
    return existing;
}
// 不然抛出异常
throw IllegalTransactionStateException;
复制代码

NEVER

当传播行为被设为NEVER时,Spring在线程上下文中发现一个活跃事务时会抛出一个异常:

@Transactional(propagation = Propagation.NEVER)
public void neverExample(String user) { 
    // ... 
}
复制代码

让咱们看看使用NEVER传播行为建立事务的伪代码:

// 检测是否已经位于事务中
if (isExistingTransaction()) {
    // 若是位于事务中就抛出异常
    throw IllegalTransactionStateException;
}
// 不然不使用事务
return emptyTransaction;
复制代码

NOT_SUPPORTED

当传播行为被设为NOT_SUPPORTED时,Spring会将线程上下文中现有的事务进行挂起,而后再以不使用事务的方式去执行notSupportedExample()方法:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedExample(String user) { 
    // ... 
}
复制代码

JTATransactionManager事务管理器以开箱即用的方式支持真正的事务挂起。其它 的事务管理器则是经过保存现有事务的引用而后将其从线程上下文中清除的方法来模拟事务挂起。

REQUIRES_NEW

当传播行为设为REQUIRES_NEW时,Spring发现线程上下文中存在一个活跃的事务时,则先将其挂起,而后建立一个新的事务去执行requiresNewExample()方法:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewExample(String user) { 
    // ... 
}
复制代码

NOT_SUPPORTED选项相似,对于真正的事务挂起,咱们须要使用JTATransactionManager事务管理器

该选项对应的伪代码实现:

// 检测是否已经位于事务中
if (isExistingTransaction()) {
    // 先挂起已存在的事务
    suspend(existing);
    try {
        // 而后建立一个新事务
        return createNewTransaction();
    } catch (exception) {
        // 若是新事务发生异常则恢复旧事务
        resumeAfterBeginException();
        throw exception;
    }
}
// 没有找到旧事务,直接建立新事务
return createNewTransaction();
复制代码

NESTED

对于NESTED而言,Spring会检测线程上下文中是否存在活跃的事务。

若是已经存在则在该事务上创建一个保存点,这意味着若是咱们的nestedExample()方法发生异常,该事务将会回滚到咱们创建的保存点处。

若是没有检测到活跃事务的存在,那么该选项的行为与REQUIRED选项同样。

DataSourceTransactionManager支持以开箱即用的方式使用该选项。此外,JTATransactionManager的某些实现也支持这个选项。

JpaTransactionManager仅对JDBC链接支持NESTED选项。可是,若是咱们将nestedTransactionAllowed标志设置为true,并且咱们的JDBC驱动程序也支持保存点功能,则它也适用于在JPA事务中的JDBC访问代码。

最后,让咱们把传播行为设为NESTED

@Transactional(propagation = Propagation.NESTED)
public void nestedExample(String user) { 
    // ... 
}
复制代码

事务隔离级别

隔离性是ACID属性中的一个:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

隔离性表示的是数据在并发事务间的可见性。

每种级别的隔离能够防止在并发事务中零或多个并发反作用的发生:

  • 脏读:能够读取到并发事务还未提交的更改。
  • 不可重复读:在并发事务中,某个事务对某行数据更改后,其它事务对该行的屡次读取可能会获取到不一样的结果。
  • 幻读:在并发事务中,若是一个事务添加或删除了一些行并提交,其它事务在从新执行范围查询后可能得到不一样的结果。

咱们能够经过@Transactional::isolation设置事务的隔离级别。在Spring中,它可使用下面五个枚举值:

  • DEFAULT
  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

Spring中的隔离级别管理

在Spring中事务的默认隔离级别是DEFAULT。因此,当Spring建立新事务时,隔离级别是咱们数据库中所设置的隔离级别。所以,若是咱们在对数据库中的数据进行修改时须要注意这一点。

咱们还要思考在调用链中的,当各个方法对应不一样的事务隔离级别时会出现什么状况。

一般状况下,在建立事务时也会设置事务的隔离级别,若是咱们不想让咱们的调用链中的方法存在不一样的事务隔离级别,咱们能够设置TransactionManager::setValidateExistingTransactiontrue,它的功能使用伪代码表示为:

// 若是隔离级别不是DEFAULT
if (isolationLevel != ISOLATION_DEFAULT) {
    // 当前事务设置的隔离级别与已存在事务的隔离级别不一致则抛出异常
    if (currentTransactionIsolationLevel() != isolationLevel) {
        throw IllegalTransactionStateException
    }
}
复制代码

如今让咱们深刻了解不一样的隔离级别与其做用。

READ_UNCOMMITTED

READ_UNCOMMITTED是隔离性最低的级别,具备最高的并发访问性能,该级别下会发生脏读、不可重复读与幻读的状况。

咱们能够为方法或类设置这个隔离级别:

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void log(String message) {
    // ...
}
复制代码

Postgres不支持READ_UNCOMMITTED隔离级别,它使用READ_COMMITED隔离级别做为替代。Oracle不支持READ_UNCOMMITTED级别。

READ_COMMITTED

隔离性的第二个级别是READ_COMMITTED。它能够防止脏读的发生。

这个隔离级别下仍是存在不可重复读与幻读的状况,因此在并发事务中未提交的更改对咱们没有影响,可是,在事务提交后,对一样数据的从新查询可能会发生不一样的结果。

下面的代码设置事务的隔离级别为READ_COMMITTED

@Transactional(isolation = Isolation.READ_COMMITTED)
public void log(String message){
    // ...
}
复制代码

REPEATABLE_READ

隔离性的第三个级别是REPEATABLE_READ。它能够防止脏读、不可重复读的发生。所以,咱们不受并发事务中未提交更改的影响。

一样,当咱们从新查询一行数据时,咱们不会获得不一样的结果。可是在从新执行范围查询时,咱们可能会得到新添加或删除的行。

此外,它是知足防止更新丢失的要求最低级别,当两个或多个并发事务读取并更新同一行时,就会发生更新丢失。REPEATABLE_READ不容许并发事务同时访问同一行数据。所以,不会出现更新丢失的状况。

下面的代码设置事务的隔离级别为READ_COMMITTED

@Transactional(isolation = Isolation.REPEATABLE_READ) 
public void log(String message){
    // ...
}
复制代码

REPEATABLE_READ是Mysql中的默认隔离级别。Oracle不支持REPEATABLE_READ隔离级别。

SERIALIZABLE

SERIALIZABLE是隔离等级最高的级别。它能够防止脏读、不可重复读与幻读。

由于它把并发的事务变成了串行的,因此它可能会致使最低的并发访问性能,

换句话说,并行执行一组事务的结果与串行执行它们的结果相同。

如今看一下如何把隔离级别设为SERIALIZABLE

@Transactional(isolation = Isolation.SERIALIZABLE)
public void log(String message){
    // ...
}
复制代码

总结

在本教程中,咱们详细探讨了@Transaction的传播行为。而后,咱们学习了并发事务的反作用与隔离级别。

与往常同样,你能够在GitHub上找到完整的代码。

相关文章
相关标签/搜索