2

在 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(customer) 重新附加分离的对象

  • 这是 Propagation.Nestedtransaction 的情况吗?嵌套事务的语义是什么?大多数数据库没有嵌套事务(postgresql有但我没用过,叫做两阶段提交

  • 我可以重写方法以使用 customerId 并在第二个事务中再次加载客户

现在我有两个关于我的问题的问题:

  1. 您如何编写测试来重现上述错误?

  2. 我怎样才能轻松地围绕这个展开一个开放的会话,或者在多个事务中完成大量工作的最佳方式是什么?

4

1 回答 1

0

我不太了解您关于重现它的测试的第一个问题,据我了解,您只需要调用cron()非事务性测试即可。

关于可能的解决方案,我认为最好的解决方案是在初始查询中加载 id(只要您获得每个新实例Customer进行处理并不重要)。

请注意,merge()具有 id 的解决方案没有任何优势,它只会增加内存使用量和网络带宽消耗。另外据我记得嵌套事务不支持JpaTransactionManager

从理论上讲,在这种情况下,您可以使用扩展持久性上下文而不是事务性上下文,但我认为不可能以这种方式配置 Spring Data JPA。

编辑: 如果您不喜欢它的外观,您可以封装低级细节以将您的业务逻辑与它们分离,如下所示:

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);
}
于 2012-09-13T09:01:59.450 回答