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);
Related
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();
});
}
I have the following tables with the following structure
Table A {
id <-- Primary key
someColumn
}
Table B {
id <-- Primary key
someColumn
idOfA <-- Foreign key mapping to Table A
}
Entity classes look like below
#Entity
#Table(name = "A")
public class A implements Serializable {
private static final long serialVersionUID = -78448557049178402L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.......
.......
#OneToMany(mappedBy = "a")
private List<B> bs = new ArrayList<>();
}
#Entity
#Table(name = "B")
public class B implements Serializable {
private static final long serialVersionUID = -659500557015441771L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.......
.......
#OneToOne
#JoinColumn(name = "a_id", nullable = false)
private A a;
}
Using JPA2, I want to select records from table A which do not have a reference in Table B.
The Expected native postgres query is
select * from A a
where a.id not in
(select b.idOfA from B b);
What I have so far managed to do is
public List<A> getANotInB() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// Select From Table B
CriteriaQuery<B> criteriaQueryB = criteriaBuilder
.createQuery(B.class);
Root<B> rootB = criteriaQueryB.from(B.class);
criteriaQueryB.select(rootB);
// Select records from Table A
CriteriaQuery<A> criteriaQueryA = criteriaBuilder.createQuery(A.class);
Root<A> rootA = criteriaQueryA.from(A.class);
criteriaQueryA.select(A);
// Create predicate
Predicate predicate = rootAttemptA.in(criteriaQueryB.getSelection());
criteriaQueryA.where(criteriaBuilder.not(predicate));
// Create query
TypedQuery<A> query = entityManager.createQuery(criteriaQueryA);
List<A> as= query.getResultList();
System.out.println(as);
return as;
}
I know the code above is incorrect and I have got a lot of basics wong.
Kindly help
Note: I Want to use JPA2 Criteria Query
Try this
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// Select distinct aid from B
CriteriaQuery<B> bQuery = cb.createQuery(B.class);
Root<B> bRoot = bQuery.from(B.class);
bQuery.select(bRoot.get("a").get("id")).distinct(true);
// Select * from A where aid not in ()
CriteriaQuery<A> aQuery = cb.createQuery(A.class);
Root<A> aRoot = aQuery.from(A.class);
aQuery.select(aRoot).where(cb.not(aRoot.get("id").in(bQuery)));
TypedQuery<A> query = entityManager.createQuery(aQuery);
List<A> result = query.getResultList();
Basically, you will construct part of the query and glue them together.
More information here:
JPA Criteria
I was able to get it done using subquery() as below. Posting it so that it can help others
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// select a from A a
CriteriaQuery<A> queryA = criteriaBuilder.createQuery(A.class);
Root<A> rootA = queryA.from(A.class);
queryA.select(rootA);
// Select distinct aId from B
CriteriaQuery<B> subQueryB = queryA.subquery(B.class);
Root<B> rootB = subQueryB.from(B.class);
bQuery.select(rootB.get("a")).distinct(true);
queryA.where(criteriaBuilder.not(criteriaBuilder.in(rootA.get("id").value(subQueryB))));
TypedQuery<A> query = entityManager.createQuery(aQuery);
List<A> result = query.getResultList();
Thanks #Mạnh for showing the way
I have two entities Document and Property where a document has a set of properties:
#Entity
public class Document{
#Id
private Integer id;
#OneToMany
private Set<Property> properties;
}
And
#Entity
public class Property{
#Id
private Integer id;
private String key;
private String value;
}
I want to implement the following JPQL query using Criteria API:
SELECT d FROM Document d
WHERE "value11" = ANY(SELECT p.value FROM d.properties p WHERE p.key="key11")
I tried the following:
EntityManager em = PersistenceManager.INSTANCE.getEntityManager();
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Document> query = criteriaBuilder.createQuery(Document.class);
Root<Document> fromDoc = query.from(Document.class);
Subquery<String> subQuery = query.subquery(String.class);
Root<Property> subRoot = subQuery.from(Property.class);
subQuery.select(subRoot.get(Property_.value));
SetJoin<Document, Property> join = fromDoc.join(Document_.properties, JoinType.INNER);
subQuery.select(join.<String> get("value"));
subQuery.where(criteriaBuilder.equal(join.<String> get("key"), "key11"));
query.where(criteriaBuilder.equal(criteriaBuilder.any(subQuery), "value11"));
Query q = em.createQuery(query);
List<Document> docs = q.getResultList();
PersistenceManager.INSTANCE.close();
But I got this exception:
Exception Description: The query has not been defined correctly, the
expression builder is missing. For sub and parallel queries ensure
the queries builder is always on the left. Query:
ReadAllQuery(referenceClass=Document ) at
org.eclipse.persistence.exceptions.QueryException.invalidBuilderInQuery(QueryException.java:689)
at
org.eclipse.persistence.internal.expressions.SQLSelectStatement.appendFromClauseToWriter(SQLSelectStatement.java:537)
at
org.eclipse.persistence.internal.expressions.SQLSelectStatement.printSQL(SQLSelectStatement.java:1704)
Any help would be appreciated!
I have entity Person
#Entity(name = "Person")
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person")
private Set<Phone> phones=new HashSet<Phone>();
public Person() {
}
public Person(String name) {
this.name = name;
}
Ad entity Phone :
#Entity(name = "Phone")
public class Phone {
#Id
#GeneratedValue
private Long id;
#Column(name = "`number`")
private String number;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id", nullable = false)
private Person person;
public Phone() {
}
They have one-to-many relation.
Now I want to build in jpa criteria such query:
select p.phones from person p join phone ph where p.name = :name;
So I want to extract Set<Phone> phones from Person entity where person's name is parameter.
I've written this jpa criteria query:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
CriteriaQuery<Person> where = query.where(builder.equal(root.get("name"), "Mary Dick"));
CompoundSelection<Set> projection = builder.construct(Set.class, root.get("phones"));
where.select(projection); //compile error: The method select(Selection<? extends Person>) in the type CriteriaQuery<Person> is not applicable for the arguments (CompoundSelection<Set>)
}
But it gives compile error:
The method select(Selection<? extends Person>) in the type CriteriaQuery<Person> is not applicable for the arguments (CompoundSelection<Set>)
How is it correct? Do I need metamodel classes?
CompoundSelection<Y> construct(Class<Y> result, Selection<?>... terms)
This method is useful only when the query would involve certain projections which are not entirely encapsulated by a single entity class. If that is the case, first parameter would be the custom POJO class (with suitable constructor) with fields which corresponding to the select clause of the query.
In this case, the selection is already a part of the entity class. So, you can simply choose the fields you need.
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
query.where(builder.equal(root.get("name"), "Mary Dick"));
query.select(root.get("phones"));
Above query will return a list of person. But if you are looking for just an iterable list of phones, try with a slightly different query.
select ph from phone ph join ph.person p where p.name = :name;
And its equivalent CriteriaQuery:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Phone> query = builder.createQuery(Phone.class);
Root<Phone> root = query.from(Phone.class);
Join<Phone, Person> join = root.join(root.get("person"))
query.where(builder.equal(join.get("name"), "Mary Dick"));
Let's say I have the following example entities - one is an #Embeddable, embedded inside another #Entity:
#Embeddable
public class ContactInfoEntity {
#Column
private String phone;
#Column
private String zipCode;
}
#Entity
#Table(name = "EMPLOYEE")
public class EmployeeEntity {
#Id
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "phone",
column = #Column(name = "EMPLOYEE_PHONE")),
#AttributeOverride(name = "zipCode",
column = #Column(name = "EMPLOYEE_ZIP_CODE"))
})
private ContactInfoEntity employeeContactInfo;
}
The meta-model classes generated by the openjpa-maven-plugin include only an employeeContactInfo variable, not the #AttributeOverride columns.
Now suppose I want to do this:
Select the EMPLOYEE_ID and EMPLOYEE_PHONE where the EMPLOYEE_ZIP_CODE is equal to "123456"
How do I create this as a CriteriaQuery?
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<String> qDef = cb.createQuery(String.class);
Root<EmployeeEntity> e = qDef.from(EmployeeEntity.class);
qDef.select(e.get(EmployeeEntity_.employeeId),
e.get(????))
.where(cb.equal(e.get(????), "123456"));
return entityManager.createQuery(qDef).getResultList();
An example approach may look like this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> qDef = cb.createQuery(Object[].class);
Root<EmployeeEntity> e = qDef.from(EmployeeEntity.class);
qDef.multiselect(
e.get(EmployeeEntity_.employeeId),
e.get(EmployeeEntity_.employeeContactInfo).get(ContactInfoEntity_.phone));
qDef.where(
cb.equal(
e.get(EmployeeEntity_.employeeContactInfo).get(ContactInfoEntity_.zipCode),
cb.literal("123456")));
List<Object[]> objects = em.createQuery(qDef).getResultList();
for (Object[] element : objects) {
System.out.format("%d %s", element[0], element[1]);
}
Depending on your preferences you may also want to get the results of the query as:
constructor expression
public class EmployeeEntityResult {
private int id;
private String phone;
public EmployeeEntityResult(int id, String phone) {
this.id = id;
this.phone = phone;
}
...
}
CriteriaQuery<EmployeeEntityResult> cq = cb.createQuery(EmployeeEntityResult.class);
...
List<EmployeeEntityResult> result = em.createQuery(cq).getResultList();
for (EmployeeEntityResult element : result) {
System.out.format("%d %s", element.getId(), element.getPhone());
}
tuple
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
...
cq.select(
cb.tuple(
e.get(EmployeeEntity_.employeeId)
.alias("id"),
e.get(EmployeeEntity_.employeeContactInfo).get(ContactInfoEntity_.phone)
.alias("phone")));
...
List<Tuple> tuple = em.createQuery(cq).getResultList();
for (Tuple element : tuple) {
System.out.format("%d %s", element.get("id"), element.get("phone"));
}
The JPQL query looks as follows:
SELECT e.id, e.employeeContactInfo.phone
FROM EmployeeEntity e
WHERE e.employeeContactInfo.zipCode = '123456'