Zademy

Spring程序化事务管理:初学者实用指南

spring-boot; transactions
words 字

Spring 的 @Transactional 注解做声明式事务很方便,但不是万能的。这篇 Jotting 聊聊什么时候该用程序化事务,怎么用。

为什么需要程序化事务?

连接池耗尽问题

一个很常见的场景:方法里同时有数据库操作和外部 API 调用。

@Transactional
public void processPayment(PaymentRequest request) {
    savePaymentRequest(request);              // 数据库
    callPaymentProviderApi(request);          // 外部API(慢)
    updatePaymentState(request);              // 数据库
    saveAuditHistory(request);                // 数据库
}

问题在哪?@Transactional 开启事务后:

  1. 从连接池拿一条连接,整个方法期间一直握着
  2. 连接被占用,等外部 API 响应
  3. 如果 API 要 5-10 秒,这条连接整个期间都是阻塞的
  4. 高并发下,慢 API 会把连接池里的连接全部吃光

黄金法则: 同一个事务里,别混数据库操作和外部 API 调用。

解决方案1:TransactionTemplate(推荐)

TransactionTemplate 提供基于回调的 API,手动管理事务。干净、现代,是我处理程序化事务的首选。

基本配置

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.stereotype.Component;

@Component
public class PaymentService {

    private final TransactionTemplate transactionTemplate;

    public PaymentService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }
}

注意: Spring Boot 会自动配置 PlatformTransactionManager,直接注入就行。

示例1:带返回值的事务

public Long createSuccessfulPayment(PaymentRequest request) {
    // 在事务内执行代码并返回ID
    Long paymentId = transactionTemplate.execute(status -> {
        Payment payment = new Payment();
        payment.setAmount(request.getAmount());
        payment.setReferenceNumber(request.getReference());
        payment.setState(Payment.State.SUCCESSFUL);

        entityManager.persist(payment);

        // ID在persist后自动生成
        return payment.getId();
    });

    return paymentId;
}

原理很简单:自动开事务,执行 lambda,正常就提交,抛异常就回滚,最后返回 lambda 的值。

示例2:异常时的自动回滚

public void createTwoPaymentsWithRollback() {
    try {
        transactionTemplate.execute(status -> {
            Payment first = new Payment();
            first.setReferenceNumber("REF-001");
            first.setAmount(1000L);
            entityManager.persist(first);  // 正常

            Payment second = new Payment();
            second.setReferenceNumber("REF-001"); // 重复!
            second.setAmount(2000L);
            entityManager.persist(second);  // 抛出异常

            return null;
        });
    } catch (Exception e) {
        // 第一笔付款也会被回滚 - 保证原子性
        System.out.println("事务已回滚:" + e.getMessage());
    }
}

第一笔 payment 也会跟着回滚,原子性有保证。

示例3:显式手动回滚

public Long createPaymentWithValidation(PaymentRequest request) {
    return transactionTemplate.execute(status -> {
        Payment payment = new Payment();
        payment.setReferenceNumber(request.getReference());
        payment.setAmount(request.getAmount());

        entityManager.persist(payment);

        // 自定义业务验证
        if (request.getAmount() > 100000) {
            // 标记为回滚 - 事务将被撤销
            status.setRollbackOnly();
            return null;  // 或抛出自定义异常
        }

        return payment.getId();
    });
}

业务校验不过的时候,status.setRollbackOnly() 标记回滚,事务撤销。

示例4:无返回值的事务

public void saveAuditLogEntry(AuditEntry entry) {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            auditRepository.save(entry);
            // 不返回任何内容,仅执行操作
        }
    });
}

不需要返回值时用 TransactionCallbackWithoutResult。

示例5:每个实例的自定义配置

可以创建多个 TransactionTemplate 实例,每个配不同的隔离级别、传播行为、超时:

@Component
public class TransactionConfig {

    @Bean
    public TransactionTemplate readOnlyTransactionTemplate(PlatformTransactionManager txManager) {
        TransactionTemplate template = new TransactionTemplate(txManager);
        template.setReadOnly(true);  // 查询优化
        return template;
    }

    @Bean
    public TransactionTemplate serializableTransactionTemplate(PlatformTransactionManager txManager) {
        TransactionTemplate template = new TransactionTemplate(txManager);
        template.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
        template.setTimeout(30);  // 秒
        return template;
    }

    @Bean
    public TransactionTemplate requiresNewTransactionTemplate(PlatformTransactionManager txManager) {
        TransactionTemplate template = new TransactionTemplate(txManager);
        template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        return template;
    }
}

可用配置

属性值描述
setIsolationLevel()ISOLATION_READ_UNCOMMITTED读取未提交隔离级别
ISOLATION_READ_COMMITTED只读取已提交数据
ISOLATION_REPEATABLE_READ防止不可重复读
ISOLATION_SERIALIZABLE最大隔离级别
setPropagationBehavior()PROPAGATION_REQUIRED使用现有事务或创建新事务
PROPAGATION_REQUIRES_NEW始终创建新事务
PROPAGATION_NESTED嵌套事务(保存点)
setTimeout()秒(int)最大执行时间
setReadOnly()true/false只读优化

