文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

聊聊如何优雅地Spring事务编程

2024-11-29 23:02

关注

Spring 作为一个高度灵活和可扩展的框架,早就提供了一个强大的扩展点,即事务同步器 TransactionSynchronization 。通过 TransactionSynchronization ,我们可以轻松地控制事务生命周期中的关键阶段,实现自定义的业务逻辑与事务管理的结合。

package org.springframework.transaction.support;

import java.io.Flushable;

public interface TransactionSynchronization extends Flushable {
   
    int STATUS_COMMITTED = 0;
   
    int STATUS_ROLLED_BACK = 1;
   
    int STATUS_UNKNOWN = 2;
    //挂起该事务同步器
    default void suspend() {
    }
    //恢复事务同步器
    default void resume() {
    }
    //flush底层的session到数据库
    default void flush() {
    }
   // 事务提交之前
    default void beforeCommit(boolean readOnly) {
    }
  // 操作完成之前(包含commit/rollback)
    default void beforeCompletion() {
    }
   // 事务提交之后
    default void afterCommit() {
    }
   // 操作完成之后(包含commit/rollback)
    default void afterCompletion(int status) {
    }
}

TransactionSynchronization 是一个接口,它里面定义了一系列与事务各生命周期阶段相关的方法。比如,我们可以这样使用:

public class UserService {

    @Transactional(rollbackFor = Exception.class)
    public void saveUser(User user) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                System.out.println("saveUser事务已提交...");
            }
        });
        userDao.saveUser(user);
    }
}

在 Spring 事务刚开始的时候,我们向 TransactionSynchronizationManager 事务同步管理器注册了一个事务同步器,事务提交前/后,会遍历执行事务同步器中对应的事务同步方法(一个 Spring 事务可以注册多个事务同步器)。

需要注意的是注册事务同步器必须得在一个 Spring 事务中才能注册,否则会抛出 Transaction synchronization is not active 这个错误。

图片

isSynchronizationActive() 方法用来判断当前是否存在事务(判断线程共享变量,是否存在 TransactionSynchronization)

图片

Spring 在创建事务的时候,会初始化一个空集合放到 synchronizations 属性中,所以只要当前存在事务,isSynchronizationActive()  就为 true。

TransactionSynchronizationManager 解析

Spring 对于事务的管理都是基于 TransactionSynchronizationManager 这个类,先看下 TransactionSynchronizationManager 的一些属性:

private static final ThreadLocal> resources = new NamedThreadLocal("Transactional resources");
    private static final ThreadLocal> synchronizations = new NamedThreadLocal("Transaction synchronizations");
    private static final ThreadLocal currentTransactionName = new NamedThreadLocal("Current transaction name");
    private static final ThreadLocal currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
    private static final ThreadLocal currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
    private static final ThreadLocal actualTransactionActive = new NamedThreadLocal("Actual transaction active");

Spring 创建事务时,DataSourceTransactionManager.doBegin 方法中,将新创建的 connection 包装成 connectionHolder ,通过 TransactionSynchronizationManager#bindResource 方法存入 resources 中。

然后标注到一个事务当中的其它数据库操作就可以通过 TransactionSynchronizationManager#getResource 方法获取到这个连接。

@Nullable
    public static Object getResource(Object key) {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Object value = doGetResource(actualKey);
        if (value != null && logger.isTraceEnabled()) {
            logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
        }

        return value;
    }

    @Nullable
    private static Object doGetResource(Object actualKey) {
        Map map = (Map)resources.get();
        if (map == null) {
            return null;
        } else {
            Object value = map.get(actualKey);
            if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
                map.remove(actualKey);
                if (map.isEmpty()) {
                    resources.remove();
                }

                value = null;
            }

            return value;
        }
    }

从上面我们也能看到,Spring 对于多个数据库操作的事务实现是基于 ThreadLocal 的,所以 Spring 事务操作是无法使用多线程的。

应用场景

TransactionSynchronization  可以用于一些需要在事务结束后执行清理操作或其他相关任务的场景。

