使用 Hibernate 从 table 加载所有实体会使应用程序崩溃

Loading all entities from a table with Hibernate crashes application

我试图从我使用 Hibernate 的 MySQL table 中获取所有未删除的对象,但是由于 table 中有这么多行(不足为奇)崩溃了我的应用程序。每个类别的查询返回大约 28 万个项目。

我可以做些什么来缓解这种情况? Hibernate 中是否提供了某种可以应对这种情况的功能?或者你有什么想法可以改变我的逻辑来避免这种情况吗?

有问题的方法:

public void removeCategory(ItemCategory category)
{
    User user = userAuthentication.getLoggedInUser();
    Set<Category> deletedCategories = category.orphan();
    sessionManager.commit();

    for (Category cat : deletedCategories)
    {
        List<Item> itemsInCategory = itemDAO.getItemsInCategory(category);
        reindexer.reindex(cat, ReindexerPriority.HIGH);
        reindexer.reindex(itemsInCategory, ReindexerPriority.LOW);
    }
}

itemDAO#getItemsInCategory(类别):

public List<Items> getItemsInCategory(final ItemCategory category)
{
                                        // HQL here, not SQL
    final Query query = session.createQuery("SELECT item" + 
                                            "FROM Item item, ItemCategory c" + 
                                            "WHERE asset in elements(c.items)" + 
                                            "AND c = :category" + 
                                            "AND item.dateDeleted IS null");
    query.setEntity("category", category);
    return query.list();
}

您可以通过多种方式对大型结果集进行操作。

正如Pace所说,首先是分批操作。您可以通过在循环中执行相同的查询但指定偏移量和限制条件来轻松地做到这一点。这也通常被称为使用分页来获取更小的数据切片并在间隔中对更大的数据集进行操作。

所以第一件事就是改变你的方法,让你要求它给你一个页面切片而不是所有项目:

public List<Items> getItemsByPage(int page, int pageSize) {
  return session.createQuery( "..." )
    .setFirstResult( ( page - 1 ) * pageSize )
    .setMaxResults( pageSize )
    .getResultList();
}

下一步是在循环中使用它并根据返回的子集列表调用您的 reindexer

int page = 1;
for ( List<Items> items = getItemsByPage( page, 100 ); !items.isEmpty(); ++page ) {
  reindexer.reindex( items, ReindexerPriority.LOW );
  // make sure to clear the session to avoid out of memory with L1C
  session.clear();
}

另一种方法是重新实现上述内容并使用 ScrollableResults 对象,这将允许您执行单个查询而不是多个查询,而是逐行返回结果集。

List<Items> batch = new ArrayList<>();
final ScrollableResults results = session.createQuery( ... ).scroll();
while ( results.next() ) {
  batch.add( results.get( 0 ) );
  if ( ( batch.size() % 100 ) == 0 ) {
    reindexer.reindex( batch, ReindexerPriority.LOW );
    batch.clear();
    session.clear();
  }
}

// handle left-overs of < 100 on last batch to process.
if ( !batch.isEmpty() ) {
 reindexer.reindex( batch, ReindexerPriority.LOW );
 batch.clear();
 session.clear();
}    

在这两种情况下,注意 L1C(一级缓存)很重要。 Hibernate 维护所有已加载和附加实体的内存缓存,因此当您从数据库加载数据时,尤其是批量加载数据时,您需要注意此缓存并定期从中 evict/clear 以避免 运行 进入 OutOfMemory 个异常;因此,为什么你会看到我在某些地方使用 Session#clear