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

Posted

技术标签:

【中文标题】Hibernate Search 手动索引抛出“org.hibernate.TransientObjectException:实例未与此会话关联”【英文标题】:Hibernate Search manual indexing throw a "org.hibernate.TransientObjectException: The instance was not associated with this session" 【发布时间】:2019-07-06 14:37:23 【问题描述】:

我在我的 Spring Boot 2 应用程序上使用 Hibernate Search 5.11,允许进行全文研究。 这个库需要索引文档。

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

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

MyEntity.class 有一个属性 attachFiles,它是一个哈希集,填充了一个连接 @OneToMany(),启用了延迟加载模式:

@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 为空(不为空,只有空),则不抛出异常。

我的索引方法:

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;
    


有人有想法吗?

谢谢!

【问题讨论】:

【参考方案1】:

这可能是由循环中的“清除”调用引起的。

本质上,你正在做的是:

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

您需要做的是仅在会话清除后加载每批实体,以便在您为它们编制索引时确定它们仍在会话中。

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

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

【讨论】:

【参考方案2】:

感谢@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();


【讨论】:

以上是关于Hibernate Search 手动索引抛出“org.hibernate.TransientObjectException:实例未与此会话关联”的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate Search + Infinispan + S3 -- 防止字母数字文件名

Hibernate Search 不索引/重新索引实体

Lucene索引未使用Hibernate Search和Spring Data进行更新

Hibernate Search和复杂的关系

Hibernate搜索监控索引过程

Spring boot + hibernate search:entityManagerFactory依赖错误