Convert postgresql join and group by query to JPA criteria API - java

I am having trouble to converting the following postgresql query (with a join and a group by) to JPA criteria API for a Spring Boot, JPA, Hibernate application:
select u.id, u.full_name, count(*) project_applications_count from users u
join project_applications pa on pa.created_by = u.id
group by u.id, u.full_name
having count(*) >= 1 and count(*) <= 5
The tables look like this:
create table project_applications (
id serial primary key,
...
city_id integer not null references cities (id),
created_by integer not null references users (id)
);
create table users (
id serial primary key,
...
full_name varchar(100) not null
);
And the entities look like this:
#Entity
#Table(name = "project_applications")
public class ProjectApplication {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "created_by")
private User createdBy;
...
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "full_name")
private String fullName;
...
}
I tried searching online for a solution but every exemple I found was using either a join or group by, but not both.

Using #akortex's idea with projections, I think something like this should work:
public class UserSummary {
private Long id;
private String fullName;
private Long count;
public UserSummary() {
}
public UserSummary(Long id, String fullName, Long count) {
this.id = id;
this.fullName = fullName;
this.count = count;
}
... (getters and setters)
}
public List<UserSummary> getSummaries(Integer minProjectAppsCount, Integer maxProjectAppsCount) {
CriteriaBuilder cb = _entityManager.getCriteriaBuilder();
CriteriaQuery<UserSummary> query = cb.createQuery(UserSummary.class);
Root<ProjectApplication> projectApp = query.from(ProjectApplication.class);
Join<ProjectApplication, User> userJoin = projectApp.join("createdBy", JoinType.INNER);
query.multiselect(userJoin.get("id"), userJoin.get("fullName"), cb.count(projectApp))
.groupBy(userJoin.get("id"), userJoin.get("fullName"));
List<Predicate> predicates = new ArrayList<>();
if (minProjectAppsCount != null ) {
Predicate p = cb.ge(cb.count(projectApp), minProjectAppsCount);
predicates.add(p);
}
if (maxProjectAppsCount != null ) {
Predicate p = cb.le(cb.count(projectApp), maxProjectAppsCount);
predicates.add(p);
}
query.having(predicates.toArray(new Predicate[0]));
return _entityManager.createQuery(query).getResultList();
}

You could potentially look into projections in order to achieve what you want.
For example consider the following projection and repository:
#Data
#AllArgsConstructor
public class ProjectApplicationSummary {
private Long id;
private String fullName;
private Long count;
}
And:
#Repository
public interface ProjectApplicationRepository extends JpaRepository<ProjectApplication, Long> {
#Query(
"""
SELECT new com.example.springdemo.entities.ProjectApplicationSummary(u.id, u.fullName, count(pa))
FROM User u, ProjectApplication pa
GROUP BY u.id, u.fullName
"""
)
List<ProjectApplicationSummary> getSummaries();
}
You will most likely need to tweak the query a bit (which revolves experimenting with JPQL) but other than that, the basic idea is there.

I'm not sure in my solution, but it should be similar. I took an idea from here. Maybe it helps you to resolve your problem.
public static Specification<User> getUsers() {
return Specification.where((root, query, criteriaBuilder) -> {
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
Root<ProjectApplication> subRoot = subQuery.from(ProjectApplication.class);
subQuery
.select(criteriaBuilder.count(subRoot))
.where(criteriaBuilder.equal(root.get("id"), subRoot.get("createdBy").get("id")));
query
.multiselect(criteriaBuilder.construct(root.get("id"), root.get("fullName")))
.groupBy(root.get("id"), root.get("fullName"))
.having(criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(subQuery.getSelection(), 1L),
criteriaBuilder.lessThanOrEqualTo(subQuery.getSelection(), 5L)));
return query.getRestriction();
});
}

Related

JPA Criteria for MAX id GROUP BY FOREIGN KEY in a ManyToMany relationship

