【问题标题】:spring - Hibernate session management with multiple transactions in a cron job?spring - 在 cron 作业中具有多个事务的休眠会话管理?
【发布时间】:2012-09-06 07:21:06
【问题描述】:

在 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();
         // ...
}

我的 spring 配置的一部分:

<!-- 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. 我怎样才能轻松地围绕这个问题跨越一个开放的会话,或者在多个事务中完成大量工作的最佳方法是什么?

【问题讨论】:

  • 我猜你的意思是protected void doSomething (Customer customer )而不是protected void do (Customer customer )
  • 您确定aspectj 模式已启用吗?因为如果它会你没有得到LazyInitializationException
  • @OleksandrBondarenko:为什么? batchJob()Propagation.NEVER,因此从它调用的方法将有自己的会话。
  • @axtavt:实际上我的意思是以下想法(来自 Spring 文档的quote):... self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
  • 肯定是aspectj激活的。这不是问题。我只是用 Propagation.NEVER 标记了一种方法,以明确没有类注释或其他东西。我配置弹簧没有问题。这更像是一个架构问题。我想知道其他人是如何做这样的事情的。

标签: spring hibernate transactions


【解决方案1】:

我不太明白你关于重现它的测试的第一个问题,据我了解,你只需要在非事务性测试中调用 cron()

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

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

从理论上讲,在这种情况下,您可以使用 extended 持久性上下文而不是 事务性 一种,但我认为不可能以这种方式配置 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);
}

【讨论】:

  • 我的第一个问题是关于这个问题,实际类有一个错误,因为它使用客户作为参数,但客户处于分离状态。但我认为这只是一个集成测试,我不能使用模拟来重现异常。
  • 您只加载 Ids 的解决方案是可以的,这就是我知道的方式。但我真的不喜欢它,因为方法 doSomething 在我的服务层中。由于适用于所有 Web 请求的 OSIV 模式,所有其他服务方法都可以使用常规对象调用。
  • 问题是所有服务方法都受益于 OpenSessionInView,但不是我的由 cron 调用的服务方法。所以我认为应该有一种方法来拥有像 OpenSessionInCron 这样的东西或像 @TransactionSynchronization 这样的注释
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-04
  • 2012-11-11
  • 2021-09-28
  • 1970-01-01
  • 2011-11-16
  • 2013-03-10
  • 2013-04-30
相关资源
最近更新 更多