如何在同一类中结合@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通过动态代理实现,执行流程为:
- 代理类先开启事务
- 调用目标类的
synchronized方法(进入同步块) - 目标方法执行完毕,退出同步块
- 代理类提交事务
可见,synchronized仅保护了目标方法的执行过程,事务提交在同步块之外完成,导致前一个线程事务未提交时,下一个线程已进入目标方法,违背需求。
内容的提问来源于stack exchange,提问作者user1589188

