how to test two conditions on each child of a OneToMany in hibernate search?

28 Views Asked by At

Given two entities Organization and Job and their indexing with Hibernate Search (I am forced to still use HS 5.12).

@Entity
@Indexed(index = "idx_organization")
public class Organization {
    // ...

    @OneToMany(mappedBy = "orga", cascade = CascadeType.ALL, orphanRemoval = true)
    @IndexEmbedded
    final private Set<Job> jobs = new HashSet<>();
}
@Entity
@Indexed(index = "idx_job")
public class Job {
    // ...

    @NotNull
    @ManyToOne
    @JoinColumn(nullable = false)
    @ContainedIn
    private Organization orga;

    @NotNull
    @Column(nullable = false)
    private String title;

    @Nullable
    @Field
    private OffsetDateTime archivedOn;

    @Nullable
    @Field
    private OffsetDateTime publishedOn;
}

I want to get all organizations that have at least one Job a with a.publishedOn != null and a.archivedOn == null (i.e. organizations that have at least one published and non-archived job)

This is what I tried, but the result does not include organizations that have 2 published jobs of which one is archived.

FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);

QueryBuilder qb = fullTextEntityManager
        .getSearchFactory()
        .buildQueryBuilder()
        .forEntity(Organization.class)
        .get()
final var q = qb.bool();
q.must(qb.keyword()
        .wildcard()
        .onField(Organization_.JOBS + "." + Job_.PUBLISHED_ON)
        .ignoreFieldBridge()
        .matching("*")
        .createQuery()
);
q.must(qb.bool().must(
            qb.keyword()
                .wildcard()
                .onField(Organization_.JOBS + "." + Job_.ARCHIVED_ON)
                .ignoreFieldBridge()
                .matching("*")
                .createQuery()
        ).not() // <-- note the "not"
        .createQuery()
);
1

There are 1 best solutions below

4
yrodiere On BEST ANSWER

I am forced to still use HS 5.12

I guess you mean 5.11, as 5.12 doesn't exist.

I want to get all organizations that have at least one Job a with a.publishedOn != null and a.archivedOn == null

This is not possible at query time in Hibernate Search 5, because Lucene flattens the document structure in the index, unless you use Lucene's nested document feature which Hibernate Search 5 doesn't support. See this section of Hibernate Search 6's documentation for more information.

The best you can do in Hibernate Search 5 is to perform the combination of the two criteria at indexing time, as shown below.

@Entity
@Indexed(index = "idx_job")
public class Job {
    // ...

    @NotNull
    @ManyToOne
    @JoinColumn(nullable = false)
    @ContainedIn
    private Organization orga;

    @NotNull
    @Column(nullable = false)
    private String title;

    @Nullable
    @Field
    private OffsetDateTime archivedOn;

    @Nullable
    @Field
    private OffsetDateTime publishedOn;

    @Field
    @javax.persistence.Transient
    public boolean isLive() {
        return publishedOn != null && archivedOn == null;
    } 
}

Reindex your data, then you can use the new field as shown below.

FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);

QueryBuilder qb = fullTextEntityManager
        .getSearchFactory()
        .buildQueryBuilder()
        .forEntity(Organization.class)
        .get()
final var q = qb.bool();
q.must(qb.keyword()
        .wildcard()
        .onField(Organization_.JOBS + ".live")
        .matching(true)
        .createQuery()
);