Spring 事务管理

发布于 2026-07-30 19:36 更新于 2026-07-30 19:36 2564 字 13 min read ... 访问量

本文系统介绍了Spring事务管理的核心机制和实践要点。重点阐述了PlatformTransactionManager接口及其在JDBC、JPA、Hibernate和JTA等场景下的使用方式,详细说明了事务属性(如传播行为、隔离级别、只读标记、超时时间)的含义与配置,并分析了事务状态、回滚规则及常见失效场景。同时对比了编程式与声明式事务的实现方式,提出事务应合理配置在Service层、避免长时操作、根据业务语义选择传播行为,并强调异常处理时需确保回滚规则生效。

Spring 事务管理

Spring 事务管理相关接口的关系如下:

image-001
image-001

事务管理概述

Spring 为 JDBC、JPA、Hibernate、JTA 等不同数据访问技术提供统一的事务编程模型。业务代码主要面向 Spring 的事务抽象,而具体的提交、回滚和资源管理由相应事务管理器完成。

核心接口是 PlatformTransactionManager

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition)
            throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

getTransaction() 根据事务定义获取当前事务状态;commit()rollback() 分别负责提交与回滚。

常见事务管理器

JDBC 事务

使用 JDBC 或 JdbcTemplate 时,通常使用 DataSourceTransactionManager

<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

它通过同一数据源获取的 JDBC Connection 管理事务,并最终调用连接的 commit()rollback()

JPA 事务

使用 JPA 时,通常使用 JpaTransactionManager

<bean id="transactionManager"
      class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

JpaTransactionManager 与 JPA 的 EntityManagerFactoryEntityManager 协作管理事务。

Hibernate 事务

直接使用原生 Hibernate SessionFactory 时,可以使用 HibernateTransactionManager。如果项目通过 JPA 使用 Hibernate 实现,则应优先使用 JpaTransactionManager

<bean id="transactionManager"
      class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

JTA 事务

当一个业务操作需要协调多个事务资源时,可以使用 JTA 和 JtaTransactionManager。例如,一个事务同时涉及多个数据库或数据库与消息系统。

<bean id="transactionManager"
      class="org.springframework.transaction.jta.JtaTransactionManager"/>

JTA 的具体配置依赖应用服务器或所使用的事务协调器,不能仅靠一个固定 Bean 配置适配所有环境。

事务属性

TransactionDefinition 用于描述事务策略,主要包含传播行为、隔离级别、超时时间、只读标记和事务名称。

image-002
image-002
public interface TransactionDefinition {
    int getPropagationBehavior();

    int getIsolationLevel();

    int getTimeout();

    boolean isReadOnly();

    String getName();
}

事务传播行为

传播行为决定一个带事务的方法被其他事务方法调用时,应加入现有事务、创建新事务,还是禁止事务。

传播行为含义
REQUIRED默认值。有事务则加入,没有事务则新建事务。
SUPPORTS有事务则加入,没有事务则以非事务方式运行。
MANDATORY必须存在事务,否则抛出异常。
REQUIRES_NEW总是创建新事务;若已有事务,则暂停外层事务。
NOT_SUPPORTED以非事务方式运行;若已有事务,则暂停该事务。
NEVER必须以非事务方式运行;若存在事务,则抛出异常。
NESTED有事务时使用保存点形成嵌套范围;没有事务时行为类似 REQUIRED

REQUIRED

多个方法加入同一个物理事务,其中任意参与者把事务标记为仅回滚,最终提交时整个事务都会回滚。

@Transactional(propagation = Propagation.REQUIRED)
public void saveOrder() {
    // 业务逻辑
}

REQUIRES_NEW

内层方法使用独立事务,外层事务在内层事务执行期间被挂起。内层事务提交后,即使外层事务随后回滚,内层已提交内容通常不会随之回滚。

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditLog() {
    // 独立保存审计日志
}

NESTED

NESTED 通常基于同一个物理事务中的 JDBC 保存点实现。内层范围可以回滚到保存点,但它不能像独立事务一样脱离外层事务单独最终提交;外层事务回滚时,嵌套范围也会回滚。

该传播行为依赖事务管理器和底层资源对保存点的支持,常见于 JDBC 资源事务。

事务隔离级别

隔离级别用于控制并发事务之间的数据可见性。

并发事务常见问题

  • 脏读:读取到其他事务尚未提交的数据。
  • 不可重复读:同一事务内两次读取同一行,得到不同值,通常由其他事务更新并提交引起。
  • 幻读:同一事务内按相同条件查询两次,结果集中的行数发生变化,通常由其他事务插入或删除并提交引起。

Spring 隔离级别

隔离级别含义
DEFAULT使用数据库默认隔离级别。
READ_UNCOMMITTED允许读取未提交数据,可能出现脏读、不可重复读和幻读。
READ_COMMITTED只能读取已提交数据,可避免脏读。
REPEATABLE_READ同一事务内重复读取已读取行时保持一致,可避免脏读和不可重复读。
SERIALIZABLE事务近似串行执行,隔离程度最高,但并发性能通常最低。

不同数据库的锁机制和多版本并发控制实现不同,因此同名隔离级别在具体细节上可能存在差异。

@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateAccount() {
    // 业务逻辑
}

只读事务

只读标记表示该事务主要执行查询操作,事务管理器和数据库可以据此进行优化。

@Transactional(readOnly = true)
public User queryById(Long id) {
    return userMapper.selectById(id);
}

