Zademy

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

spring-boot; transactions
words 字

Spring的@Transactional注解在声明式事务管理方面表现出色,但并不总是最佳选择。在本篇Jotting中,您将了解何时以及如何在Spring中使用程序化事务控制,这对于需要精细管理事务生命周期的场景非常理想。

为什么需要程序化事务?

连接池耗尽问题

想象一下这个常见场景:一个将数据库调用与外部API相结合的方法:

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

这里的问题是什么?

当Spring使用@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());
    }
}

示例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();
    });
}

示例4:无返回值的事务

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

示例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。这是@TransactionalTransactionTemplate内部使用的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");
        });
    }
}

何时使用每种方法

使用@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. 设计服务层 - 事务方法应在执行的尽可能晚的阶段调用