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

Spring单例Bean后台线程报错:No EntityManager with actual transaction available

解决Spring单例Bean中TimerTask线程里的TransactionRequiredException异常

首先来看你遇到的错误日志:

ERROR c.r.r.c.uploader.RWDataUploader - Exception at ObjectUploaderMonitor.wait No EntityManager with actual transaction available for current thread - cannot reliably process 'merge' call javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'merge' call at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:282) at com.sun.proxy.$Proxy230.merge(Unknown Source) at com.retailwave.rwos.compartment.uploader.RWDataUploader.uploadTxnToServer(RWDataUploader.java:106) at com.retailwave.rwos.compartment.uploader.RWDataUploader.startUpload(RWDataUploader.java:82) at com.retailwave.rwos.compartment.uploader.RWDataUploader$1.run(RWDataUploader.java:57)

问题根源分析

这个异常的核心原因是Spring的@Transactional注解依赖线程上下文来管理事务,而你用java.util.Timer创建的后台线程并不在Spring的事务上下文范围内。也就是说,uploadTxnToServer方法上的@Transactional注解根本没有生效,当你调用em.merge(openDevnt)时,当前线程没有绑定任何事务,自然就抛出了这个异常。

另外还有个小问题:你原代码里的TimerTask执行一次后就调用了bgProcessTimer.cancel(),这会导致定时器直接停止,任务只跑一次,这应该不是你想要的持续上传逻辑吧?

解决方案1:改用Spring自带的任务调度框架(推荐)

Spring的@Scheduled注解是专门用来处理定时任务的,它会自动管理线程上下文,让事务注解正常生效,步骤如下:

  1. 先在你的Spring配置类上开启调度支持:
@Configuration
@EnableScheduling
public class AppConfig {
    // 其他配置内容
}
  1. 修改RWDataUploader,去掉所有Timer相关代码,改用@Scheduled:
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
@Singleton
@Component
public class RWDataUploader {
    protected org.slf4j.Logger log = LoggerFactory.getLogger(RWDataUploader.class);
    @PersistenceContext(type = PersistenceContextType.TRANSACTION)
    private EntityManager em;
    protected EntityManager getEntityManager() { return em; }

    private boolean isActive = true;
    private int errorCount = 0;
    private long sleepTime = 20000l;

    @PostConstruct
    public void init() {
        log.info("RWDataUploadClient initialized {} ", this);
    }

    @PreDestroy
    public void close() {
        stopUpload();
        log.info("RWDataUploadClient closed {} ", this);
    }

    public void stopUpload() {
        isActive = false;
    }

    // 初始延迟10秒启动,之后每3秒执行一次
    @Scheduled(initialDelay = 10000, fixedRate = 3000)
    public void startUpload() {
        if (!isActive) {
            log.info("Uploader client is stopped");
            return;
        }
        try {
            uploadTxnToServer();
            log.debug("UploadTxnToServer Done, waiting for next cycle");
        } catch (Exception ex) {
            errorCount++;
            log.error("Exception during upload process: {}", ex.getMessage(), ex);
            if (errorCount == 1) {
                isActive = false;
            }
        }
    }

    @Transactional
    private void uploadTxnToServer() {
        List<RWDataEvent> openDevnts = getPendingDataEvents();
        for (RWDataEvent openDevnt : openDevnts) {
            log.info("Uploaded DE {} ", openDevnt);
            // 这里写上传到远程服务器的逻辑
            // 上传完成后标记状态
            openDevnt.setProcessedStatusCode("UPLOADED");
            em.merge(openDevnt);
        }
    }

    private List<RWDataEvent> getPendingDataEvents() {
        Query nq = em.createNamedQuery("RWDataEvent.findByProcessedStatusCode");
        nq.setParameter("processedStatusCode", "WAIT_UPLOAD");
        return nq.getResultList();
    }
}

这样修改后,Spring会负责管理任务的线程,@Transactional注解就能正常生效,因为Spring会在调度任务时把事务上下文绑定到当前线程。而且也不用自己手动处理wait和notify的逻辑,@Scheduled已经帮我们搞定了执行间隔。

解决方案2:手动管理事务(如果必须保留Timer)

如果因为某些原因你必须使用java.util.Timer,那可以手动获取事务管理器来控制事务:

  1. 先注入Spring的事务管理器:
@Autowired
private PlatformTransactionManager transactionManager;
  1. 修改uploadTxnToServer方法,去掉@Transactional,手动开启和提交事务:
private void uploadTxnToServer() {
    // 定义事务属性,设置传播行为为REQUIRED(和@Transactional默认一致)
    DefaultTransactionDefinition transactionDef = new DefaultTransactionDefinition();
    transactionDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    
    TransactionStatus status = transactionManager.getTransaction(transactionDef);
    try {
        List<RWDataEvent> openDevnts = getPendingDataEvents();
        for (RWDataEvent openDevnt : openDevnts) {
            log.info("Uploaded DE {} ", openDevnt);
            // 上传到远程服务器的逻辑
            openDevnt.setProcessedStatusCode("UPLOADED");
            em.merge(openDevnt);
        }
        // 提交事务
        transactionManager.commit(status);
    } catch (Exception ex) {
        // 发生异常回滚事务
        transactionManager.rollback(status);
        throw ex; // 抛出异常让上层处理
    }
}

同时记得把原TimerTask里的bgProcessTimer.cancel()去掉,否则任务只执行一次就停了。

额外提示

  • java.util.Timer是JDK自带的基础定时任务工具,功能比较有限,而且异常处理、线程管理都不如Spring的调度框架灵活。如果没有特殊需求,优先用Spring的@Scheduled或者更强大的@Async结合调度来实现。
  • 你原代码里的objectUploaderMonitor锁和wait逻辑,在改用Spring调度后就不需要了,因为@Scheduled已经帮我们控制了任务的执行频率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:03:27