从另一个线程(Runnable)调用@Transactional方法

24

有没有简单的解决方案,在新的线程中使用JPA将数据保存到数据库中?

我的基于Spring的Web应用程序允许用户管理计划任务。在运行时,他可以创建和启动预定义任务的新实例。我正在使用Spring的TaskScheduler,一切都很顺利。

但是我需要将每个触发的任务的布尔结果保存到数据库中。我该怎么做?

编辑: 我必须概括我的问题:我需要从任务中调用我的@Service类中的一个方法。因为任务结果必须在保存到数据库之前进行“处理”。

编辑2: 我有一个问题的简化版本。当从调度程序调用saveTaskResult()时,会打印出消息,但不会保存到数据库中。但是每当我从控制器调用saveTaskResult()时,记录会正确保存到数据库中。

@Service
public class DemoService {

    @Autowired
    private TaskResultDao taskResultDao;

    @Autowired
    private TaskScheduler scheduler;

    public void scheduleNewTask() {
        scheduler.scheduleWithFixedDelay(new Runnable() {

            public void run() {
                // do some action here
                saveTaskResult(new TaskResult("result"));
            }

        }, 1000L);
    }

    @Transactional
    public void saveTaskResult(TaskResult result) {
        System.out.println("saving task result");
        taskResultDao.persist(result);
    }

}

问题到底出在哪里?你启动了一个线程,调用Spring服务的方式与你没有启动线程时完全一样,一切都应该没问题。 - JB Nizet
问题在于目标业务方法使用了 @Transactional 注解。当我在 run() 方法中调用该方法时,数据未能持久化。(我已经更新了问题标题) - Ondřej Míchal
事务拦截器并不关心调用该方法的线程是由您的Servlet容器创建的线程还是由您创建的线程。它应该可以正常工作。请展示一些代码。 - JB Nizet
在原始问题中添加了示例代码 - Ondřej Míchal
你正在使用哪个Spring事务管理器? - Philippe Marschall
org.springframework.orm.jpa.JpaTransactionManager - Ondřej Míchal
3个回答

44
您的代码存在问题,因为您期望在调用saveTaskResult()时启动一个事务。但实际上这不会发生,因为Spring使用AOP来启动和停止事务。
如果您从bean工厂或依赖注入中获取一个带有@Transactional注解的Spring bean的实例,则实际上获得的是该bean周围的代理。该代理在调用实际方法之前启动事务,并在方法完成后提交或回滚事务。
在这种情况下,您调用了bean的本地方法,而没有通过事务代理。将带有@Transactional注解的saveTaskResult()方法放在另一个Spring bean中。将此其他Spring bean注入DemoService中,并从DemoService调用其他Spring bean即可解决问题。

非常感谢,这个解决方案与CompletableFuture完美契合。 - Saeed Zarinfam

7

事务保存在线程本地存储中。
如果您的其他方法正在运行带有 @Transactional 注释的线程。
默认设置为REQUIRED,这意味着如果您从不同的线程运行带有@Transacitonal注释的方法,则会有一个新事务 (因为当前线程的线程本地存储中没有保留任何事务)。


1
这实际上是非常有效的,虽然它不是一个答案,但最好作为评论。在给定的示例中,这并没有什么区别,但如果scheduleNewTask已经是@Transactional,那么即使将saveTaskResult设置为REQUIRED,它也会启动一个新事务,该事务将加入现有事务(如果存在)。 - Bart Swennenhuis
在这里,您将找到一个技巧来处理此问题,但是有时候这没有意义。例如,Oracle连接中的所有公共方法都是同步的[Oracle Docs]。这种方法将使用相同的连接。即使它起作用(我自己还没有测试过,所以不能确定),您也不会获得任何好处。请注意,检查您的连接文档。 - Aliaksei Yatsau

6

除了创建带有@Transactional方法的单独Spring Bean之外,另一个选项是通过使用TransactionTemplate手动设置事务。

final TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
   taskExecutor.execute(new Runnable() {
        @Override
        public void run() {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) {
                   dao.update(object);
                }
            });
        }
    });  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接