I'm developing an application using Spring Boot and Spring Data JPA.
The following are the relevant entities:
#Entity
public class Certification {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "certification_type_id")
private CertificationType certificationType;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "certification", cascade = CascadeType.ALL)
private Set<CertificationStatus> certificationStatuses;
}
#Entity
public class CertificationType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Enumerated(EnumType.STRING)
private Code code;
private String name;
private String description;
...
public enum Code {
...
}
}
#Entity
public class CertificationStatus {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "certification_id")
private Certification certification;
#ManyToOne
#JoinColumn(name = "certification_status_type_id")
private CertificationStatusType certificationStatusType;
private String remarks;
}
#Entity
public class CertificationStatusType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Enumerated(EnumType.STRING)
private Code code;
private String name;
private String description;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "certificationStatusType", cascade = CascadeType.ALL)
private Set<CertificationStatus> certificationStatuses;
public enum Code {
...
}
}
In my service, I have a method, CertificationService.findAll(). The idea is to retrieve certification records whose latest certification_status is based on MAX(id), GROUP BY certification_id and WHERE certification_status_type_id = ?. This method has five (5) parameters:
Integer certificationTypeId,
Integer certificationStatusTypeId,
LocalDate dateFrom,
LocalDate dateTo,
String client
The combination could be zero (0) to five (5). Thus, I chose to apply JPA Criteria. So far, I have coded the following:
#Override
public List<CertificationDto> findAll(
Integer certificationTypeId,
Integer certificationStatusTypeId,
LocalDate dateFrom,
LocalDate dateTo,
String client) {
var certifications = this.certificationRepository.findAll((root, criteriaQuery, criteriaBuilder) -> {
var predicates = new ArrayList<Predicate>();
// This is working
if (certificationTypeId != null && certificationTypeId > 0) {
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.join(Certification_.certificationType, JoinType.INNER), certificationTypeId)));
}
// To do
if (certificationStatusTypeId != null && certificationStatusTypeId > 0) {
/*
* I have no idea how to apply my SQL statement:
* SELECT * FROM certification_status WHERE id IN (SELECT MAX(id) FROM certification_status GROUP BY certification_id) and certification_status_type_id = ?;
*/
// Not sure what is the direction of the ff codes
var certificationStatuses = root.join(Certification_.certificationStatuses);
predicates.add(criteriaBuilder.and(criteriaBuilder.max(certificationStatuses.get(CertificationStatus_.id))));
// It says Cannot resolve method 'and(javax.persistence.criteria.Expression<N>)'
// Not also sure how to pass in parameter certificationStatusTypeId
}
// This is working
if (dateFrom != null) {
var offsetDateTimeFrom = OffsetDateTime.of(dateFrom, LocalTime.MIDNIGHT, ZoneOffset.ofHours(8));
var dateTimeFrom = Date.from(offsetDateTimeFrom.toInstant());
var offsetDateTimeTo = OffsetDateTime.of(Objects.requireNonNullElse(dateTo, dateFrom), LocalTime.MAX, ZoneOffset.ofHours(8));
var dateTimeTo = Date.from(offsetDateTimeTo.toInstant());
predicates.add(criteriaBuilder.and(criteriaBuilder.between(
root.get(Certification_.createdAt),
criteriaBuilder.literal(dateTimeFrom),
criteriaBuilder.literal(dateTimeTo)))
);
}
// This is working
if (client != null && !client.isBlank()) {
predicates.add(criteriaBuilder.and(criteriaBuilder.like(criteriaBuilder.lower(root.join(Certification_.client).get(Client_.name)), "%" + client.toLowerCase() + "%")));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
});
return certifications.stream()
.map(this.certificationMapper::fromEntityToDto)
.collect(Collectors.toUnmodifiableList());
}
My criteria for certificationTypeId, dateFrom, dateTo, and client are working. For the certificationStatusTypeId, I want to create criteria based on the following SQL statement:
SELECT * FROM certification_status WHERE id IN (SELECT MAX(id) FROM certification_status GROUP BY certification_id) and certification_status_type_id = ?;
This statement is working in the database. Not sure though if this is the best composition of query to select the record based on MAX(id), GROUP BY certification_id and WHERE certification_status_type_id = ?.
Appreciate any help. Thank you.
**Update
This is now my code:
if (certificationStatusTypeId != null && certificationStatusTypeId > 0) {
Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
Root<CertificationStatus> subRoot = subQuery.from(CertificationStatus.class);
subQuery.select(criteriaBuilder.max(subRoot.get(CertificationStatus_.id)))
.groupBy(subRoot.get(CertificationStatus_.certification).get(Certification_.id));
predicates.add(criteriaBuilder.and(criteriaBuilder.in(subRoot.get(CertificationStatus_.id)).value(subQuery)));
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(subRoot.get(CertificationStatus_.certificationStatusType).get(CertificationStatusType_.id), certificationStatusTypeId)));
}
But it throws the following error:
org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.id' [select generatedAlias0 from entity.Certification as generatedAlias0 where ( generatedAlias1.id in (select max(generatedAlias1.id) from entity.CertificationStatus as generatedAlias1 group by generatedAlias1.certification.id) ) and ( generatedAlias1.certificationStatusType.id=2 )]; nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.id' [select generatedAlias0 from entity.Certification as generatedAlias0 where ( generatedAlias1.id in (select max(generatedAlias1.id) from entity.CertificationStatus as generatedAlias1 group by generatedAlias1.certification.id) ) and ( generatedAlias1.certificationStatusType.id=2 )]
Looking at the generated JPQL, it seems it looks similar to my SQL statement. I just don't understand why it's Invalid path.
**Update
My apologies! My SQL statement is actually similar to the following:
select
// props of Certification
from certification c
inner join certification_type ct on ct.id = c.certification_type_id
inner join client cl on cl.certification_id = c.id
inner join certification_status cs on cs.certification_id = c.id
inner join certification_status_type cst on cst.id = cs.certification_status_type_id
where cs.id in (select max(cs2.id) form certification_status cs2 group by cs2.certification_id) and cs.certification_status_type_id = ?;
Update
This is solved. Thanks to Thorben Janssen for being patient looking into my issue.
This is the final portion of my codes:
if (certificationStatusTypeId != null && certificationStatusTypeId > 0) {
var certificationStatuses = root.join(Certification_.certificationStatuses);
Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
Root<CertificationStatus> subRoot = subQuery.from(CertificationStatus.class);
subQuery.select(criteriaBuilder.max(subRoot.get(CertificationStatus_.id)))
.groupBy(subRoot.get(CertificationStatus_.certification).get(Certification_.id));
predicates.add(criteriaBuilder.and(certificationStatuses.get(CertificationStatus_.id).in(subQuery)));
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(certificationStatuses.get(CertificationStatus_.certificationStatusType).get(CertificationStatusType_.id), certificationStatusTypeId)));
}
The following code snippet creates a CriteriaQuery that's identical to your SQL statement. Because you're using Spring Data JPA, you can ignore the first block and the last line. Spring Data JPA provides them for you.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CertificationStatus> criteriaQuery = cb.createQuery(CertificationStatus.class);
Root<CertificationStatus> root = criteriaQuery.from(CertificationStatus.class);
criteriaQuery.select(root);
Subquery<Long> sub = criteriaQuery.subquery(Long.class);
Root<CertificationStatus> subRoot = sub.from(CertificationStatus.class);
sub.select(cb.max(subRoot.get("id")));
sub.groupBy(subRoot.get("certification").get("id"));
criteriaQuery.where(root.get("id").in(sub));
em.createQuery(criteriaQuery).getResultList();
You can create a subquery by calling the subquery method on your CriteriaQuery. This returns a Subquery interface which you can use in the same way as the CriteriaQuery interface.
After you defined your Subquery, you can define the IN clause and include it in the WHERE clause of your query. This is the part that always looks a little strange. Instead of using the CriteriaBuilder to define the Expression, you get the entity attribute and call the in method on it with a reference to your Subquery.
In the final step, you use the CriteriaQuery to instantiate a TypedQuery and execute it. After you activated the logging of SQL statements, you can see that Hibernate executes the following SQL statement:
select
certificat0_.id as id1_4_,
certificat0_.certification_id as certific3_4_,
certificat0_.certification_status_type_id as certific4_4_,
certificat0_.remarks as remarks2_4_
from
CertificationStatus certificat0_
where
certificat0_.id in (
select
max(certificat1_.id)
from
CertificationStatus certificat1_
group by
certificat1_.certification_id
)
**Update
It looks like you're referencing the wrong table when defining the IN clause. Please try to replace
predicates.add(criteriaBuilder.and(criteriaBuilder.in(subRoot.get(CertificationStatus_.id)).value(subQuery)));
with
predicates.add(criteriaBuilder.and(certificationStatuses.get(CertificationStatus_.id).in(subQuery)));