应用场景举例:

举例:假设一个电商系统中存在订单支付的业务场景,当用户支付订单时,需要在事务提交后发送订单支付成功的消息通知给用户。

由于事务是和数据库连接相绑定的,如果把发送消息和数据库操作放在一个事务里面。当发送消息时间过长时会占用数据库连接,所以就要把数据库操作与发送消息到 MQ 解耦开来。

这时就可以通过 TransactionSynchronization 来实现在事务提交后发送消息通知的功能。具体示例代码如下:

@Component
public class OrderPaymentNotification implements TransactionSynchronization {

    private String orderNo;

    public OrderPaymentNotification(String orderNo) {
        this.orderNo = orderNo;
    }

    @Override
    public void beforeCommit(boolean readOnly) {
        // 在事务提交前不执行任何操作
    }

    @Override
    public void beforeCompletion() {
        // 在事务即将完成时不执行任何操作
    }

    @Override
    public void afterCommit() {
        // 在事务提交后发送订单支付成功的消息通知
        sendMessage("订单支付成功", orderNo);
    }

    @Override
    public void afterCompletion(int status) {
        // 在事务完成后不执行任何操作
    }

    private void sendMessage(String message, String orderNo) {
        // 发送消息通知的具体实现逻辑
        System.out.println(message + ": " + orderNo);
    }
}
@Transactional
    public void finishOrder(String orderNo) {
        // 修改订单成功
        updateOrderSuccess(orderNo);
        // 发送消息到 MQ
        TransactionSynchronizationManager.registerSynchronization(new OrderPaymentNotification(orderNo));
    }

这样当事务成功提交之后,就会把消息发送给 MQ,并且不会占用数据库连接资源。

@TransactionalEventListener

在 Spring Framework 4.2版本后还可以使用 @TransactionalEventListener 注解处理数据库事务提交成功后的执行操作。

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {
    TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;

    // 表明若没有事务的时候,对应的event是否需要执行,默认值为false表示,没事务就不执行了。
    boolean fallbackExecution() default false;

    @AliasFor(
        annotation = EventListener.class,
        attribute = "classes"
    )
    Class[] value() default {};

    @AliasFor(
        annotation = EventListener.class,
        attribute = "classes"
    )
    Class[] classes() default {};

    String condition() default "";
}



public enum TransactionPhase {
    // 在事务commit之前执行
    BEFORE_COMMIT,
    // 在事务commit之后执行
    AFTER_COMMIT,
    // 在事务rollback之后执行
    AFTER_ROLLBACK,
    // 在事务完成后执行(包括commit/rollback)
    AFTER_COMPLETION;

    private TransactionPhase() {
    }
}

从命名上可以直接看出,它就是个 EventListener,效果跟 TransactionSynchronization 一样,但比 TransactionSynchronization 更加优雅。它的使用方式如下:

@Data
public class Order {

    private Long orderId;
    private String orderNumber;
    private BigDecimal totalAmount;
}

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Transactional
    public void createOrder(Order order) {
        // 保存订单逻辑
        System.out.println("Creating order: " + order.getOrderNumber());
        
        orderRepository.save(order);
        
        // 发布订单创建事件
        OrderCreatedEvent orderCreatedEvent = new OrderCreatedEvent(order);
        eventPublisher.publishEvent(orderCreatedEvent);
    }
}

@Getter
@Setter
public class OrderCreatedEvent {

    private Order order;

    public OrderCreatedEvent(Order order) {
        this.order = order;
    }
}

@Component
@Slf4j
public class OrderEventListener {

    @Autowired
    private EmailService emailService;

    

    @Async
    @Order(1)
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, classes = OrderCreatedEvent.class)
    public void onOrderCreatedEvent(OrderCreatedEvent event) {
        // 处理订单创建事件,例如发送邮件通知
        log.info("Received OrderCreatedEvent for order: " + event.getOrder().getOrderNumber());
        emailService.sendOrderConfirmationEmail(event.getOrder());
    }
}
来源:Java随想录内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