解决方案2:PlatformTransactionManager(底层)

需要完全控制的时候,直接上手 PlatformTransactionManager。这是 @Transactional 和 TransactionTemplate 底层用的 API。

示例:完全手动控制

import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Component
public class ManualTransactionService {

    private final PlatformTransactionManager transactionManager;

    public ManualTransactionService(PlatformTransactionManager txManager) {
        this.transactionManager = txManager;
    }

    public void processWithTotalControl() {
        // 1. 定义事务配置
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
        definition.setTimeout(5);  // 5秒
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        // 2. 启动事务
        TransactionStatus status = transactionManager.getTransaction(definition);

        try {
            // 3. 执行业务操作
            Payment payment = new Payment();
            payment.setAmount(500L);
            payment.setReferenceNumber("MANUAL-001");
            entityManager.persist(payment);

            // 4. 提交事务
            transactionManager.commit(status);

        } catch (Exception ex) {
            // 5. 出错时回滚
            transactionManager.rollback(status);
            throw new RuntimeException("手动事务出错", ex);
        }
    }
}

对比:TransactionTemplate vs PlatformTransactionManager

方面TransactionTemplatePlatformTransactionManager
抽象级别高(回调)低(手动)
错误处理自动手动(try-catch)
自动回滚是否(必须调用)
结果代码更简洁更冗长
灵活性受回调限制完全控制
推荐使用大多数情况需要精细控制时

实践案例:将数据库与外部API分离

原始问题,连接池会被耗尽:

@Transactional
public void processOrder(Order order) {
    orderRepository.save(order);              // 数据库
    shippingApi.createShipment(order);        // 慢速API
    notificationService.notifyCustomer(order); // 慢速API
}

用 TransactionTemplate 的方案:

@Component
public class OrderService {

    private final TransactionTemplate txTemplate;
    private final OrderRepository orderRepository;
    private final ShippingApiService shippingApi;
    private final NotificationService notificationService;

    public void processOrderSafely(Order order) {
        // 步骤1:仅在事务内进行数据库部分
        Long orderId = txTemplate.execute(status -> {
            order.setStatus("PROCESSING");
            orderRepository.save(order);
            return order.getId();
        });
        // 连接已释放!

        // 步骤2:外部API(不占用连接)
        String trackingNumber = shippingApi.createShipment(order);

        // 步骤3:另一个外部API(不占用连接)
        notificationService.notifyCustomer(order);

        // 步骤4:更新最终状态(新的短事务)
        txTemplate.executeWithoutResult(status -> {
            Order updated = orderRepository.findById(orderId).orElseThrow();
            updated.setTrackingNumber(trackingNumber);
            updated.setStatus("SHIPPED");
        });
    }
}

注意看:数据库操作分两个短事务完成,中间的外部 API 调用不占用连接。这才是正确的写法。

何时使用每种方法

@Transactional(声明式)适合简单 CRUD,没有外部 API 调用,不需要复杂条件控制,代码可读性优先的场景。

TransactionTemplate(程序化)在需要混合数据库操作和外部 I/O、条件逻辑决定回滚、同一服务里需要不同事务配置、需要从事务中返回值的时候用。

PlatformTransactionManager 适合需要完全控制生命周期、一个方法里开多个事务、和非标准事务系统集成、需要详细事务日志或审计的场景。

结论

程序化事务能提供 @Transactional 给不了的控制粒度。大多数需要把数据库操作和外部调用分离的场景,TransactionTemplate 是我推荐的方案。

记住: 目标不是替换 @Transactional,而是在声明式方式约束你的设计时,多一个选择。


参考文献和额外资源

官方文档

  • Spring Framework - Programmatic Transaction Management: Spring 团队关于程序化事务管理的完整指南。 docs.spring.io
  • Spring Data Access Documentation: 关于数据访问和事务的官方文档。 docs.spring.io

推荐文章

  • Vlad Mihalcea - Spring Transaction and Connection Management: 深入分析 Spring 如何处理数据库连接和事务。 vladmihalcea.com
  • Baeldung - Programmatic Transaction Management in Spring: TransactionTemplate 和 PlatformTransactionManager 的实用教程。 baeldung.com
  • Marco Behler - Spring Transaction Management @Transactional In-Depth: 关于 Spring 事务内部运作的详细指南。 marcobehler.com

额外的最佳实践

  1. 在连接池中配置 auto-commit=false,启用延迟连接获取。
  2. 使用 Hibernate 时设置 hibernate.connection.provider_disables_autocommit=true。
  3. 考虑使用 DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION,最大化连接重用。
  4. 事务方法应在执行流程的尽可能晚的阶段调用,设计服务层时注意这一点。