那些年背过的题:Spring事务机制设计与实现

366 阅读7分钟

Spring 事务管理机制是 Spring 框架的重要组成部分,通过它可以简化数据库事务的管理。Spring 提供了声明式事务管理和编程式事务管理两种方式,其中声明式事务管理最为常用。

声明式事务管理

声明式事务管理通过配置和注解的方式,将事务管理逻辑从业务代码中分离出来,使得代码更加简洁和易维护。

关键配置和注解

  1. 在 XML 中配置事务管理器:

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="
               http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans.xsd
               http://www.springframework.org/schema/tx
               http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
            <property name="username" value="user"/>
            <property name="password" value="password"/>
        </bean>
    
        <!-- 配置事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!-- 启用声明式事务管理 -->
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
    </beans>
    
  2. 使用注解进行事务管理:

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class MyService {
    
        @Autowired
        private MyRepository myRepository;
    
        @Transactional
        public void performTransactionalOperation() {
            // 业务逻辑
            myRepository.saveSomeData();
            // 其他操作
        }
    }
    

核心类和接口

  • PlatformTransactionManager:事务管理器接口,不同类型的事务(如 JDBC、JPA)有不同的实现。
  • TransactionDefinition:定义事务的属性,如传播行为、隔离级别等。
  • TransactionStatus:表示事务的当前状态。

编程式事务管理

编程式事务管理需要在代码中显式地管理事务,适用于对事务管理要求更为精细控制的场景。

示例代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Service
public class MyService {

    @Autowired
    private PlatformTransactionManager transactionManager;
    
    @Autowired
    private MyRepository myRepository;

