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

Posted

技术标签:

【中文标题】Hibernate Search 不索引/重新索引实体【英文标题】:Hibernate Search doesn't index/reindex entities 【发布时间】:2014-12-29 09:29:07 【问题描述】:

我正在尝试在我的项目中使用 Hibernate Search(现在使用 junit + dbunit 编写测试),但搜索查询不返回任何结果。我昨天研究了这个问题并得出结论,问题是 Hibernate Search 不能很好地与 dbunit @DatabaseSetup 一起工作(与这个未回答的问题类似的问题:link)。我将介绍更多细节,但首先是我的实体类:

@Entity
@Indexed
public class User 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "userId")
    private Long id;
    (...)
    @Column(nullable = false, unique = true)
    @Field(index = Index.YES, analyze=Analyze.YES, store=Store.NO)
    private String email;
    (...)
    @Column(nullable = false, unique = true)
    @Field(index = Index.YES, analyze=Analyze.YES, store=Store.NO)
    private String username;
    (...)

我通过我的 DAO 将它保存到 db:

@Repository
public class UserDAOImpl implements UserDAO 

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public long save(User toSave) 

        return (Long) this.sessionFactory.getCurrentSession().save(toSave);
    
(...)

这是负责运行 lucene 查询的代码:

@Override
    public List<User> searchByEmail(String email) throws InterruptedException 

        return generateHibernateSearchQueryFor("email", email).list();
    

    private org.hibernate.Query generateHibernateSearchQueryFor(String field, String searchParam) 

        FullTextSession fullTextSession = Search.getFullTextSession(sessionFactory.getCurrentSession());
        QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(User.class).get();

        org.apache.lucene.search.Query lQuery = queryBuilder.keyword().onFields(field).matching(searchParam).createQuery();
        org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery(lQuery, User.class);

        return fullTextQuery;
    

这就是 spring config 中的配置方式:

<bean id="hibernate4AnnotatedSessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="me.ksiazka.model" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.mysqlDialect</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</prop>
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <prop key="hibernate.cache.use_query_cache">true</prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>

                <prop key="hibernate.search.default.directory_provider">filesystem</prop>
                <prop key="hibernate.search.default.indexBase">src/searching_indexes</prop>
            </props>
        </property>
    </bean>

现在我是如何测试的。我用 dbunit 配置了我的测试数据集并创建了这样的测试方法:

    @Test
    @DatabaseSetup("classpath:/testsDataset.xml")
    public void searchByEmailTest() 

        User u1 = new User("Maciej", "Adamowicz", "k2", "mac@gmial.com", "MacAda");
        userDAO.save(u1);

        List<User> u = null;
        try 
            //It worked at first - as new user was saved with hibernate he got his index in hibernate search indexes folder and searching found him.
            u = searchService.searchByEmail("mac@gmail.com");
         catch (InterruptedException e) 
            e.printStackTrace();
        
        //I know there should be asserts, its just for simplification for need of moment.
        System.out.println(":: " + u.size());
        System.out.println(":: " + u.get(0).getName());
    


    List<User> u2 = null;
    try 
        //abc@gmial.com is in db - setted up by @DatabaseSetup
        u2 = searchService.searchByEmail("abc@gmail.com");
     catch (InterruptedException e) 
        e.printStackTrace();
    
    //This didnt work, rows putted into db by dbunit doesn't have indexes in my indexing folder.
    System.out.println(":: " + u2.size());
    System.out.println(":: " + u2.get(0).getName());

查看 Hibernate Search 文档后,我发现了 fullTextSession.createIndexer().startAndWait(); 方法。我使用了它,但它仍然不适用于@DatabaseSetup 中的行。无论如何,它适用于我在使用 sql“手动”测试之前放置的行,所以我认为这只是 dbunit 的问题,只是用 @Before 编写了设置:

@Before
    public void setupDatabase() 

        if(!doneBefore) 

            try 
                //It calls createIndexer().startAndWait() to make sure everything is indexed before test
                searchService.reindex();
             catch (InterruptedException e)     
                e.printStackTrace();
            

            User u1 = new User("Maciej", "Adamowicz", "k2", "mac@gmial.com", "MacAda");
            userDAO.save(u1);

            doneBefore = true;
        

    

