在spring / hibernate应用程序中,我每晚都会运行一些cronjobs。其中一个应该在多个交易中完成它的工作:
@Scheduled(cron = "...")
public void cron ( )
{
batchJob();
}
@Transactional(propagation = Propagation.NEVER)
public void batchJob ( )
{
List<Customer> customers = getCustomers();
for (Customer customer : customers
{
doSomething(customer);
}
}
@Transactional(readOnly = true)
protected List<Customer> getCustomers ( )
{
return customerRepository.getCustomers();
}
@Transactional
protected void doSomething (Customer customer )
{
// LazyInitializationException
customer.getAddress();
// ...
}
我春天的部分配置:
<!-- Transaction -->
<tx:annotation-driven mode="aspectj" />
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
这里的关键点是我不希望有一个长时间运行的事务。首先,我得到所有客户并为每个客户调用交易方法。 (我希望发布的代码足以理解这个问题)
当然我得到一个LazyInitializationException,因为当提交'getCustomer'周围的事务时,Spring正在关闭会话。
我能想到的可能的解决方案:
我可以使用OpenSessionInViewInterceptor,但这是一个Web组件
我可以使用session.merge(客户)重新附加分离的对象
这是Propagation.Nestedtransaction的案例吗?嵌套事务的语义是什么?大多数数据库没有嵌套事务(postgresql没有,但我从未使用它,它被称为两阶段提交
我可以重写方法来使用customerId并在第二次交易中再次加载客户
现在我对我的问题有两个问题:
您如何编写测试来重现上述错误?
如何轻松地围绕此开放会话或在多个交易中进行大量工作的最佳方式是什么?
答案 0 :(得分:0)
我不太明白你关于重现它的测试的第一个问题,据我所知你只需要在非事务性测试中调用cron()
。
关于可能的解决方案,我认为最好的解决方案是在初始查询中加载id(只要你得到每个Customer
的新实例进行处理并不重要。)
请注意,使用merge()
的解决方案与使用ID的解决方案相比没有任何优势,它只会增加内存使用量和网络带宽消耗。另据我记得JpaTransactionManager
不支持嵌套事务。
从理论上讲,在这种情况下,你可以使用扩展持久化上下文而不是 transactional ,但我不认为以这种方式配置Spring Data JPA是可能的。 / p>
修改强> 如果您不喜欢它的外观,可以封装低级细节以将业务逻辑与它们分离,如下所示:
interface CustomerProcessor {
public void process(Customer c);
}
public void processCustomersInBatchJob(CustomerProcessor processor) {
List<Long> ids = ...;
for (Long id: ids) {
processCustomer(id, processor);
}
}
@Transactional
protected void processCustomer(Long id, CustomerProcessor processor) {
Customer c = getCustomer(id);
processor.process(c);
}