如何避免<X> JpaRepository 中的 Stream 返回类型方法使用 EclipseLink JPA 将所有数据加载到内存中?



我们在项目中使用Spring数据2.4.4+EclipseLink 2.7.0(JPA 2.2(。

最近,我们正在开发一个允许用户通过xlsx下载数据的功能,当我用大数据集测试接口时,它的内存不足(OutOfMemoryError(不足为奇。因此,我们正在考虑在JpaRepository中使用Streamtpye方法,期望EclipseLink将返回由CursoredStreamScrollableCursor实现的Stream。然而,它的行为似乎就像得到一个List

为了验证,我定义了一个方法,可以从数据库中获取所有作业订单:

@Query("select jo from JobOrder jo order by jo.oid")
Stream<JobOrder> streamAll();

并用一个交易来包装它:

@Repository
public class JobOrderTestDAO {
@Autowired
private JobOrderRepository repository;
@Transactional(readOnly = true)
public Stream<JobOrder> testGetAllByStream() {
return repository.streamAll();
}
}

最后,在测试中,我将流大小限制为10,并在控制台中打印它们的oid。如果使用Cursor作为容器,则应立即返回结果。

@Autowired
private JobOrderTestDAO testDAO;
@Test
void testGetAllByStream() {
Stream<JobOrder> joStream = testDAO.testGetAllByStream();
joStream.limit(10).forEach(System.out::println);
joStream.close();
}

然而,没有返回任何结果,只发现内存爆炸。我们检查了源代码,EclipseLink似乎没有为getResultStream()提供真正的流媒体解决方案;提供额外的能力";。

default Stream<X> getResultStream() {
return getResultList().stream();
}

现在,我们使用了一个棘手的解决方法,将JPA降级为2.1.x。因为StreamExecutor将显式调用基于Cursor的函数。

protected Object doExecute(final AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
if (!SurroundingTransactionDetectorMethodInterceptor.INSTANCE.isSurroundingTransactionActive()) {
throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION);
}
Query jpaQuery = query.createQuery(accessor);
// JPA 2.2 on the classpath
if (streamMethod != null) {
return ReflectionUtils.invokeMethod(streamMethod, jpaQuery);
}
// Fall back to legacy stream execution
PersistenceProvider persistenceProvider = PersistenceProvider.fromEntityManager(query.getEntityManager());
//Implementation here is using Cursor
CloseableIterator<Object> iter = persistenceProvider.executeQueryWithResultStream(jpaQuery);
return StreamUtils.createStreamFromIterator(iter);
}

排除与版本匹配的jar并重新包含过期的jar可能不是一个好的做法。因此,我们正在寻找一种解决方案,该解决方案可能保留JpaRepositoryJpaSpecificationExecutor,而不是直接使用ExpressionBuilderStream下面的流进行编码。

也有同样的问题,我发现从1.11.8版本开始的spring数据jpa改变了JpaQueryExecution.doExecute的实现。因此,它没有运行persistenceProvider.executeQueryWithResultStream,而是调用Query.getResultStream方法。getResultStream方法的默认实现是getResultList((.stream((。这意味着它尝试将所有数据放入内存,而不是真正的流式处理和使用可滚动光标。在当前版本3.0之前,Eclipselink不会覆盖getResultStream方法的默认行为。

这里可以使用几个选项:

  1. 不像在1.11.8之前的版本上使用spring数据那样直接使用JDBC,而不是使用spring数据
  2. 使用hibernate或任何其他完全支持JPA2.2功能的持久性提供者来代替eclipselink

相关内容

  • 没有找到相关文章

最新更新