Search code examples
hibernatehibernate-searchspring-boot-3hibernate-search-7

Hibernate Search with Spring Boot 3


I'm using spring boot v3.1.2. I'm trying to implement the search feature using Hibernate search with Lucene. For the configuration, I followed the following documentation

I'm struggling bcz manything have been changed in the latest versions of Hibernate Search ! the searchByQuery returns an empty list even though I expect data to match the query

@RequiredArgsConstructor
public class ProductRepositoryImpl implements ProductRepository {

    private final EntityManager entityManager;

    @Override
    public List<Product> searchByQuery(String query, Pageable pageable) throws RuntimeException {

        SearchSession searchSession = Search.session( entityManager );

        SearchResult<Product> result = searchSession.search( Product.class )
                .where( f -> f.match()
                        .fields( "title", "description" )
                        .matching( query ) )
                .fetch( 20 );
        long totalHitCount = result.total().hitCount();
        List<Product> hits = result.hits();

        return hits;
    }
}

I suspect that the data hasn't been indexed yet ! any insights on what I might have overlooked ?

// Product.java

import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;

@Table(name = "products")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@Indexed
public class Product extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @FullTextField
    private String title;
    private String code;

    @FullTextField
    private String description;
    ...

Update

Still doesn't able to index correctly the data, I've created a basic example here https://github.com/smaillns/demo-hibernate-search

Get /book/search?query=prod should return items !


Solution

  • To index the pre-existing data on application bootstrap, we can add this bean

    import com.example.demo.model.Book;
    import jakarta.persistence.EntityManager;
    import org.hibernate.search.mapper.orm.Search;
    import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
    import org.hibernate.search.mapper.orm.session.SearchSession;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.event.ContextRefreshedEvent;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    
    @Component
    public class EntityIndexer  implements ApplicationListener<ContextRefreshedEvent> {
    
        @Autowired
        private final EntityManager entityManager;
    
        public EntityIndexer(EntityManager entityManager) {
            this.entityManager = entityManager;
        }
    
    
        @Override
        @Transactional
        @Async
        public void onApplicationEvent(ContextRefreshedEvent event) {
            SearchSession searchSession = Search.session(entityManager);
    
            MassIndexer indexer = searchSession.massIndexer(Book.class).threadsToLoadObjects(7);
            try {
                indexer.startAndWait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    
        }
    }
    
    

    we should then get something like that in the logs

    2024-03-03T12:50:09.583+01:00  INFO 79783 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path ''
    Hibernate: select count(b1_0.id) from book b1_0
    2024-03-03T12:50:09.781+01:00  INFO 79783 --- [ ID loading - 0] s.m.p.m.i.PojoMassIndexingLoggingMonitor : HSEARCH000027: Mass indexing is going to index 2 entities.
    Hibernate: select b1_0.id from book b1_0
    Hibernate: select b1_0.id,b1_0.author,b1_0.title from book b1_0 where b1_0.id in (?,?)
    2024-03-03T12:50:10.027+01:00  INFO 79783 --- [           main] s.m.p.m.i.PojoMassIndexingLoggingMonitor : HSEARCH000028: Mass indexing complete. Indexed 2 entities.
    2024-03-03T12:50:10.029+01:00  INFO 79783 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 3.985 seconds (process running for 4.404)
    
    

    Note

    In another project, I had to upgrade to spring boot v3.2.1 to make the solution work, otherwise you may encounter the following error

    2024-03-03T12:54:27.374+01:00 ERROR 80057 --- [         task-1] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected exception occurred invoking async method: public void com.mypackage.backend.config.hibernate.EntityIndexer.onApplicationEvent(org.springframework.context.event.ContextRefreshedEvent)
    
    java.lang.NoSuchMethodError: 'org.hibernate.SessionBuilder org.hibernate.engine.spi.SessionBuilderImplementor.tenantIdentifier(java.lang.Object)'
        at org.hibernate.search.mapper.orm.massindexing.impl.HibernateOrmMassIndexingContext$HibernateOrmMassIndexingLoadingStrategy.createEntityLoader(HibernateOrmMassIndexingContext.java:201) ~[hibernate-search-mapper-orm-7.0.0.Final.jar:7.0.0.Final]
        at org.hibernate.search.mapper.pojo.massindexing.impl.PojoMassIndexingEntityLoadingRunnable.runWithFailureHandler(PojoMassIndexingEntityLoadingRunnable.java:61) ~[hibernate-search-mapper-pojo-base-7.0.0.Final.jar:7.0.0.Final]
        at org.hibernate.search.mapper.pojo.massindexing.impl.PojoMassIndexingFailureHandledRunnable.run(PojoMassIndexingFailureHandledRunnable.java:38) ~[hibernate-search-mapper-pojo-base-7.0.0.Final.jar:7.0.0.Final]