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

20211030Spring @Transactional 中的事务传播和隔离

1.介绍

介绍 @Transactional 注释,以及它的隔离和传播设置。

2.什么是@Transactional?

可以使用@Transactional 将方法包装在数据库事务中。

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

2.1. @Transactional 实现细节

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

简单地说,如果我们有一个 callMethod 这样的方法,并且我们将它标记为 @Transactional,Spring 将围绕调用的 invocation@Transactional 方法包装一些事务管理代码:

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

2.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

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

3.2. SUPPORTS

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

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

伪代码如下:

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

3.3. MANDATORY

当传播为 MANDATORY 时,如果存在活动事务,则将使用它。 如果没有活动事务,则 Spring 会抛出异常:

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

伪代码如下:

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

3.4. NEVER

对于 NEVER 传播的事务逻辑,如果有活动事务,Spring 会抛出异常:

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

伪代码如下:

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

3.5. NOT_SUPPORTED

如果当前存在事务,首先Spring将其挂起,然后在没有事务的情况下执行业务逻辑:

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

JTATransactionManager支持开箱即用的实际事务暂停。另一些则通过持有对现有挂起的引用,然后从线程上下文中清除它来模拟挂起

3.6. REQUIRES_NEW

当传播为 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

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

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

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

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

4.事务隔离

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

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

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

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

4.1. Spring中的隔离管理

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

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

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

接下来深入了解不同的隔离级别及其影响。

4.2. READ_UNCOMMITTED

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

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

可以在方法或类上设置隔离级别:

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

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

4.3. READ_COMMITTED

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

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

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

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

4.4. REPEATABLE_READ

第三级隔离 REPEATABLE_READ 可防止脏读和不可重复读。 所以不会受到并发事务中未提交更改的影响。

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

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

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

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

4.5. SERIALIZABLE

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

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

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

猜你喜欢

转载自blog.csdn.net/niugang0920/article/details/121584926