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

mvl*_*pan 14 java hibernate jpa spring-data spring-data-jpa

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

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

代码的工作原理只是因为我在TaskScheduler类上启动了父事务.如果我删除@Transactional注释,则不再管理实体,并且对任务实体的更新不会传播到db.我发现使调度方法具有事务性是不自然的.

从我看到我有两个选择:

1.保持现在的代码.

  • 也许它只是我,这是一个正确的方法.
  • 此变体对数据库的访问次数最少.

2. @Transactional从Scheduler中删除注释,传递任务的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 有更多的数据库访问(一个额外的查询/元素)
  • 可以使用执行 @Async

你能否提出你对哪种解决这类问题的正确方法的看法,或许还有另一种我不知道的方法?

Dra*_*vic 10

如果您打算在单独的事务中处理每个任务,那么您的第一个方法实际上不起作用,因为一切都是在调度程序事务结束时提交的.

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

这种方法也非常危险,因为如果您尝试Task在嵌套事务中的实例上访问未初始化的代理,则可能会出现问题.如果Task通过向嵌套事务中加载一些其他实体实例来更改嵌套事务中的对象图,则可能会出现问题(因为当控件返回到调度程序事务时,该实例现在将被分离).

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

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