然后运行这个测试:

    @Test
    public void searchByEmailTest() 

        List<User> u = null;
        try 
            u = searchService.searchByEmail("mac@gmail.com");
         catch (InterruptedException e) 
            e.printStackTrace();
        

        //Also asserts here, I know.
        System.out.println(":: " + u.size());
        System.out.println(":: " + u.get(0).getName());
    

虽然数据是由休眠保存的,但它不起作用。我试图找到错误并将我的代码恢复为测试通过的eariel版本(带有@DatabaseSetup但仅适用于使用我的dao保存的行),现在这个也没有通过。我很困惑,不知道为什么它不索引新对象,更不用说为什么在调用大量索引器时它不重新索引所有数据库。任何帮助将不胜感激。

编辑:

在可能的答案之后,我做了更多的测试。关于搜索有时会导致两倍或三倍行的事实,我尝试了.purgeAll() 并将索引提供程序更改为 RAM,以确保在开始测试时我的索引是干净的。基本没什么变化。如前所述,为了构建我的索引,我使用了.startAndWait()。尝试使用.index()“手动”构建它,但在尝试使用 fullTextSession 时遇到了一些嵌套事务问题。显式提交事务(或设置@Rollback(false) - 两者都尝试过)也不起作用。 我在 Hibernate Search 文档 - link 中找到的所有尝试。 如果我在搜索之前使用 DAO 保存某些内容,则索引和搜索工作正常,但在 @Before 执行相同操作然后搜索就不起作用。

【问题讨论】:

【参考方案1】:

如果我没记错的话,Hibernate Search 会在你提交事务时更新索引。

这对于普通代码来说没有问题,但在测试中这种行为可能会导致问题,因为测试的常见模式是,您在开始测试时启动事务,并在测试结束时扮演交易回来,但你从不提交。

要验证这是否是您的问题的原因,请创建一个启动显式新事务的测试,修改某些内容,然后提交该事务。然后在提交后检查您的 hiberante 搜索索引。

【讨论】:

不幸的是,明确声明事务没有帮助。更重要的是 - 搜索结果有时什么也不返回,然后在下一次测试运行时返回两倍或三倍的结果(例如,对于搜索“a@a.a”,它会找到它并返回三个对象,即使 db 中只有一行)。它还会发现奇怪的结果,例如搜索“a@a.a”会返回电子邮件字段中没有“a@a.a”的对象。我在这个错误中看不到任何模式,所以我什至不知道它们什么时候发生。此外,它似乎会在调用 startAndWait 时重新索引更多实体。【参考方案2】:

正如Hibernate Search doesn't index/reindex entities 中所述,您需要在保存数据后显式提交您的事务以进行索引。索引发生在事务后同步(至少默认情况下)。

您可以尝试使用手动索引 API 或质量索引器。我不确定为什么这对您不起作用。我也不确定@DatabaseSetup 究竟是如何工作的,以及如何与 JUnit 生命周期挂钩。

关于三重结果。您可能正在使用基于文件系统的索引(默认使用),它创建了一个基于文件的 Lucene 索引,该索引在测试运行之间不会被清理。使用 RAM 索引或确保清理基于文件的索引。

如果您分享您的 Hibernate 属性配置,这可能会有所帮助。

【讨论】:

您可以在我的原始帖子(第 4 条代码 sn-p)中看到我的 Hibernate 属性配置,并且在那里您可以看到我确实在使用基于文件系统的索引。我需要它是基于文件的,但出于测试的目的,我将它更改为 ram 并且它不起作用。有关更多详细信息,请查看我的问题的编辑。

以上是关于Hibernate Search 不索引/重新索引实体的主要内容,如果未能解决你的问题,请参考以下文章

org.hibernate.HibernateException:在 Hibernate Search 中编制索引时出错(在事务完成之前)

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

Hibernate搜索监控索引过程

Hibernate Search和复杂的关系

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

如何在 Hibernate Search/Lucene 中禁用默认评分/提升?