how to select multiple columns in subquery(hql)

Is there a way to select multiple columns in a subquery of hql?
I want to convert the following mysql query to hql
select u.name, sub.cnt from user u
inner join (select user_id, count(user_id) cnt from user_log group by user_id order by cnt desc limit 5) sub
on u.id = sub.user_id;
But in hql, I know that can use subquery only in where clause
I'm not aware of a pure Hibernate solution. I talk about that in my Blog post.
But there is a FluentJPA solution that you may consider:
FluentQuery query = FluentJPA.SQL((User u) -> {
UserIdCount sub = subQuery((UserLog log) -> {
int count = alias(COUNT(log.getUserId()), UserIdCount::getCount);
SELECT(log.getUserId(), count);
FROM(log);
ORDER(BY(count).DESC());
LIMIT(5);
});
SELECT(u.getName(), sub.getCount());
FROM(u).JOIN(sub).ON(u.getId() == sub.getUserId());
});
return query.createQuery(em, UserNameCount.class).getSingleResult();
which produces the following SQL:
SELECT t0.name, q0.count
FROM USER t0 INNER JOIN (SELECT t1.user_id, COUNT(t1.user_id) AS count
FROM USER_LOG t1
ORDER BY COUNT(t1.user_id) DESC
LIMIT 5 ) q0 ON (t0.id = q0.user_id)
Entities I used (declared with lombok):
#Entity
#Getter
#Table(name = "USER")
class User {
private Long id;
private String name;
}
#Entity
#Getter
#Table(name = "USER_LOG")
class UserLog {
private Long userId;
}
#Tuple
#Getter
class UserIdCount {
private Long userId;
private int count;
}
#Tuple
#Data
class UserNameCount {
private int count;
private String name;
}

