我的应用程序加载了应该处理的实体列表。这发生在使用调度程序
的类中@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
类上启动了父事务。如果我删除了@Transactional
注释,则不再管理实体,并且对任务实体的更新不会传播到db。我发现将调度方法设置为事务性并不自然。
从我看到我有两个选择:
1。保持现在的代码。
2。从调度程序中删除@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);
}
}
}
@Async
您能否提出您对解决此类问题的正确方法的看法,或许还有其他我不了解的方法?
答案 0 :(得分:10)
如果您打算在单独的事务中处理每个任务,那么您的第一个方法实际上不起作用,因为一切都是在调度程序事务结束时提交的。
原因在于嵌套事务Task
实例基本上是分离的实体(嵌套事务中启动的Session
不知道那些实例)。在调度程序事务结束时,Hibernate会对托管实例执行脏检查,并将更改与数据库同步。
这种方法也非常危险,因为如果您尝试在嵌套事务中的Task
实例上访问未初始化的代理,则可能会出现问题。如果通过在嵌套事务中添加一些其他实体实例来更改嵌套事务中的Task
对象图,则可能会出现问题(因为当控件返回到调度程序事务时,该实例现在将被分离)
另一方面,你的第二种方法是正确和直接的,有助于避免上述所有陷阱。只是,我会读取ID并提交事务(在处理任务时不需要暂停它)。实现它的最简单方法是从调度程序中删除Transactional
注释,并使存储库方法具有事务性(如果它不是事务性的)。
如果(并且仅当)第二种方法的性能是一个问题,正如您已经提到的那样,您可以使用异步处理,甚至可以在某种程度上并行化处理。此外,您可能需要查看extended sessions(对话),也许您会发现它适合您的用例。
答案 1 :(得分:4)
当前代码处理嵌套事务中的任务,但更新外部事务中任务的状态(因为Task对象由外部事务管理)。因为这些是不同的事务,所以有可能一个成功而另一个失败,使数据库处于不一致状态。特别是,使用此代码,如果处理另一个任务抛出异常,或者在处理完所有任务之前重新启动服务器,则已完成的任务将保持打开状态。
正如您的示例所示,将托管实体传递给另一个事务会使哪些事务更新这些实体变得模糊,因此最好避免使用。相反,您应该传递id(或分离的实体),并避免不必要的事务嵌套。
答案 2 :(得分:1)
假设processTask(task);
是HandlingService
类中的方法(与handle(task)
方法相同),那么删除@Transactional
中的HandlingService
将无效,因为Spring的动态代理的自然行为。
当Spring加载你的TestService时,它用代理包装它。如果在TestService之外调用TestService的方法,则将调用代理,并且将正确管理您的事务。但是,如果在同一对象的方法中调用事务方法,则不会调用代理,而是直接调用代理的目标,并且不会执行包含在服务中的代码来管理事务。
This is one of SO thread关于这个主题,以下是一些关于此的文章:
如果您真的不喜欢在@Transaction
方法中添加@Scheduled
注释,则可以从EntityManager获取事务并以编程方式管理事务,例如:
UserTransaction tx = entityManager.getTransaction();
try {
processTask(task);
task.setStatus(Status.PROCCESED);
tx.commit();
} catch (Exception e) {
tx.rollback();
}
但我怀疑你会采取这种方式(嗯,我不会)。最后,
您能否就解决此类问题的正确方法提出您的意见
在这种情况下,没有正确的方法。我个人认为,注释(例如,@Transactional
)只是一个标记,你需要一个注释处理器(在本例中是spring)来使@Transactional
工作。没有处理器,注释将完全没有影响。
我会更担心,例如,为什么processTask(task)
和task.setStatus(Status.PROCESSED);
生活在processTask(task)
之外,如果它看起来像是同样的事情等等。
HTH。