Search code examples
javajpafull-text-searchhibernate-search

Performing Full Text Search on Results of JPA Query Using Hibernate Search


I have 3 Entities:

public class Parent{
    @Id
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    
    @Field(name ="name")
    private String name;

    @OneToMany(targetEntity = Child.class)
    private Set<Child> children;
  }

  public class Child{
     @Id
     @Column(name = "id", nullable = false)
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private int id;

     @ManyToOne(targetEntity = Parent.class)
     private Parent parent;

     @ManyToOne(targetEntity = GrandChild.class)
     private GrandChild grandchild;
  }

  public class GrandChild{
    @Id
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
  }

I currently perform a full text search on "name" in parent using hibernate search. I would like to allow the user to supply GrandChild.id, use normal JPA query to get all Parents associated with Grandchild, and then perform a full text search on name using Hibernate Search.

Is this possible?


Solution

  • Unless you have very specific requirements, I wouldn't recommend mixing a JPA query and a full-text search query; this will complicate things and may lead to performance bottlenecks.

    The thing is, it's absolutely possible to perform the whole query using Hibernate Search only, by adding two predicates to your full-text query: one on the name, and one on the grandchild ID.

    Step 1: make sure that you include grandchildren (and their ID) in Parent using @IndexedEmbeded:

    @Indexed // THIS IS NEW
    public class Parent{
        @Id
        @Column(name = "id", nullable = false)
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
        
        @Field(name ="name") // Removed "nullable" here, this attribute doesn't exist
        private String name;
    
        // Don't forget to add the missing "mappedBy" here
        @OneToMany(targetEntity = Child.class, mappedBy = "parent")
        @IndexedEmbedded // THIS IS NEW
        private Set<Child> children;
      }
    
      public class Child{
         @Id
         @Column(name = "id", nullable = false)
         @GeneratedValue(strategy = GenerationType.IDENTITY)
         private int id;
    
         @ManyToOne(targetEntity = Parent.class)
         @ContainedIn // THIS IS NEW
         private Parent parent;
    
    
         @IndexedEmbedded(includePaths = "id") // THIS IS NEW
         @ManyToOne(targetEntity = GrandChild.class)
         private GrandChild grandchild;
      }
    
      public class GrandChild{
        @Id
        @Field // THIS IS NEW
        @Column(name = "id", nullable = false)
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        // THIS IS NEW
        @OneToMany(targetEntity = Child.class, mappedBy = "grandchild")
        @ContainedIn
        private Set<Child> parents = new HashSet<>();
      }
    

    Step 2: review the code that updates your entities. You must make sure that whenever you create or update an association on one side (e.g. Child.grandchild) you also update the other side (e.g. GrandChild.parents).

    Step 3: reindex

    FullTextSession fullTextSession = Search.getFullTextSession( session );
    fullTextSession.createIndexer().startAndWait();
    

    Step 4: query

    // Input
    int grandChildId = ...;
    String terms = ...;
    
    FullTextSession fullTextSession = Search.getFullTextSession( session );
    QueryBuilder qb = fullTextSession.getSearchFactory()
            .buildQueryBuilder().forEntity( Parent.class ).get();
    Query luceneQuery = qb.bool()
            .must( qb.keyword().onField( "name" ).matching( terms ) )
            .must( qb.keyword().onField( "children.grandchild.id" )
                    .matching( grandChildId ) )
            .createQuery();
    org.hibernate.Query query =
            fullTextSession.createFullTextQuery( luceneQuery, Parent.class );
    List<Parent> result = query.list();