Hibernate Search手动索引编制引发“ org.hibernate.TransientObjectException:实例未与此会话关联”

时间:2019-07-06 14:37:23

标签: lucene lazy-loading hibernate-search full-text-indexing hibernate-onetomany

我在Spring Boot 2应用程序上使用Hibernate Search 5.11,可以进行全文研究。 此库要求将文档编入索引。

启动我的应用程序时,我尝试每五分钟手动重新索引一个索引实体(MyEntity.class)的数据(由于特定原因,由于我的服务器环境)。

我尝试为MyEntity.class的数据建立索引。

MyEntity.class具有一个属性AttachedFiles,它是一个哈希表,填充有一个@OnToMany()连接,并启用了延迟加载模式:

@OneToMany(mappedBy = "myEntity", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<AttachedFile> attachedFiles = new HashSet<>();

我对所需的索引过程进行了编码,但是当给定实体的attachFiles属性填充一个或多个项目时,“ fullTextSession.index(result); ”上会引发异常:

org.hibernate.TransientObjectException: The instance was not associated with this session

在这种情况下,调试模式在实体哈希集值上显示类似“无法加载[...]”的消息。

如果HashSet为空(不为null,仅为空),则不会引发异常。

我的索引方法:

private void indexDocumentsByEntityIds(List<Long> ids) {

final int BATCH_SIZE = 128;

Session session = entityManager.unwrap(Session.class);

FullTextSession fullTextSession = Search.getFullTextSession(session);
fullTextSession.setFlushMode(FlushMode.MANUAL);
fullTextSession.setCacheMode(CacheMode.IGNORE);

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<MyEntity> criteria = builder.createQuery(MyEntity.class);
Root<MyEntity> root = criteria.from(MyEntity.class);
criteria.select(root).where(root.get("id").in(ids));

TypedQuery<MyEntity> query = fullTextSession.createQuery(criteria);

List<MyEntity> results = query.getResultList();

int index = 0;

for (MyEntity result : results) {
    index++;
    try {
        fullTextSession.index(result); //index each element
        if (index % BATCH_SIZE == 0 || index == ids.size()) {
            fullTextSession.flushToIndexes(); //apply changes to indexes
            fullTextSession.clear(); //free memory since the queue is processed
        }
    } catch (TransientObjectException toEx) {
        LOGGER.info(toEx.getMessage());
        throw toEx;
    }
}
}

有人有主意吗?

谢谢!

2 个答案:

答案 0 :(得分:1)

这可能是由于您在循环中进行的“清除”调用所致。

本质上,您正在做的是:

  • 加载所有实体以重新索引到会话中
  • 索引一批实体
  • 从会话(fullTextSession.clear())中删除所有实体
  • 尝试索引下一批实体,即使它们不再在会话中...?

您需要做的是仅在会话清除后加载每一批实体,以确保在为它们建立索引时它们仍在会话中。

在文档中有一个使用滚动和适当的批处理大小的示例:https://docs.jboss.org/hibernate/search/5.11/reference/en-US/html_single/#search-batchindex-flushtoindexes

或者,您可以将ID列表分成128个元素的较小列表,然后对每个列表运行查询以获取相应的实体,为所有这128个实体重新索引,然后刷新并清除。

答案 1 :(得分:0)

感谢@yrodiere的解释,他们对我有很大帮助!

我选择了您的替代解决方案:

  

或者,您可以将ID列表分成128个元素的较小列表,然后对每个列表运行查询以获取相应的实体,为所有这128个实体重新索引,然后刷新并清除。

...一切正常!

很好看!

请参见下面的代码解决方案:

private List<List<Object>> splitList(List<Object> list, int subListSize) {

List<List<Object>> splittedList = new ArrayList<>();

if (!CollectionUtils.isEmpty(list)) {

    int i = 0;
    int nbItems = list.size();

    while (i < nbItems) {
        int maxLastSubListIndex = i + subListSize;
        int lastSubListIndex = (maxLastSubListIndex > nbItems) ? nbItems : maxLastSubListIndex;
        List<Object> subList = list.subList(i, lastSubListIndex);
        splittedList.add(subList);
        i = lastSubListIndex;
    }
}

return splittedList;
}


private void indexDocumentsByEntityIds(Class<Object> clazz, String entityIdPropertyName, List<Object> ids) {

Session session = entityManager.unwrap(Session.class);

List<List<Object>> splittedIdsLists = splitList(ids, 128);

for (List<Object> splittedIds : splittedIdsLists) {

    FullTextSession fullTextSession = Search.getFullTextSession(session);
    fullTextSession.setFlushMode(FlushMode.MANUAL);
    fullTextSession.setCacheMode(CacheMode.IGNORE);

    Transaction transaction = fullTextSession.beginTransaction();

    CriteriaBuilder builder = session.getCriteriaBuilder();
    CriteriaQuery<Object> criteria = builder.createQuery(clazz);
    Root<Object> root = criteria.from(clazz);
    criteria.select(root).where(root.get(entityIdPropertyName).in(splittedIds));

    TypedQuery<Object> query = fullTextSession.createQuery(criteria);

    List<Object> results = query.getResultList();

    int index = 0;

    for (Object result : results) {
        index++;
        try {
            fullTextSession.index(result); //index each element
            if (index == splittedIds.size()) {
                fullTextSession.flushToIndexes(); //apply changes to indexes
                fullTextSession.clear(); //free memory since the queue is processed
            }
        } catch (TransientObjectException toEx) {
            LOGGER.info(toEx.getMessage());
            throw toEx;
        }
    }

    transaction.commit();
}
}