spring中的@Transactional中的事务传播和隔离

1、什么是@Transactional?

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

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

2、如何使用@Transactional

我们可以将注释放在接口,类的定义上,也可以直接放在方法上。它们根据优先级顺序相互覆盖。从最低到最高,我们有:接口,超类,类,接口方法,超类方法和类方法。

Spring将类级别的注释应用于我们未使用@Transactional注释的此类的所有公共方法

但是,如果将批注放在私有或受保护的方法上,Spring将忽略它而不会出现错误。

让我们从一个接口示例开始:

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

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

@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) {
    // ...
}

3.事务传播

传播定义了业务逻辑的事务边界。Spring根据我们的传播设置设法开始和暂停事务

Spring会根据传播调用TransactionManager :: getTransaction来获取或创建一个事务。它支持所有类型的TransactionManager的某些传播,但其中的一些传播仅受TransactionManager的特定实现支持

现在,让我们看一下不同的传播方式以及它们是如何工作的。

3.1. REQUIRED Propagation

默认传播为“必需”。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();

3.2. SUPPORTS Propagation

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

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

让我们看看SUPPORTS的事务创建的伪代码

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

3.3. MANDATORY Propagation

当传播为MANDATORY时,如果存在活动事务,则将使用该传播如果没有活动的事务,那么Spring会抛出一个异常:

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

再看看伪代码

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

3.4. NEVER Propagation

对于从不传播的事务逻辑,如果存在活动事务,Spring会引发异常:

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

让我们看一下伪造代码,它说明事务创建如何从不传播:

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

3.5. NOT_SUPPORTED Propagation

Spring首先挂起当前事务(如果存在),然后执行业务逻辑而没有事务。

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

JtaTransactionManager中支持实时交易暂停外的开箱。其他人则通过持有对现有引用的引用,然后从线程上下文中将其清除来模拟该悬浮。

3.6. REQUIRES_NEW Propagation

当传播为REQUIRES_NEW时,Spring暂停当前事务(如果存在),然后创建一个新事务:

@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();

 

3.7. NESTED Propagation

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

DataSourceTransactionManager开箱即用地支持这种传播。此外,JTATransactionManager的某些实现可能支持此功能。

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

最后,让我们将传播设置NESTED

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

4. Transaction Isolation

隔离是常见的ACID属性之一:原子性,一致性,隔离性和持久性隔离描述了并发事务所应用的更改如何彼此可见。

每个隔离级别可防止对事务进行零个或多个并发副作用:

  • 脏读:读取并发事务的未提交更改
  • 不可重复读取:如果并发事务更新同一行并提交,则在重新读取一行时获得不同的值
  • 幻象读取:如果另一个事务在范围中添加或删除了一些行并提交,则在重新执行范围查询后获得不同的行

我们可以通过@Transactional :: isolation设置事务的隔离级别在春季它具有以下五个枚举:DEFAULTREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE。

4.1. Isolation Management in Spring

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

当我们调用具有不同隔离度的方法链时,还应考虑各种情况在正常流程中,隔离仅在创建新事务时适用。因此,如果由于某种原因我们不想让方法以不同的隔离方式执行,则必须将TransactionManager :: setValidateExistingTransaction设置为true。然后,交易验证的伪代码将为:

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

现在让我们深入了解不同的隔离级别及其影响。

4.2. READ_UNCOMMITTED Isolation

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

结果,它遭受了所提到的所有三个并发副作用。因此,具有这种隔离的事务将读取其他并发事务的未提交数据。同样,不可重复读取和幻像读取都可能发生。因此,在重新读取行或重新执行范围查询时,我们可以获得不同的结果。

我们可以为方法或类设置隔离级别:

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

Postgres不支持READ_UNCOMMITTED隔离,而是回退到READ_COMMITED另外,Oracle不支持并允许READ_UNCOMMITTED

4.3. READ_COMMITTED Isolation

第二个隔离级别READ_COMMITTED防止脏读。

其余的并发副作用仍然可能发生。因此,并发事务中未提交的更改对我们没有影响,但是,如果事务提交了更改,则可以通过重新查询来更改我们的结果。

在这里,我们设置隔离级别:

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

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

4.4. REPEATABLE_READ Isolation

第三个隔离级别REPEATABLE_READ防止脏的和不可重复的读取。因此,我们不受并发事务中未提交的更改的影响。

同样,当我们重新查询一行时,我们不会得到不同的结果。但是在重新执行范围查询时,我们可能会获得新添加或删除的行。

而且,它是防止丢失更新所需的最低级别。当两个或多个并发事务读取并更新同一行时,将发生丢失的更新。REPEATABLE_READ根本不允许同时访问一行。因此,丢失的更新不会发生。

以下是设置方法的隔离级别的方法:

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

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

4.5. SERIALIZABLE Isolation

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

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

现在,让我们看看如何将SERIALIZABLE设置隔离级别:

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

 

5. Conclusion

posted @ 2021-04-22 16:20  aaron616  阅读(814)  评论(0)    收藏  举报