DTO Query with JPA CriteriaQuery and Hibernate using entity as a DTO constructor creates many selects

hibernate 5.2.10.Final
jpa 2.1
I want to map a projection query to a DTO (Data Transfer Object) with JPA Criteria Query and Hibernate. I specify a constructor that will be applied to the results of the query execution.
If the constructor is for entire entity class, I have multiple of selects instead one(it is a long running process for thousands of records). If the constructor is for a set of params of the Entity then I see only one select in the console. I can't understand where I've mistaken or is it a bug?
public class ServiceDAO {
public List<ServicesDTO> getAllServicesByFilter(ServicesFilter filter) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ServicesDTO> criteria = cb.createQuery(ServicesDTO.class);
Root<ServicesEntity> serviceEntity = criteria.from(ServicesEntity.class);
// here is only one select to get list of services
criteria.select(cb.construct(ServicesDTO.class, serviceEntity.get("active"), serviceEntity.get("providerId"), serviceEntity.get("serviceId")));
// in this case I have multiple selects
//criteria.select(cb.construct(ServicesDTO.class, serviceEntity));
if(filter != null) {
List<Predicate> pcl = new ArrayList<Predicate>();
if(filter.getActive() != null)
pcl.add(cb.equal(serviceEntity.get("active"), filter.getActive()));
if(filter.getProviderId() != null)
pcl.add(cb.equal(serviceEntity.get("providerId"), filter.getProviderId()));
if(filter.getServiceId() != null)
pcl.add(cb.equal(serviceEntity.get("serviceId"), filter.getServiceId()));
criteria.where(pcl.toArray(new Predicate[pcl.size()]));
}
return entityManager.createQuery(criteria).getResultList();
}
}
-
public class ServicesDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Boolean active;
private Integer providerId;
private Integer serviceId;
public ServicesDTO() {}
public ServicesDTO(Boolean active, String providerId, Integer serviceId) {
this.active = active;
this.providerId = Integer.parseInt(providerId);
this.serviceId = serviceId;
}
public ServicesDTO(ServicesEntity service) {
if(service != null) {
this.active = service.isActive();
this.providerId = Integer.parseInt(service.getProviderId());
this.serviceId = service.getServiceId();
}
// getters & setters
}
-
#Entity
#Table
public class ServicesEntity {
#Id
#Column(name = "id", unique = true)
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name = "serviceId", nullable = false)
private int serviceId;
#Column(nullable = false)
private String providerId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="categoryId")
private Categories categoryId;
private boolean active;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "service", cascade = CascadeType.ALL)
private List<Service_Area_Ref> areas = new ArrayList<Service_Area_Ref>();
#ManyToOne(fetch=FetchType.LAZY, optional = true)
#JoinColumn(name="parentCatId")
private Categories parentCatId;
public ServicesEntity() {}
public ServicesEntity(int serviceId) {
this.serviceId = serviceId;
}
// getters & setters
// equals & hashcode
}
Yea, so it does. There is probably not much of a use case for that. Given
#Entity
public class A {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Integer value;
public class ADto {
private Integer va;
public ADto(A a) {
this.va = a.getValue();
}
public ADto(Integer va) {
this.va = va;
}
Then
tx.begin();
A a1 = new A();
a1.setValue(1);
A a2 = new A();
a1.setValue(2);
em.persist(a1);
em.persist(a2);
tx.commit();
em.clear();
System.out.println("As usual");
em.createQuery("select new dto.ADto(a.value) from A a where a.value <= 2", ADto.class).getResultList();
System.out.println("As A");
em.createQuery("select new dto.ADto(a) from A a where a.value <= 2", ADto.class).getResultList();
gives you
create table A (id integer generated by default as identity (start with 1), value integer, primary key (id))
create table B (id integer generated by default as identity (start with 1), value integer, primary key (id))
insert into A (id, value) values (default, ?)
insert into A (id, value) values (default, ?)
As usual
select a0_.value as col_0_0_ from A a0_ where a0_.value<=2
As A
select a0_.id as col_0_0_ from A a0_ where a0_.value<=2
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
And you don't like the fact that entity A is selected each time for a new ADto instance. It's probably done that way because you could have created a DTO with multiple entities, not just A, like A, B, and C and so how would JPA/Hibernate do that conveniently in a single select statement? While it could select all the attributes and then keep track of which attributes belong to which entities and then construct them and pass them to your DTO so you can deconstruct them that seems like a lot of work for a rare thing. It's probably more efficient and better all around if you select the attributes you want and make a constructor out of whatever that is, as in the first case.
I am using Hibernate 5.3 and also encounter this behaviour. But I found that if using JPA Tuple as a DTO container and multiselect, this problem will not happen. So my final solution is use Tuple to query the result set first and then convert it to DTO manually , something likes:
CriteriaQuery<Tuple> criteria = cb.createTupleQuery();
.......
criteria.multiselect(serviceEntity);
List<ServicesDTO> result = entityManager.createQuery(criteria).getResultList().stream()
.map(t->new ServicesDTO(t.get(0,ServicesEntity.class)))
.collect(toList());

hibernate native query complex constructor mapping

Java, Spring Data JPA
I have 2 entities:
class Source {
Integer id;
String name;
}
class Item {
Integer id;
String name;
Integer sourceId;
}
I need statistic native query result like this:
select s.id source_id, s.name source_name, count(i.id) item_count
from source s
left join item i on s.id = i.source_id
group by s.id
And i want to have result in Java object MyResult:
class MyResult {
Source source;
Integer itemCount;
MyResult(Source source, Integer itemCount) {...}
}
The closest solution is using #SqlResultSetMapping like this:
#SqlResultSetMapping(
name = "MyResultMapping",
entities = {
#EntityResult(
entityClass = Source.class,
fields = {
#FieldResult(name = "id", column = "source_id"),
#FieldResult(name = "name", column = "source_name"),
}
),
...
???
}
)
OR
#SqlResultSetMapping(
name = "MyResultMapping",
classes = {
#ConstructorResult(
targetClass = MyResult.class,
columns = {
#ColumnResult(name = "???"),
???
}
)
}
)
With second variant i can use something like this:
MyResult(Integer sourceId, String sourceName, Integer itemsCount) {
this.source = new Source(sourceId, sourceName);
this.itemsCount = itemsCount;
}
but i want it to automate with #SqlResultSetMapping... (because my real objects more complex)
With Spring Data JPA it's better to use projections to achieve you need, for example:
public interface SourceWithItemCount {
Source getSource();
Integer getItemCount();
}
Then in your Source repository create HQL query method, like this:
public interface SourceRepo extends JpaRepository<Source, Integer> {
#Query("select s as source, count(i) like itemCount from Source s left join Item i on i.sourceId = s.id group by s"
List<SourceWithItemCount> getSourcesWithItemCount();
}
Important note is to use aliases for returned values (s as source etc.) it's allows Spring Data JPA to map them to projections properties.
Join on <condition> works from Hibernate version 5.1+ (if I'm not mistaken) so I recommend you to create classic one-to-many relation between your objects, like this, for example:
#Entity
class Source {
#Id private Integer id;
private String name;
#OneToMany #JoinColumn(name = "source_id") private List<Item> items;
}
#Entity
class Item {
#Id private Integer id;
private String name;
}
Then create JPQL query method supported by all versions of Hibernate (and other ORM providers):
#Query("select s as source, count(i) like itemCount from Source s left join s.items i group by s"
List<SourceWithItemCount> getSourcesWithItemCount();

JPA CriteriaBuilder with junction table

How can the sql expression below be expressed using CriteriaBuilder?
select * from Ref where prac_id = (select prac_id from loc l join staff_loc sl where sl.loc = l.id and sl.pracstaff_id = 123)
Model Classes
#Entity
public class Ref {
private Long id;
private Prac prac;
}
#Entity
public class Loc {
Long id;
#ManyToOne
Prac prac;
#ManyToMany
Set<PracStaff> pracStaff;
}
#Entity
public class Prac {
Long id;
#OneToMany
Set<Loc> locs;
}
#Entity
public class PracStaff {
Long id;
#ManyToMany
Set<Loc> locs;
}
There's a join table that maps Loc to PracStaff; it has two columns: pracstaff_id and loc_id
A Loc can belong to only one Prac.
What I'm trying to get is all Ref objects that have a PracStaff with id 123 using CriteriaBuilder.
Here's the solution I got to work though I haven't tested it thoroughly. Using
Expression<Collection<PracStaff>>
to return the collection is what I was missing
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Ref> criteriaQuery = criteriaBuilder.createQuery(Ref.class);
Root<Ref> from = criteriaQuery.from(Ref.class);
criteriaQuery.select(from);
Subquery<Prac> subquery = criteriaQuery.subquery(Prac.class);
Root<Loc> fromLoc = subquery.from(Loc.class);
Expression<Collection<PracStaff>> pracStaffInLoc = fromLoc.get("pracStaff");
subquery.where(criteriaBuilder.isMember({pracStaffObj}, pracStaffInLoc));
subquery.select(fromLoc.<Prac>get("prac"));
Path<Prac> specialist = from.get("{field in Ref class}");
Predicate p = criteriaBuilder.equal(specialist, subquery);

Categories