Transaction Propagation and Isolation in Spring @Transactional – Spring中的事务传播和隔离@Transactional

最后修改: 2019年 10月 20日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll cover the @Transactional annotation, as well as its isolation and propagation settings.

在本教程中,我们将介绍@Transactional 注解,以及其隔离传播设置。

2. What Is @Transactional?

2.什么是@Transactional?

We can use @Transactional to wrap a method in a database transaction.

我们可以使用@Transactional来将一个方法包裹在数据库事务中。

It allows us to set propagation, isolation, timeout, read-only, and rollback conditions for our transaction. We can also specify the transaction manager.

它允许我们为事务设置传播、隔离、超时、只读和回滚条件。我们还可以指定事务管理器。

2.1. @Transactional Implementation Details

2.1.@Transactional 实施细节

Spring creates a proxy, or manipulates the class byte-code, to manage the creation, commit, and rollback of the transaction. In the case of a proxy, Spring ignores @Transactional in internal method calls.

Spring创建了一个代理,或操纵类的字节码,以管理事务的创建、提交和回滚。在代理的情况下,Spring忽略了内部方法调用中的@Transactional

Simply put, if we have a method like callMethod and we mark it as @Transactional, Spring will wrap some transaction management code around the invocation@Transactional method called:

简单地说,如果我们有一个类似callMethod的方法,并将其标记为@Transactional,Spring将在调用@Transactional方法的周围包装一些事务管理代码。

createTransactionIfNecessary();
try {
    callMethod();
    commitTransactionAfterReturning();
} catch (exception) {
    completeTransactionAfterThrowing();
    throw exception;
}

2.2. How to Use @Transactional

2.2.如何使用@Transactional

We can put the annotation on definitions of interfaces, classes, or directly on methods.  They override each other according to the priority order; from lowest to highest we have: interface, superclass, class, interface method, superclass method, and class method.

我们可以把注解放在接口、类的定义上,或者直接放在方法上。 它们根据优先级顺序相互覆盖;从低到高我们有:接口、超类、类、接口方法、超类方法和类方法。

Spring applies the class-level annotation to all public methods of this class that we did not annotate with @Transactional.

Spring对我们没有用@Transactional注解的该类的所有公共方法应用类级注解。

However, if we put the annotation on a private or protected method, Spring will ignore it without an error.

但是,如果我们把注解放在一个私有或受保护的方法上,Spring会忽略它而不出错。

Let’s start with an interface sample:

让我们从一个界面样本开始。

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

Usually it’s not recommended to set @Transactional on the interface; however, it is acceptable for cases like @Repository with Spring Data. We can put the annotation on a class definition to override the transaction setting of the interface/superclass:

通常不建议在接口上设置@Transactional;但是,对于像Spring Data的@Repository这样的情况是可以接受的。我们可以把注解放在类的定义上,以覆盖接口/超类的事务设置。

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

Now let’s override it by setting the annotation directly on the method:

现在让我们通过在方法上直接设置注解来覆盖它。

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

3. Transaction Propagation

3.事务传播

Propagation defines our business logic’s transaction boundary. Spring manages to start and pause a transaction according to our propagation setting.

传播定义了我们业务逻辑的事务边界。Spring根据我们的传播设置来管理事务的启动和暂停。

Spring calls TransactionManager::getTransaction to get or create a transaction according to the propagation. It supports some of the propagations for all types of TransactionManager, but there are a few of them that are only supported by specific implementations of TransactionManager.

Spring调用TransactionManager::getTransaction来根据传播获得或创建一个事务。它支持所有类型的TransactionManager的一些传播,但也有一些传播只被TransactionManager的特定实现所支持。

Let’s go through the different propagations and how they work.

让我们来看看不同的传播方式以及它们是如何工作的。

3.1. REQUIRED Propagation

3.1.REQUIREDPropagation

REQUIRED is the default propagation. Spring checks if there is an active transaction, and if nothing exists, it creates a new one. Otherwise, the business logic appends to the currently active transaction:

REQUIRED是默认的传播方式。Spring检查是否有一个活动的事务,如果没有,它就创建一个新的。否则,业务逻辑会追加到当前的活动事务中。

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

Furthermore, since REQUIRED is the default propagation, we can simplify the code by dropping it:

此外,由于REQUIRED是默认的传播方式,我们可以通过放弃它来简化代码。

@Transactional
public void requiredExample(String user) { 
    // ... 
}

Let’s see the pseudo-code of how transaction creation works for REQUIRED propagation:

让我们看看事务创建如何为REQUIRED传播工作的伪代码。

if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    return existing;
}
return createNewTransaction();

3.2. SUPPORTS Propagation

3.2.SUPPORTSPropagation

For SUPPORTS, Spring first checks if an active transaction exists. If a transaction exists, then the existing transaction will be used. If there isn’t a transaction, it is executed non-transactional:

对于SUPPORTS,Spring首先检查是否存在一个活动的事务。如果存在一个事务,那么将使用现有的事务。如果没有事务,则以非交易方式执行。

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

Let’s see the transaction creation’s pseudo-code for SUPPORTS:

让我们看看交易创建的SUPPORTS的伪代码。

if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    return existing;
}
return emptyTransaction;

3.3. MANDATORY Propagation

3.3.MANDATORYPropagation

When the propagation is MANDATORY, if there is an active transaction, then it will be used. If there isn’t an active transaction, then Spring throws an exception:

当传播是MANDATORY时,如果有一个活动的事务,那么它将被使用。如果没有活动的事务,那么Spring会抛出一个异常。

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

Let’s again see the pseudo-code:

让我们再次看看伪代码。

if (isExistingTransaction()) {
    if (isValidateExistingTransaction()) {
        validateExisitingAndThrowExceptionIfNotValid();
    }
    return existing;
}
throw IllegalTransactionStateException;

3.4. NEVER Propagation

3.4.NEVERPropagation

For transactional logic with NEVER propagation, Spring throws an exception if there’s an active transaction:

对于具有NEVER传播的事务性逻辑,如果有一个活动的事务,Spring会抛出一个异常。

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

Let’s see the pseudo-code of how transaction creation works for NEVER propagation:

让我们看看事务创建在NEVER传播中的工作原理的伪代码。

if (isExistingTransaction()) {
    throw IllegalTransactionStateException;
}
return emptyTransaction;

3.5. NOT_SUPPORTED Propagation

3.5.NOT_SUPPORTEDPropagation

If a current transaction exists, first Spring suspends it, and then the business logic is executed without a transaction:

如果当前的事务存在,首先Spring会暂停它,然后在没有事务的情况下执行业务逻辑。

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

The JTATransactionManager supports real transaction suspension out-of-the-box. Others simulate the suspension by holding a reference to the existing one and then clearing it from the thread context

JTATransactionManager支持真正的事务暂停,即开即用。其他人通过持有对现有事务的引用来模拟暂停,然后从线程上下文中清除它

3.6. REQUIRES_NEW Propagation

3.6.REQUIRES_NEW传播

When the propagation is REQUIRES_NEW, Spring suspends the current transaction if it exists, and then creates a new one:

当传播是REQUIRES_NEW时,如果当前事务存在,Spring会暂停它,然后创建一个新的事务。

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

Similar to NOT_SUPPORTED, we need the JTATransactionManager for actual transaction suspension.

NOT_SUPPORTED类似,我们需要JTATransactionManager来实现实际的交易暂停。

The pseudo-code looks like so:

伪代码看起来是这样的。

if (isExistingTransaction()) {
    suspend(existing);
    try {
        return createNewTransaction();
    } catch (exception) {
        resumeAfterBeginException();
        throw exception;
    }
}
return createNewTransaction();

3.7. NESTED Propagation

3.7.内嵌式传播

