Search code examples
spring-boothibernatehibernate-search

Hibernate Search Sync strategy with Inheritance


I have a Spring boot project with 3 entities, UserEntity which is a standalone and Person which is inherited by lawyer.

I set the automatic indexing strategy to sync, When I insert a new user into the database, the new user is picked immediately, but a new lawyer or person are indexed but the don't appear in the result until I restart the mass indexer.

UserEntity:


@Entity
@Accessors(chain = true)
@Getter
@Setter
@Indexed
@SyncAnnotation(convertor = UserConvertor.class, repoClass = UserDetailServiceImplementation.class)
public class UserEntity implements UserDetails {

    @Id
    @Basic
    @Column(name = "id", columnDefinition = "uniqueidentifier")
    @Type(type = "uuid-char")
    private UUID id;

    @Column(name = "user_name", length = 20)
    @Basic
    private String username;

    @Basic
    @Column(name = "email")
    @Email
    private String email;

    @Basic
    @Column(name = "full_name", length = 50, nullable = false, columnDefinition = "nvarchar(50)")
    @NotNull
    @FullTextField(termVector = TermVector.WITH_POSITIONS_OFFSETS)
    private String fullName;

PersonEntity:



@Entity
@Accessors(chain = true)
@Getter
@Setter
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "person_type", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue("1")
@Indexed
@SyncAnnotation(convertor = ClientConvertor.class, repoClass = PersonRepository.class)
public class PersonEntity implements Serializable {

    public PersonEntity(){
        this.personType=1;
    }
    
    @Id
    @Basic
    @Column(name = "id", columnDefinition = "uniqueidentifier")
    @Type(type = "uuid-char")
    private UUID id;
    
    @Basic
    @Column(name = "first_name", nullable = false, length = 50, columnDefinition = "nvarchar(50)")
    private String firstName;
    
    @Basic
    @Column(name = "last_name", nullable = false, length = 50, columnDefinition = "nvarchar(50)")
    private String lastName;
    
    @Basic
    @Column(name = "father_name", length = 50, columnDefinition = "nvarchar(50)")
    private String fatherName;

@Basic
@FullTextField(termVector = TermVector.YES)
@Column(name = "full_name", columnDefinition = "as concat(first_name,' ',isnull(father_name,''),' ',last_name)", insertable = false, updatable = false)
private String fullName;

@Basic
@Column(name = "person_type", insertable = false, updatable = false)

@GenericField
private Integer personType;

And a LawyerEntity that inherits PersonEntity:

@Entity
@Accessors(chain = true)
@Getter
@Setter
@DiscriminatorValue("2")
@Indexed
@SyncAnnotation(convertor = ClientConvertor.class, repoClass = LawyerRepository.class)
public class LawyerEntity extends PersonEntity {

        public LawyerEntity(){
                this.setPersonType(2);
        }

    @Basic
    @Column(name = "bar_id")
    @GenericField
    private Integer barId;

    @Basic
    @Column(name = "bar_card_number")
    private Long barCardNumber;

    @Basic
    @Column(name = "bar_regisration_date")
    private LocalDate barRegistrationDate;

    @ManyToOne(targetEntity = BarEntity.class)
    @JoinColumn(foreignKey = @ForeignKey(name = "fk_lawyer_bar"),
            name = "bar_id", referencedColumnName = "id", insertable = false, updatable = false)
    @JsonIgnore
    private BarEntity bar;
}

When using Sync hibernate search automatic indexing strategy, the UserEntity index updates and includes the newly inserted entities in the index , the TRACE output:

2022-12-22 10:16:06.112 TRACE 68869 --- [nio-8080-exec-4] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's beforeCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl@5193eb5f.
2022-12-22 10:16:06.119 TRACE 68869 --- [nio-8080-exec-4] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's afterCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl@5193eb5f. Executing indexing plan.
2022-12-22 10:16:06.119 TRACE 68869 --- [nio-8080-exec-4] o.h.s.e.b.o.spi.SingletonTask            : Scheduling task 'Lucene indexing orchestrator for index 'User' - 9'.
2022-12-22 10:16:06.120 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.SingletonTask            : Running task 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.120 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.BatchingExecutor         : Processing 1 works in executor 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.132 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.BatchingExecutor         : Processed 1 works in executor 'Lucene indexing orchestrator for index 'User' - 9'
2022-12-22 10:16:06.132 TRACE 68869 --- [rker thread - 2] o.h.s.e.b.o.spi.SingletonTask            : Completed task 'Lucene indexing orchestrator for index 'User' - 9'

However, when entering a new person or a lawyer, the index doesn't reflect the changes, not even after awhile, I need to restart the massindexer for it work, it has a similar output to the previous log, but it doesn't reflect the changes on the index until I restart the mass indexer

2022-12-22 10:14:38.086 TRACE 68869 --- [nio-8080-exec-6] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's beforeCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl@6b9d9f5e.
2022-12-22 10:14:38.089 TRACE 68869 --- [nio-8080-exec-6] i.AfterCommitIndexingPlanSynchronization : Processing Transaction's afterCompletion() phase for org.hibernate.engine.transaction.internal.TransactionImpl@6b9d9f5e. Executing indexing plan.
2022-12-22 10:14:38.091 TRACE 68869 --- [nio-8080-exec-6] o.h.s.e.b.o.spi.SingletonTask            : Scheduling task 'Lucene indexing orchestrator for index 'Person' - 8'.
2022-12-22 10:14:38.091 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.SingletonTask            : Running task 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.092 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.BatchingExecutor         : Processing 1 works in executor 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.137 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.BatchingExecutor         : Processed 1 works in executor 'Lucene indexing orchestrator for index 'Person' - 8'
2022-12-22 10:14:38.138 TRACE 68869 --- [rker thread - 3] o.h.s.e.b.o.spi.SingletonTask            : Completed task 'Lucene indexing orchestrator for index 'Person' - 8'

How can I make it detect show the change in the index without restart mass index ? I also tried calling hibernate search indexing plan but to no success

I am using Hibernate search 6.1.6.Final with lucene backend and spring boot 2.7.5

As per request: The code used to search for UserEntity (user can belong to bar):

