【问题标题】:Hibernate no session in ItemProcessor (Spring Batch)在 ItemProcessor 中休眠没有会话(Spring Batch)
【发布时间】:2021-12-01 05:51:09
【问题描述】:

我在运行 Spring Batch 作业时尝试访问 Hibernate 实体关系作为 ItemProcessor 的一部分时遇到问题。 ItemProcessor 是基于块的步骤的一部分。据我所知,ItemProcessor 在事务中运行,因此应该能够延迟加载实体关系。

问题

作为ItemProcessor 逻辑的一部分,我收到以下异常:

org.hibernate.LazyInitializationException: could not initialize proxy [org.powo.model.registry.Organisation#1] - no Session
        at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:169)
        at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309)
        at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
        at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
        at org.powo.model.registry.Organisation$HibernateProxy$OFnEWoXa.getIdentifier(Unknown Source)
        at org.powo.model.solr.BaseSolrInputDocument.build(BaseSolrInputDocument.java:39)
        at org.powo.model.solr.TaxonSolrInputDocument.<init>(TaxonSolrInputDocument.java:67)
        at org.powo.model.Taxon.toSolrInputDocument(Taxon.java:1091)
        at org.powo.job.reindex.TaxonToSolrInputDocumentProcessor.process(TaxonToSolrInputDocumentProcessor.java:20)
        at org.powo.job.reindex.TaxonToSolrInputDocumentProcessor.process(TaxonToSolrInputDocumentProcessor.java:13)
        at org.springframework.batch.core.step.item.SimpleChunkProcessor.doProcess(SimpleChunkProcessor.java:126)
        at org.springframework.batch.core.step.item.SimpleChunkProcessor.transform(SimpleChunkProcessor.java:303)
        at org.springframework.batch.core.step.item.SimpleChunkProcessor.process(SimpleChunkProcessor.java:202)
        at org.springframework.batch.core.step.item.ChunkOrientedTasklet.execute(ChunkOrientedTasklet.java:75)
        at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:406)
        at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:330)
        at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
        at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:272)
        at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:81)
        at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:375)
        at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215)
        at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:145)
        at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:257)
        at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:200)
        at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148)
        at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:66)
        at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:67)
        at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:169)
        at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:144)
        at org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:136)
        at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:308)
        at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:141)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:834)

这是供参考的ItemProcessor(在toSolrInputDocument 逻辑中是遍历实体关系的地方):

@Component
public class TaxonToSolrInputDocumentProcessor implements ItemProcessor<Taxon, SolrInputDocument> {
  @Autowired
  private ApplicationContext context;

  @Override
  public SolrInputDocument process(Taxon item) throws Exception {
    return item.toSolrInputDocument(context);
  }
}

我使用org.springframework.batch.item.database.HibernatePagingItemReader 作为阅读器。

我尝试过的

我尝试了以下方法,但没有一个解决方案可以防止上述错误:

  • 使用 JpaPagingItemReader 而不是 HibernatePagingItemReader 但这仍然存在同样的问题
  • 使用@Autowired 获取SessionFactory,然后围绕遍历实体关系的代码执行openSession/closeSession

由于数据模型的原因,我无法在一个查询中获取所有关系,因此我需要使用有状态会话(尽管我想获取一些!)。

【问题讨论】:

  • 您能否使用调用该过程的方法更新您的样本。如果在事务中调用它并且实体没有在该持久上下文中分离,则没有理由会出现延迟初始化异常。
  • 它被 Spring Batch 调用,作为批处理作业中基于块的步骤的一部分。
  • 您如何以及从何处管理应用程序的数据库事务?你是@Transactional 注释吗?理想情况下,当 spring 启动一个数据库事务时,一个持久的上下文会与之绑定。在该方法中,您需要使您的对象附加到该持久上下文(有几种方法可以这样做)。如果您尝试获取列表项,则在退出该事务方法之前,您不应该得到惰性异常。
  • 因为这是作为 Spring Batch 基于块的步骤的一部分运行的,所以我认为 Spring 正在管理事务 - 它肯定是在从数据库读取时。文档说事务在读取开始时打开,在读取开始时关闭,在写入提交时关闭 - 因此它应该在处理器运行时打开(因此我很困惑)。我认为这可能与 XML 配置有关 - 我在一个新的 Spring Boot 项目上对此进行了测试,它按预期工作。

标签: java spring hibernate spring-batch


【解决方案1】:

AFAIK 每一步(读取、处理、写入)都会为每个处理的块创建一个单独的事务,因此如果您的 ItemReader 未初始化处理器或写入器中所需的关联,您将遇到此错误因为当实体管理器在事务提交后关闭时实体变得分离。我不知道是否可以告诉 Spring 在整个过程中保留一个实体管理器,但是如果您想依赖延迟初始化,则必须弄清楚这一点。

我建议您确保使用正确的连接提取,以避免需要延迟初始化,即select t from Taxon t join fetch t.organization o,或者更好的是,使用 DTO 方法,这样您就不必关心延迟初始化完全没有。

我认为这是 Blaze-Persistence Entity Views 的完美用例。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的用例的 DTO 模型可能如下所示:

@EntityView(Taxon.class)
public interface TaxonDto {
    @IdMapping
    Long getId();
    String getName();
    OrganizationDto getOrganization();

    @EntityView(Organization.class)
    interface OrganizationDto {
        @IdMapping
        Long getId();
        String getName();
    }
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

TaxonDto a = entityViewManager.find(entityManager, TaxonDto.class, id);

Spring Data 集成让您可以像使用 Spring Data Projections 一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<TaxonDto> findAll(Pageable pageable);

最好的部分是,它只会获取实际需要的状态!

【讨论】:

  • 感谢您的评论克里斯蒂安!我以前听说过一些关于 Blaze 的事情。不幸的是,这是一个遗留项目,因此目前迁移数据访问层对我们来说并不可行。另一个问题是我们必须加载很多的关系,其中一些是多对多的。由于数据量很大,这意味着我们必须在内存中进行分页(这不可能发生,因为它会立即超出堆空间)。不过感谢您提供的替代方案,它看起来比用 JPQL 编写所有内容要好一些!
  • 内存中不需要分页。 Blaze-Persistence 完美支持分页到 SQL 级别。此外,使用MULTISET 提取可以避免由于 *-to-many 提取导致的 JDBC 结果集爆炸。
【解决方案2】:

我现在已经解决了这个问题 - 最后更改为 JpaPagingItemReader 成功了。我不确定为什么它以前不起作用,尽管我确实发现设置任何类型的 taskExecutor 似乎确实会立即打破延迟加载。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-03
    • 2012-05-05
    • 2010-10-18
    • 1970-01-01
    • 2013-07-26
    • 2021-09-28
    相关资源
    最近更新 更多