For NESTED propagation, Spring checks if a transaction exists, and if so, it marks a save point. This means that if our business logic execution throws an exception, then the transaction rollbacks to this save point. If there’s no active transaction, it works like REQUIRED.

对于NESTED传播,Spring检查事务是否存在,如果存在,它将标记一个保存点。这意味着如果我们的业务逻辑执行抛出一个异常,那么事务就会回滚到这个保存点。如果没有活动的事务,它的工作方式是REQUIRED

DataSourceTransactionManager supports this propagation out-of-the-box. Some implementations of JTATransactionManager may also support this.

DataSourceTransactionManager支持这种开箱即用的传播。JTATransactionManager的一些实现也可能支持这一点。

JpaTransactionManager supports NESTED only for JDBC connections. However, if we set the nestedTransactionAllowed flag to true, it also works for JDBC access code in JPA transactions if our JDBC driver supports save points.

JpaTransactionManager仅对JDBC连接支持NESTED。然而,如果我们将nestedTransactionAllowed标志设置为true,那么如果我们的JDBC驱动程序支持保存点,它也可以用于JPA事务中的JDBC访问代码。

Finally, let’s set the propagation to NESTED:

最后,让我们把传播设为NESTED

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

4. Transaction Isolation

4.事务隔离

Isolation is one of the common ACID properties: Atomicity, Consistency, Isolation, and Durability. Isolation describes how changes applied by concurrent transactions are visible to each other.

隔离性是常见的ACID属性之一。原子性、一致性、隔离性和持久性。隔离描述了由并发事务应用的变化如何对彼此可见。

Each isolation level prevents zero or more concurrency side effects on a transaction:

每个隔离级别都能防止事务上的零个或多个并发副作用。

  • Dirty read: read the uncommitted change of a concurrent transaction
  • Nonrepeatable read: get different value on re-read of a row if a concurrent transaction updates the same row and commits
  • Phantom read: get different rows after re-execution of a range query if another transaction adds or removes some rows in the range and commits

We can set the isolation level of a transaction by @Transactional::isolation. It has these five enumerations in Spring: DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE.

我们可以通过@Transactional::isolation来设置事务的隔离级别。它在Spring中有这五个枚举。DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE。

4.1. Isolation Management in Spring

4.1.Spring中的隔离管理

The default isolation level is DEFAULT. As a result, when Spring creates a new transaction, the isolation level will be the default isolation of our RDBMS. Therefore, we should be careful if we change the database.

默认的隔离级别是DEFAULT。因此,当Spring创建一个新事务时,隔离级别将是我们RDBMS的默认隔离。因此,如果我们改变了数据库,我们应该小心。

We should also consider cases when we call a chain of methods with different isolation. In the normal flow, the isolation only applies when a new transaction is created. Thus, if for any reason we don’t want to allow a method to execute in different isolation, we have to set TransactionManager::setValidateExistingTransaction to true.

我们还应该考虑当我们调用具有不同隔离度的方法链时的情况在正常的流程中,隔离度只在新的事务被创建时适用。因此,如果出于任何原因,我们不想让一个方法在不同的隔离中执行,我们必须将TransactionManager::setValidateExistingTransaction设为true。

Then the pseudo-code of transaction validation will be:

那么交易验证的伪代码将是。

if (isolationLevel != ISOLATION_DEFAULT) {
    if (currentTransactionIsolationLevel() != isolationLevel) {
        throw IllegalTransactionStateException
    }
}

Now let’s get deep in different isolation levels and their effects.

现在让我们深入了解一下不同的隔离水平及其影响。

4.2. READ_UNCOMMITTED Isolation

4.2.READ_UNCOMMITTED 孤立性

READ_UNCOMMITTED is the lowest isolation level and allows for the most concurrent access.

READ_UNCOMMITTED是最低的隔离级别,允许最多的并发访问。

As a result, it suffers from all three mentioned concurrency side effects. A transaction with this isolation reads uncommitted data of other concurrent transactions. Also, both non-repeatable and phantom reads can happen. Thus we can get a different result on re-read of a row or re-execution of a range query.

