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()
);
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()
);