You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在同一类中结合@Transactional与synchronized实现安全事务?

问题描述

我有一个标注了@Transactional注解的synchronized方法,代码如下:

@Transactional
public synchronized void myMethod() { ... }

我的目标是确保:

  • 没有两个线程同时调用该方法(即执行同一临界区代码)
  • 前一个线程的事务提交完成后,其他线程才能进入该方法

但实际情况是,前一个线程的事务尚未提交时,另一个线程就可以进入该方法。跟踪代码发现,事务提交是在代理方法中完成的,不受synchronized保护。

我目前的解决方案是在调用类中同步调用该方法:

synchronized (this) {
    myClass.myMethod();
}

但我不喜欢这种方式,因为同步的职责转移到了调用类中。

请问在同一类中同时实现同步与事务控制,达成上述目标的正确做法是什么?

解决方案

要解决这个问题,核心是让synchronized同步块覆盖整个事务的生命周期(包括事务的开启、业务执行、提交/回滚),而不是只覆盖业务方法本身。以下是几种可行的方案:

方案1:拆分方法+使用代理对象调用事务方法

利用Spring AOP的代理机制,将同步逻辑和事务逻辑拆分到不同方法中,在同步块内通过代理对象调用事务方法,确保同步范围覆盖事务全程。

代码示例:

import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MyService {

    // 仅负责事务控制的方法
    @Transactional
    public void doBusiness() {
        // 这里写你的业务逻辑代码
    }

    // 对外暴露的方法,负责同步控制
    public void myMethod() {
        synchronized (this) {
            // 获取当前类的代理对象,确保事务生效
            ((MyService) AopContext.currentProxy()).doBusiness();
        }
    }
}

注意事项:

  • 需要开启代理暴露功能,在启动类上添加@EnableAspectJAutoProxy(exposeProxy = true)(Spring Boot项目加在@SpringBootApplication所在类即可)。
  • 同步锁的对象可按需选择:用类锁synchronized(MyService.class)实现全局同步,或用实例锁synchronized(this)实现实例级同步。

方案2:使用编程式事务

放弃声明式事务@Transactional,改用编程式事务控制,将事务的开启、提交/回滚逻辑和同步逻辑放在同一个方法里,确保同步块包裹整个事务流程。

代码示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Service
public class MyService {

    @Autowired
    private DataSourceTransactionManager transactionManager;

    public synchronized void myMethod() {
        // 定义事务属性
        DefaultTransactionDefinition txDef = new DefaultTransactionDefinition();
        // 开启事务
        TransactionStatus status = transactionManager.getTransaction(txDef);
        
        try {
            // 业务逻辑代码
            // ...
            
            // 提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(status);
            throw e; // 抛出异常,让上层处理
        }
    }
}

优点:

  • 逻辑直观,事务和同步的范围完全可控,避免代理带来的边界问题。
  • 无需依赖AOP代理,减少配置和潜在的代理失效风险。

方案3:Spring Data JPA场景下用@Lock实现悲观锁

如果业务基于Spring Data JPA,可以结合Spring Data提供的@Lock注解实现数据库层面的悲观锁,配合方法同步实现双重保障。这种方式适合数据库操作密集的场景,依赖数据库锁机制。

代码示例(参考):

import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.LockModeType;

public interface MyRepository extends CrudRepository<MyEntity, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT e FROM MyEntity e WHERE e.id = ?1")
    MyEntity findByIdForUpdate(Long id);
}

在业务方法中调用该查询方法,结合synchronized可确保事务提交后其他线程才能继续操作。

原方案失效原因

Spring的@Transactional通过动态代理实现,执行流程为:

  1. 代理类先开启事务
  2. 调用目标类的synchronized方法(进入同步块)
  3. 目标方法执行完毕,退出同步块
  4. 代理类提交事务

可见,synchronized仅保护了目标方法的执行过程,事务提交在同步块之外完成,导致前一个线程事务未提交时,下一个线程已进入目标方法,违背需求。

内容的提问来源于stack exchange,提问作者user1589188

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 22:52:53