    public List<UserEntity> findInAnyRole(String name, Integer barId, UUID[] role) {
        var session = sessionProvider.getSearchSession();
        var search = session.search(UserEntity.class);
        var res = search.where(f -> f.bool(
                b -> {
                    b.must(f.match().field("fullName").matching(name).fuzzy(2));
                    if (role != null && role.length > 0) {
                        b.must(f.bool(b2 -> {
                            for (var roleValue : role)
                                b2.should(f.match().field("roles.id").matching(roleValue));
                        }));
                    }
                    if (barId != null)
                        b.must(f.match().field("barId").matching(barId));
                }
        ));
        return res.fetchHits(10);
    }

As for PersonEntity:

    public List<PersonEntity> findSimilar(@NotNull String name, String[] ids) {
        var session = sessionProvider.getSearchSession();
        var search = session.search(PersonEntity.class);
        var q=search.where(f -> f.bool().must(f.match().field("fullName").matching(name).fuzzy(2))
                .must(f.match().field("personType").matching(1))).sort(searchSortFactory -> searchSortFactory.score().desc());
        log.info(q.toQuery().queryString());
        return q.fetchHits(10);
    }

and LawyerEntity:

    public List<LawyerEntity> findSimilar(@NotNull String name, Integer barId) {
        var session = sessionProvider.getSearchSession();
        var search = session.search(LawyerEntity.class);
        var res = search.where(f -> f.match().field("fullName").matching(name).fuzzy(2));
        if (barId != null)
            res = search.where(f -> f.bool().must(f.match().field("fullName").matching(name).fuzzy(2))
                    .must(f.match().field("barId").matching(barId)));
        var list = res.fetchHits(10);
        return list;
    }

Solution

  • I suspect your problem is here:

    @Column(name = "full_name", columnDefinition = "as concat(first_name,' ',isnull(father_name,''),' ',last_name)", insertable = false, updatable = false)
    private String fullName;
    

    As you're defining the full name on the database side, it will only be populated correctly when it's loaded from the database. The first time you create your entity, or anytime you change the first name or last name on your Java object, the fullName property in your Java object will not have the correct value, until it's loaded back from the database.

    I think that when you create your entity, fullName is null, so Hibernate Search is indexing your entities with a fullName index field set to null, which explains that your queries (with predicates on the fullName field) do not match anything.

    When you use the mass indexer, on the other hand, entities are loaded from the database and fullName is populated correctly from the database.

    As to solutions, either:

    • Always update fullName manually whenever you update firstName or lastName. That might be inconvenient.
    • OR, if you don't need to use fullName in SQL queries, do not persist fullName in the database, do not add a fullName property to your entity, and just declare a getFullName() getter annotated with @javax.persistence.Transient that does the concatenation in Java:
      @Transient
      @FullTextField(termVector = TermVector.YES)
      @IndexingDependency(derivedFrom = {
          @ObjectPath(@PropertyValue(propertyName = "firstName")),
          @ObjectPath(@PropertyValue(propertyName = "fatherName")),
          @ObjectPath(@PropertyValue(propertyName = "lastName"))
      })
      public String getFullName() {
        return firstName
            + ( fatherName == null ? "" : " " + fatherName )
            + " " + lastName;
      }
      
      See this section of the documentation for @IndexingDependency.