readOnly = true 通常是一种优化提示,不应把它当作绝对的数据库写入安全限制。是否真正禁止写操作取决于事务管理器和数据库实现。

事务超时

事务超时用于限制事务允许执行的最长时间。超时后,事务通常会被标记为回滚。

@Transactional(timeout = 10)
public void importData() {
    // 业务逻辑
}

应避免在事务中执行长时间网络调用、文件处理或人为等待,否则会长期占用数据库连接和锁资源。

回滚规则

Spring 声明式事务默认在抛出 RuntimeExceptionError 时回滚;对于普通受检异常,默认不回滚。

@Transactional(rollbackFor = Exception.class)
public void transfer() throws Exception {
    // 发生受检异常时也回滚
}

也可以通过 noRollbackFor 指定某些异常不触发回滚。

@Transactional(noRollbackFor = BusinessWarningException.class)
public void execute() {
    // 业务逻辑
}

回滚规则应根据业务语义设置,不建议无差别捕获异常后静默处理。若异常被方法内部捕获且不再抛出,事务拦截器通常无法感知该异常,也就不会按异常自动回滚。

事务状态

TransactionStatus 表示当前事务的运行状态,并提供保存点和仅回滚标记等能力。

public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    boolean hasSavepoint();

    void flush();
}

常见状态方法包括:

  • isNewTransaction():是否为当前调用新建了事务。
  • hasSavepoint():是否持有保存点。
  • setRollbackOnly():把事务标记为只能回滚。
  • isRollbackOnly():事务是否已被标记为回滚。
  • isCompleted():事务是否已经完成。

接口的具体方法会随 Spring 版本演进,使用时应以当前项目依赖中的 API 为准。

编程式事务

编程式事务由业务代码明确控制事务边界,适合事务范围需要动态决定或精细控制的场景。它的缺点是事务代码会进入业务逻辑,侵入性较强。

使用 TransactionTemplate

TransactionTemplate 是推荐的编程式事务方式之一。

@Service
public class UserService {
    private final TransactionTemplate transactionTemplate;
    private final UserRepository userRepository;

    public UserService(
            TransactionTemplate transactionTemplate,
            UserRepository userRepository) {
        this.transactionTemplate = transactionTemplate;
        this.userRepository = userRepository;
    }

    public Boolean save(User user) {
        return transactionTemplate.execute(status -> {
            try {
                userRepository.insert(user);
                return true;
            } catch (RuntimeException e) {
                status.setRollbackOnly();
                throw e;
            }
        });
    }
}

如果回调抛出运行时异常,事务模板会按规则回滚。只有在需要把异常转换为正常返回值时,才需要显式调用 setRollbackOnly()

直接使用 PlatformTransactionManager

DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = transactionManager.getTransaction(definition);
try {
    userRepository.insert(user);
    transactionManager.commit(status);
} catch (RuntimeException e) {
    transactionManager.rollback(status);
    throw e;
}

直接操作事务管理器更灵活,但需要自己保证提交、回滚和异常传播逻辑正确。

声明式事务

声明式事务基于 Spring AOP,通过配置或注解把事务逻辑应用到目标方法,业务代码不需要手动提交和回滚。

XML 配置方式

<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="get*" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="servicePointcut"
                  expression="execution(* com.example.service..*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/>
</aop:config>

需要在根元素中声明 txaop 命名空间。

注解方式

先启用事务注解:

<tx:annotation-driven transaction-manager="transactionManager"/>

也可以使用 Java 配置:

@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

在业务类或方法上使用 @Transactional

@Service
public class UserService {
    private final UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    @Transactional(rollbackFor = Exception.class)
    public void save(User user) throws Exception {
        userDao.insert(user);
    }
}

类上的注解会作为该类方法的默认事务配置;方法上的注解可以覆盖类级配置。

@Transactional 常见失效场景

同类内部调用

默认代理模式下,一个方法通过 this 调用同类中的另一个 @Transactional 方法时,调用没有经过 Spring 代理,内层方法的独立事务配置通常不会生效。

@Service
public class OrderService {
    public void createOrder() {
        // 这是同类内部调用,不会经过代理对象。
        saveOrder();
    }

    @Transactional
    public void saveOrder() {
        // 数据库操作
    }
}

通常应把需要独立事务边界的方法拆分到另一个 Spring Bean 中。

方法不可代理

在常见代理模式下,事务通常应应用于可被代理调用的公开实例方法。private 方法、静态方法和对象自行创建的方法不会按普通 Spring Bean 代理方式生效。

对象不是 Spring Bean

通过 new 手动创建的对象不受 Spring 容器管理,其 @Transactional 注解不会自动生效。

异常被捕获

@Transactional
public void save() {
    try {
        userDao.insert();
    } catch (Exception e) {
        // 异常被吞掉后,事务拦截器无法按该异常自动回滚。
    }
}

应重新抛出异常,或根据业务需要调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()

异常类型不匹配

受检异常默认不会触发回滚。需要回滚时,应通过 rollbackFor 明确配置。

使用建议

  • 事务通常放在 Service 层,而不是 Controller 层。
  • 一个事务只覆盖必须保持一致性的数据库操作。
  • 避免在事务中进行长时间远程调用。
  • 查询方法可设置为只读事务。
  • 根据业务语义选择传播行为,不要把所有方法都配置成 REQUIRES_NEW
  • 对异常进行转换时,应保留原始异常信息,并确保回滚规则仍然生效。

喜欢的话,留下你的评论吧~

... 访问量
© 2026 跨越星轨的客 @Hoshiumi
Powered by theme astro-koharu · Inspired by Shoka