我应该将托管实体传递给需要新事务的方法吗?

Should I pass a managed entity to a method that requires a new transaction?

我的应用程序加载了应处理的实体列表。这发生在使用调度程序

的 class 中
@Component
class TaskScheduler {

    @Autowired
    private TaskRepository taskRepository;

    @Autowired
    private HandlingService handlingService;

    @Scheduled(fixedRate = 15000)
    @Transactional
    public void triggerTransactionStatusChangeHandling() {
        taskRepository.findByStatus(Status.OPEN).stream()
                               .forEach(handlingService::handle);
    }
}

在我的 HandlingService 中,使用 REQUIRES_NEW 传播级别隔离处理每个任务。

@Component
class HandlingService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void handle(Task task) {
        try {
            processTask(task); // here the actual processing would take place
            task.setStatus(Status.PROCCESED);
        } catch (RuntimeException e) {
            task.setStatus(Status.ERROR);
        }
    }
}

该代码之所以有效,是因为我在 TaskScheduler class 上启动了父事务。如果我删除 @Transactional 注释,则不再管理实体,并且不会将对任务实体的更新传播到 db.I 不认为使计划的方法具有事务性是很自然的。

据我所知,我有两个选择:

1.保持今天的代​​码。

2。去掉Scheduler的@Transactional注解,传递任务id,在HandlingService中重新加载任务实体

@Component
class HandlingService {

    @Autowired
    private TaskRepository taskRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void handle(Long taskId) {
        Task task = taskRepository.findOne(taskId);
        try {
            processTask(task); // here the actual processing would take place
            task.setStatus(Status.PROCCESED);
        } catch (RuntimeException e) {
            task.setStatus(Status.ERROR);
        }
    }
}

您能否就解决此类问题的正确方法提出您的意见,也许是我不知道的另一种方法?

如果您打算在单独的事务中处理每个任务,那么您的第一种方法实际上行不通,因为所有内容都在调度程序事务结束时提交。

原因是在嵌套事务中 Task 实例基本上是分离的实体(在嵌套事务中启动的 Sessions 不知道这些实例)。在调度程序事务结束时,Hibernate 对托管实例执行脏检查并将更改与数据库同步。

这种做法也是有很大风险的,因为如果在嵌套事务中尝试访问Task实例上未初始化的代理,可能会出现麻烦。如果您通过向嵌套事务中添加一些其他实体实例来更改嵌套事务中的 Task 对象图,可能会有麻烦(因为当控件 returns 到调度程序事务)。

另一方面,您的第二种方法是正确且直接的,有助于避免上述所有陷阱。只是,我会读取 ID 并提交事务(在处理任务时无需将其挂起)。实现它的最简单方法是从调度程序中删除 Transactional 注释并使存储库方法具有事务性(如果它还不是事务性的)。

如果(且仅当)第二种方法的性能是一个问题,正如您已经提到的,您可以使用异步处理,甚至在某种程度上并行处理。另外,您可能想看看 extended sessions(对话),也许您会发现它适合您的用例。

假设processTask(task);HandlingServiceclass中的一个方法(和handle(task)方法一样),那么去掉[=12=中的@Transactional ] 将无法工作,因为 Spring 的动态代理的自然行为。

引用自spring.io forum

When Spring loads your TestService it wrap it with a proxy. If you call a method of the TestService outside of the TestService, the proxy will be invoke instead and your transaction will be managed correctly. However if you call your transactional method in a method in the same object, you will not invoke the proxy but directly the target of the proxy and you won't execute the code wrapped around your service to manage transaction.

This is one of SO thread 关于这个话题,这里有一些关于这个的文章:

  1. http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html
  2. http://tutorials.jenkov.com/java-persistence/advanced-connection-and-transaction-demarcation-and-propagation.html
  3. http://blog.jhades.org/how-does-spring-transactional-really-work/

如果您真的不喜欢在 @Scheduled 方法中添加 @Transaction 注释,您可以从 EntityManager 获取事务并以编程方式管理事务,例如:

UserTransaction tx = entityManager.getTransaction();
try {
  processTask(task);
  task.setStatus(Status.PROCCESED);
  tx.commit(); 
} catch (Exception e) {
  tx.rollback();
}

但我怀疑你会走这条路(好吧,我不会)。最后,

Can you please offer your opinion on which is the correct way of tackling this kind of problems

在这种情况下没有正确的方法。我个人的看法是,注解(例如@Transactional)只是一个标记,你需要一个注解处理器(spring,在这种情况下)来制作@Transactional 工作。如果没有其处理器,注释将不会产生任何影响。

我会更担心,例如,为什么我 processTask(task)task.setStatus(Status.PROCESSED); 住在 processTask(task) 之外,如果它看起来做同样的事情,等等

HTH.

当前代码在嵌套事务中处理任务,但在外层事务中更新任务状态(因为Task对象由外层事务管理)。因为这些是不同的事务,所以有可能一个成功而另一个失败,从而使数据库处于不一致的状态。特别是,使用此代码,如果处理另一个任务引发异常,或者在处理所有任务之前重新启动服务器,已完成的任务将保持打开状态。

如您的示例所示,将托管实体传递给另一个事务会使哪个事务应该更新这些实体变得不明确,因此最好避免。相反,您应该传递 ID(或分离的实体),并避免不必要的事务嵌套。