我在Java中设置了一组调用我的服务类的批处理/ cron作业。我也在使用Hibernate和Spring。
最初批处理层始终创建外部事务,然后批处理作业将调用服务以从具有相同会话的DB获取对象列表,然后调用服务以分别处理每个对象。我的服务层设置了一个tx-advice,可以回滚任何throwable。因此,如果在第5个对象上发生异常,则处理的前4个对象也会回滚,因为它们都是同一事务的一部分。
所以我认为在批处理层中创建的外部事务是不必要的。我删除了它,现在我调用服务来获取对象列表。然后调用另一个服务来分别处理每个对象,如果其中一个对象失败,其他对象仍然会持续存在,因为它为每个服务调用创建了一个新的事务/会话。但我现在遇到的问题是在获取对象列表之后,当我将每个对象传递给要处理的服务时,如果我尝试获取其中一个属性,我会得到一个延迟的初始化错误,因为会话用于加载该对象(从列表中)已关闭。
我想到的一些选项只是在批处理作业中获取ID列表并将每个id传递给服务,服务将检索该一个会话中的整个对象并对其进行处理。另一个是为该对象的属性设置延迟加载为false,但即使有时不需要嵌套属性,这也会每次加载所有内容。
我总是可以回到原来每个批处理作业周围的外部事务的方式,然后在每次调用服务之前在批处理作业中创建另一个事务来处理每个单独的对象......
这样的事情的最佳做法是什么?
答案 0 :(得分:2)
我会说你列出了除OpenSessionInView之外的所有可能选项。这将使您的会话在事务中保持活动状态,但很难正确实现。如此困难,它被many视为AntiPattern。
但是,由于您没有实现Web界面而且您没有处理高度线程化的环境,我会说这是可行的方法。这不像是你将实体传递给视图。您最大的担心是在迭代集合时对数据库进行N + 1调用,但由于这是一个cron作业,因此与代码清洁度相比,性能可能不是主要问题。如果您真的很担心,请确保通过致电DAO获得所有收藏品,他们可以选择*。
此外,在您在同一事务中执行所有操作之前,您实际上是在视图中进行Open Session。在Spring中,会话基于每个事务打开,因此保持事务长时间打开实际上与保持会话打开很长一段时间相同。在你的情况下,唯一真正的区别在于你可以定期提交,而不用担心延迟的初始化错误。
<强> 修改 强>
所有这一切,在View中设置一个Open Session需要一些时间,所以除非你在同一个事务中做任何事情都有任何特殊问题,你可能会考虑回到那个。
另外,我刚刚注意到您提到在批处理层中打开一个事务,然后在服务层中打开“迷你事务”。这最重要的不是一个好主意。 Spring的注释驱动事务将依赖于会话中任何当前打开的事务。这意味着如果当前打开的事务是读写的,那么应该是只读的事务将突然变为读写。此外,在最外层事务完成之前,会话不会被刷新,所以用@Transactional
标记服务层是没有意义的。将@Transactional
置于多个层上只会带来虚假的安全感。
我实际上是blogged about this issue。