使用 Hibernate 5.2 以流形式查询结果

Posted

技术标签:

【中文标题】使用 Hibernate 5.2 以流形式查询结果【英文标题】:Query results as a Stream with Hibernate 5.2 【发布时间】:2017-10-11 16:18:32 【问题描述】:

从 Hibernate 5.2 开始,如果我们想要获取大量数据,我们可以使用 stream() 方法而不是 scroll()

但是,当使用 scroll()ScrollableResults 时,我们可以挂钩到检索过程并通过在处理对象后从持久上下文中逐出对象和/或每隔一段时间清除整个会话来释放内存然后。

我的问题:

    现在,如果我们使用stream() 方法,幕后会发生什么? 是否可以从持久上下文中逐出对象? 会话是否定期清除? 如何实现最佳内存消耗? 可以使用例如无状态会话? 另外,如果我们在 JPA 属性中将 hibernate.jdbc.fetch_size 设置为某个数字(例如 1000),那么这如何与可滚动结果很好地结合?

【问题讨论】:

与此主题相关的有趣文章:vladmihalcea.com/2016/11/23/… 相关 Hibernate JIRA 问题:hibernate.atlassian.net/browse/HHH-11260 【参考方案1】:

以下对我有用:

DataSourceConfig.java

@Bean
public LocalSessionFactoryBean sessionFactory() 
    // Link your data source to your session factory
    ...


@Bean("hibernateTxManager")
public HibernateTransactionManager hibernateTxManager(@Qualifier("sessionFactory") SessionFactory sessionFactory) 
    // Link your session factory to your transaction manager
    ...

MyServiceImpl.java

@Service
@Transactional(propagation = Propagation.REQUIRES_NEW, transactionManager = "hibernateTxManager", readOnly = true)
public class MyServiceImpl implements MyService 

    @Autowired
    private MyRepo myRepo;
    ...
    Stream<MyEntity> stream = myRepo.getStream();
    // Do your streaming and CLOSE the steam afterwards
    ...

MyRepoImpl.java

@Repository
@Transactional(propagation = Propagation.MANDATORY, transactionManager = "hibernateTxManager", readOnly = true)
public class MyRepoImpl implements MyRepo 

    @Autowired
    private SessionFactory sessionFactory;

    @Autowired
    private MyDataSource myDataSource;

    public Stream<MyEntity> getStream() 

        return sessionFactory.openStatelessSession(DataSourceUtils.getConnection(myDataSource))
            .createNativeQuery("my_query", MyEntity.class)
            .setReadOnly(true)
            .setFetchSize(1000)
            .stream();
    
    ...

请记住,当您进行流式传输时,您实际上只需要在对象具体化时注意内存。这确实是操作中唯一容易受到内存问题影响的部分。就我而言,我一次将流分块为 1000 个对象,使用 gson 将它们序列化并立即将它们发送到 JMS 代理。垃圾收集器完成其余的工作。

值得注意的是,Spring 的事务边界感知在最后关闭了与 dB 的连接,而无需明确告知。

【讨论】:

您愿意与我们分享您是如何测量内存占用的吗?我们还注意到您使用了本机查询,在我们的例子中这不是一个选项。说我们也不能使用无状态会话。 我用大量的性能测试和一个分析工具测量了内存占用。我最终将 6500 万条记录(获取大小为 1000,然后将每个 JMS 消息具体化为 1000 条记录)直接流式传输到 Solace。如果您需要此代码,我可以添加它。如果您不使用本机查询或无状态会话,我建议向休眠添加一个提示,告诉它可缓存 = false。有什么理由不能进行无状态会话吗?我们的代码存在于 Spring 事务边界内,并且操作方式与我们将执行的任何其他读取相同。 在我们的应用程序中,实体管理器禁用了二级缓存。另外,我们不能有无状态会话的原因是我们有动态条件查询。 只要连接在事务后关闭并且您使用的缓存最少,您应该没问题。 @wild_nothing:你能告诉我你使用的是哪个数据库吗?【参考方案2】:

Hibernate ORM 用户指南states that

在内部,stream() 的行为类似于 Query#scroll,底层结果由 ScrollableResults 支持。

您可以检查 the source code 或 org.hibernate.query.internal.AbstractProducedQuery 以确保您有责任定期清除会话或从持久上下文中逐出对象。

据我从 cmets 了解到,StatelessSession 不适合您。我认为解决您的案例的干净方法是实现您自己的 stream() 方法。它可能与原始方法非常相似,只需将 ScrollableResultsIterator 替换为您自己的方法即可在迭代期间执行您需要的操作(驱逐对象或清除会话)。

【讨论】:

因为使用 StatelessSession 会有所不同,并且用户需要定期清除会话等。我不太确定 ScrollableResultsstream() 在休眠中的附加值.我还在阅读以下文章:dzone.com/articles/bulk-fetching-hibernate,这是触发我调查此问题的文章。

以上是关于使用 Hibernate 5.2 以流形式查询结果的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate 5.2 版本 -> 很多查询方法已弃用?

hibernate框架之-查询结果集返回类型

nodejs+koa以流的形式返回数据

使用 AFNetworking 2.0 从 iOS 应用程序以流的形式上传 100 张图像

在 iOS 中以流的形式读取加密的 PDF

将数据库中查询的结果转换为json, 然后调用接口的方式返回json