因此,它受到上述所有三种并发副作用的影响。一个具有这种隔离的事务会读取其他并发事务的未提交数据。另外,不可重复的和幻象的读取都可能发生。因此,我们可以在重新读取一行或重新执行一个范围查询时得到不同的结果。

We can set the isolation level for a method or class:

我们可以为一个方法或类设置隔离级别。

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

Postgres does not support READ_UNCOMMITTED isolation and falls back to READ_COMMITED instead. Also, Oracle does not support or allow READ_UNCOMMITTED.

Postgres不支持READ_UNCOMMITTED隔离,而是退回到READ_COMMITED替代。此外,Oracle也不支持或允许READ_UNCOMMITTED

4.3. READ_COMMITTED Isolation

4.3.READ_COMMITTED 孤立性

The second level of isolation, READ_COMMITTED, prevents dirty reads.

第二层隔离,READ_COMMITTED,防止脏读。

The rest of the concurrency side effects could still happen. So uncommitted changes in concurrent transactions have no impact on us, but if a transaction commits its changes, our result could change by re-querying.

其余的并发副作用仍然可能发生。因此,并发事务中未提交的变更对我们没有影响,但是如果一个事务提交了它的变更,我们的结果可能会因为重新查询而改变。

Here we set the isolation level:

这里我们设置隔离级别。

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

READ_COMMITTED is the default level with Postgres, SQL Server, and Oracle.

READ_COMMITTED是Postgres、SQL Server和Oracle的默认级别。

4.4. REPEATABLE_READ Isolation

4.4.REPEATABLE_READ 孤立性

The third level of isolation, REPEATABLE_READ, prevents dirty, and non-repeatable reads. So we are not affected by uncommitted changes in concurrent transactions.

第三层隔离,REPEATABLE_READ,防止脏的、不可重复的读取。所以我们不会受到并发事务中未提交的变更的影响。

Also, when we re-query for a row, we don’t get a different result. However, in the re-execution of range-queries, we may get newly added or removed rows.

另外,当我们重新查询某行时,我们不会得到不同的结果。然而,在重新执行范围查询时,我们可能会得到新添加或删除的行。

Moreover, it is the lowest required level to prevent the lost update. The lost update occurs when two or more concurrent transactions read and update the same row. REPEATABLE_READ does not allow simultaneous access to a row at all. Hence the lost update can’t happen.

此外,它是防止丢失更新的最低要求级别。当两个或多个并发事务读取和更新同一行时,就会发生丢失更新。REPEATABLE_READ 根本不允许同时访问一条记录。因此,丢失的更新不可能发生。

Here is how to set the isolation level for a method:

下面是如何为一个方法设置隔离级别。

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

REPEATABLE_READ is the default level in Mysql. Oracle does not support REPEATABLE_READ.

REPEATABLE_READ是Mysql的默认级别。Oracle不支持REPEATABLE_READ.

4.5. SERIALIZABLE Isolation

4.5.SERIALIZABLE 隔离性

SERIALIZABLE is the highest level of isolation. It prevents all mentioned concurrency side effects, but can lead to the lowest concurrent access rate because it executes concurrent calls sequentially.

SERIALIZABLE是最高级别的隔离。它可以防止所有提到的并发性副作用,但由于它是按顺序执行并发性调用,因此可能导致最低的并发性访问率。

In other words, concurrent execution of a group of serializable transactions has the same result as executing them in serial.

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

Now let’s see how to set SERIALIZABLE as the isolation level:

现在让我们看看如何设置SERIALIZABLE作为隔离级别。

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

5. Conclusion

5.总结

In this article, we explored the propagation property of @Transaction in detail. We then learned about concurrency side effects and isolation levels.

在这篇文章中,我们详细探讨了@Transaction的传播属性。然后我们了解了并发的副作用和隔离级别。

As always, the complete code for this article is available over on GitHub.

与往常一样,本文的完整代码可在GitHub上获得over