Search code examples
spring-boothibernatejpahibernate-mappinghibernate-search

How to associate multiple entities in a many-to-many relationship for Hibernate Search


Suppose I'm building a grocery store app. I have several types, some of which are related and some which are not, e.g., Produce, Fruit, Apple, Vegetable, Asparagus, Meat, Chicken, etc.

All of these types are associated with a collection of Tags in a M:N relationship. Each entity listed above can have multiple Tags, and each Tag can be associated with many entities, of various types.

How would I best describe this relationship? Should each type hierarchy have a corresponding Tag hierarchy? Using one common Tag type works with plain vanilla JPA/Hibernate, but when introducing Hibernate Search, it wants both sides of the relationship described (e.g., using @ManyToMany(mappedBy = ...)). How could I accomplish this?

====================== EXAMPLE ========================

Fruit:

@Data
@Entity
@Indexed
public class Fruit {

  @Id
  @GeneratedValue
  private Long id;

  @FullTextSearch
  private String name;

  @ManyToMany
  @IndexedEmbedded
  private Collection<Tag> tags;

}

Chicken:

@Data
@Entity 
@Indexed
public class Chicken {
  
  @Id
  @GeneratedValue
  private Long id;

  @FullTextSearch
  private String brand;

  @ManyToMany
  @IndexedEmbedded
  private Collection<Tag> tags;

}

Tag:

@Data
@Entity
@Indexed
public class Tag {
  
  @Id
  @GeneratedValue
  private Long id;

  @FullTextSearch
  private String name;

}

Solution

  • Hibernate Search needs to have an inverse mapping on the Tag side to be able to automatically reindex any of Produce, Fruit, Apple, Vegetable, Asparagus, Meat, Chicken etc. whenever a corresponding Tag is changed and it would affect the index.

    To do so you'd need to modify your Tag entity to something like:

    @Data
    @Entity
    @Indexed
    public class Tag {
      
      @Id
      @GeneratedValue
      private Long id;
    
      @FullTextSearch
      private String name;
    
      @ManyToMany(mappedBy = "tags")
      private List<Fruit> fruits;
    
      @ManyToMany(mappedBy = "tags")
      private List<Chicken> chickens;
    
      // and so on ...
    }
    

    If your tags do not change their names after they've been saved, which would mean that you wouldn't have any changes to tags that would affect fruit/chicken/.../apple indexes you can mark your tag collections with @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW). This will result in fruit/chicken/.../apple indexes being not updated whenever a tag assigned to any of the entities in those indexes is changed.

    @Data
    @Entity
    @Indexed
    public class Fruit {
    
      @Id
      @GeneratedValue
      private Long id;
    
      @FullTextSearch
      private String name;
    
      @ManyToMany
      @IndexedEmbedded
      @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)
      private Collection<Tag> tags;
    
    }
    

    In that case you do not need to modify your Tag entity, which doesn't need to model the inverse side of associations:

    @Data
    @Entity
    @Indexed
    public class Tag {
      
      @Id
      @GeneratedValue
      private Long id;
    
      @FullTextSearch
      private String name;
    
      // No inverse side of associations!
    
    }
    

    Please refer to this section in the documentation for a more detailed explanation on shallow indexing.