    public void performTransactionalOperation() {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        
        TransactionStatus status = transactionManager.getTransaction(def);
        
        try {
            // 业务逻辑
            myRepository.saveSomeData();
            // 其他操作
            
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

事务传播行为

在 Spring 事务管理中,事务传播行为用于定义当一个事务方法被另一个事务方法调用时,事务应该如何进行协调。以下是各个事务传播行为的对比及其应用场景:

1. PROPAGATION_REQUIRED

描述

如果当前没有事务,则新建一个事务;如果当前有事务,则加入到这个事务中。

应用场景

这是最常用的传播行为,适用于大多数情况。例如,一个服务层方法调用另一个需要事务的方法时,希望它们共享同一个事务,以确保数据一致性。

@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
    innerMethod();
}

@Transactional(propagation = Propagation.REQUIRED)
public void innerMethod() {
    // ...
}

2. PROPAGATION_REQUIRES_NEW

描述

总是新建一个事务。如果当前存在事务,挂起当前事务。

应用场景

适用于必须独立于外部事务执行的操作。例如,记录日志或审计信息,这些操作不应因外部事务的回滚而受影响(内外部事务任何一个回滚,另一个都不受影响)。

@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
    try {
        // 主业务逻辑
    } catch (Exception e) {
        logError(e);
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logError(Exception e) {
    // 独立事务,记录错误日志
}

3. PROPAGATION_SUPPORTS

描述

支持当前事务,如果当前没有事务,就以非事务方式执行。

应用场景

适用于既可以在事务内执行,也可以在非事务环境下执行的操作。例如,读取一些不太重要的数据,不要求强一致性。

@Transactional(propagation = Propagation.SUPPORTS)
public void fetchData() {
    // 读操作,可以在事务环境或非事务环境下执行
}

4. PROPAGATION_NOT_SUPPORTED

描述

以非事务方式执行操作,如果当前存在事务,挂起当前事务。

应用场景

适用于不需要事务的场景,且希望避免事务带来的性能开销。例如,批量数据处理中的某些操作。

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void processBatch() {
    // 非事务操作
}

5. PROPAGATION_NEVER

描述

以非事务方式执行,如果当前存在事务,则抛出异常。

应用场景

适用于明确不允许在事务环境中执行的操作。例如,某些读操作希望确保没有任何事务存在。

@Transactional(propagation = Propagation.NEVER)
public void readWithoutTransaction() {
    // 如果有事务存在,将会抛出异常
}

6. PROPAGATION_MANDATORY

描述

支持当前事务,如果当前没有事务,则抛出异常。

应用场景

适用于必须在事务环境中执行的操作。如果调用该方法时没有事务存在,需要显式地报告错误。

@Transactional(propagation = Propagation.MANDATORY)
public void mustRunInTransaction() {
    // 如果没有事务存在,将会抛出异常
}

7. PROPAGATION_NESTED

描述

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则新建一个事务。

应用场景

适用于需要保存子事务点的场景。例如,在复杂的业务操作中,部分成功,部分失败时希望能够回滚到子事务点(内部事务回滚,外部事务不受影响;外部事务回滚,内部事务也会回滚)。

嵌套事务的工作机制

PROPAGATION_NESTED 使用保存点(Savepoint)来实现事务的嵌套。当内部事务开始时,会创建一个保存点,如果内部事务回滚,只会回滚到这个保存点,不影响外部事务的其余部分。然而,如果外部事务回滚,则所有嵌套的事务都会回滚。

@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
    saveParent();
    try {
        nestedMethod();
    } catch (Exception e) {
        // 处理嵌套事务的异常
    }
}

@Transactional(propagation = Propagation.NESTED)
public void nestedMethod() {
    saveChild();
    // 出现异常时,可以回滚到 saveChild() 前的状态,而不影响 saveParent()
}

事务隔离级别

事务隔离级别是数据库事务管理中的重要概念,它决定了一个事务在并发操作时如何可见其他事务的修改。不同的隔离级别提供不同程度的数据一致性和并发性能。这些隔离级别通常依赖于底层数据库系统的实现。以下是详细的解释:

1. ISOLATION_READ_UNCOMMITTED(未提交读)

描述

允许一个事务读取另一个事务没有提交的更改。

问题

  • 脏读:可能读取到其他事务尚未提交的数据。
  • 不可重复读:同一个事务两次读取同一数据,结果可能不同。
  • 幻读:同一个事务两次查询条件相同的数据集,结果可能不同。

应用场景

适用于不太关心数据一致性的应用,例如日志记录或监控系统。

// 示例:读取未提交的数据
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void readUncommittedData() {
    // 业务逻辑
}

2. ISOLATION_READ_COMMITTED(已提交读)

描述

只允许一个事务读取另一个事务已经提交的更改,这是大多数数据库的默认隔离级别。

问题

  • 不可重复读:同一个事务两次读取同一数据,结果可能不同。
  • 幻读:同一个事务两次查询条件相同的数据集,结果可能不同。

应用场景

适用于大多数常规应用,既能保证一定的一致性,又有较好的并发性能。

// 示例:读取已提交的数据
@Transactional(isolation = Isolation.READ_COMMITTED)
public void readCommittedData() {
    // 业务逻辑
}

3. ISOLATION_REPEATABLE_READ(可重复读)

描述

确保同一事务内的多次读取结果一致,防止不可重复读,但仍可能发生幻读。

问题

  • 幻读:同一个事务两次查询条件相同的数据集,结果可能不同。

应用场景

适用于需要较高数据一致性的场景,例如金融交易或库存管理系统。

// 示例:可重复读取数据
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void repeatableReadData() {
    // 业务逻辑
}

4. ISOLATION_SERIALIZABLE(可串行化)

描述

最高的隔离级别,通过强制事务顺序执行,防止脏读、不可重复读和幻读。

问题

  • 性能开销大,可能导致大量锁争用和阻塞。

应用场景

适用于对数据一致性要求极高的场景,通常在实际应用中较少使用。

// 示例:串行化读取数据
@Transactional(isolation = Isolation.SERIALIZABLE)
public void serializableReadData() {
    // 业务逻辑
}

隔离级别对比

隔离级别脏读不可重复读幻读
READ_UNCOMMITTED可能可能可能
READ_COMMITTED不可能可能可能
REPEATABLE_READ不可能不可能可能
SERIALIZABLE不可能不可能不可能

总结

选择合适的隔离级别需要权衡数据一致性和系统性能:

  • READ_UNCOMMITTED:性能最好,但数据一致性最低,适用于不要求高一致性的场景。
  • READ_COMMITTED:一般默认隔离级别,平衡了性能和一致性,适用于大多数应用。
  • REPEATABLE_READ:提高了一致性,适用于需要多次读取一致结果的场景,如财务系统。
  • SERIALIZABLE:最高一致性,但性能最差,适用于要求极高数据完整性的关键业务。