Using an #Embeddable entity in a JPA CriteriaQuery - java

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'

Related

Convert postgresql join and group by query to JPA criteria API

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

querydsl subquery on #ElementCollection

I have a problem with QueryDsl and an #ElementCollection
Say I have a root class
#Entity
public class Root {
#Id
private String id = UUID.randomUUID().toString();
#ElementCollection
#CollectionTable(name = "VAL", joinColumns = #JoinColumn(name = "ROOT_ID"))
#OrderBy("prop")
public SortedSet<Val> vals = new TreeSet<>();
And a Values
#Access(AccessType.FIELD)
#Embeddable
public class Val implements Comparable<Val>{
public String prop;
public LocalDateTime timestamp;
#Override
public int compareTo(Val o) {
return prop.compareTo(o.prop);
}
}
The sql query I want to perfom is like:
select * FROM VAL v, ROOT r
WHERE
r.ID = v.ROOT_ID
and v.TIMESTAMP in (select max(va.TIMESTAMP) from VAL va GROUP BY va.ROOT_ID)
and v.PROP = 'value'
The Problem when I try to translate this to querydsl is that QVal is not an 'EntityPathBase' but a 'BeanPath'. And it can't be used in a from.
The querydsl would be something like
QRoot root = QRoot.root;
JPQLQuery<Root> value = JPAExpressions.select(root)
.from(root)
.innerJoin(root.vals, QVal.val)
.where(QVal.val.timestamp.in(
JPAExpressions.select(QVal.val.timestamp.max())
.from(QVal.val) <---- does not work !
.groupBy(QVal.val))
.and(QVal.val.prop.eq("value"))
);
repository.findAll(root.in(value));

#OneToMany which is in fact OneToOne / #Formula with Parameters

I have a Table
Products ( ProductId, Name ) and
ProductPrices (ProductId, Market, Price)
ProductPrices has a compositeKey (ProductId, Market). For a given Market, a Product has 0..1 Prices in that Market.
First approach #Formula
The Market is known at runtime, and can possibly be changed per request.
In an first attempt to model the ProductEntity I took an #Formula annotation, like so:
#Entity
#Table(...)
public class Product {
#Id
private int ProductId;
private String name;
#Formula("(SELECT TOP 1 Price FROM ProductPrices p WHERE p.ProductId = ProductId AND p.Market='Berlin')")
private double price;
}
But obviously, the market is then hard-compiled as annotations need to be static final Strings. [ so no #Formula("..." + getCurMarket() ) ].
Second approach, #OneToMany
Take a separate entity class for the prices, and reference them in the product entity as:
#OneToMany(mappedBy = "product")
private List<Price> price;
In a getPrice(), I could always return the first entry (there will never be more...) or nothing if the list is empty.
I then want to create a Predicate/Specification to use from within the ProductService. Example:
public static Specification<Product> marketEquals(final String market) {
return new Specification<Product>() {
#Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
CriteriaQuery<String> q = cb.createQuery(String.class);
Root<Price> price = q.from(Price.class);
return price.get("Market").in("Berlin");
}
};
}
However, that only results in a (and I tried writing "market", "Market", ...)
org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.market' [select generatedAlias0 from ...backend.entities.Product as generatedAlias0 where generatedAlias1.market in (:param0)]
Third approach, Hibernate/JPA Filter
This time, I write in the product entity
#OneToMany(mappedBy = "product")
#Filters( {
#Filter(name="marketFilter", condition="Market = :market")
} )
private List<Price> price;
Again, I want to fill this filter in the ProductService, but I cannot gelt hold of the CurrentSession. I tried the Spring-way, adding an #Autowired private SessionFactory sessionFactory; and configuring it through
Filter filter = sessionFactory.getCurrentSession().enableFilter("marketFilter");
filter.setParameter("market", "Berlin" );
but I cannot get hold of the right context, as org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.hibernate.SessionFactory
Who could advise on how to model the database schema as entities, or could point working solutions to approach 2 and 3 ? Thanks!
Second approach is actually correct.Try it changing your entities and creteria query.
Products table:
#Entity
#Table(...)
public class Product {
#Id
private int ProductId;
private String name;
#OneToMany(mappedBy = "products")
private List<ProductPrices> ProductPrices;
}
ProductPrices table:
#Entity
#Table(...)
public class ProductPrices {
#Id
private int ProductPriceId;
private String market;
private double price;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id") //foreign key reference
private Product products
}
ProductService:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Product> qry = cb.createQuery(Product.class);
Root<Product> root = qry.from(Product.class);
Join<Product, ProductPrices> price = root.join("ProductPrices");
List<Predicate> conditions = new ArrayList<>();
conditions.add(cb.equal(price.get("products"), "Berlin"));
TypedQuery<Product> typedQuery = em.createQuery(qry
.select(root)
.where(conditions.toArray(new Predicate[] {}))
.orderBy(cb.asc(root.get("Berlin")))
.distinct(true)
);
return typedQuery;

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

Query ElementCollection of Enum by using JPA Criteria API

I'm working of a web application for a car dealer. I have a Car class with a field which contain a set of security enums.
public class Car {
#Id
#GeneratedValue
private Long id;
#NotNull(message = "{year}")
#Min(value = 1950)
#Max(value = 2020)
#Column(nullable = false)
private int year;
#NotNull()
#Column(nullable = false)
private String make;
#NotNull()
#Column(nullable = false)
private String model;
#NotNull()
#Min(value = 0)
#Max(value = 1000000)
#Column(nullable = false)
private int kilometres;
#Column(nullable = false)
private int price;
#NotNull()
#Enumerated(EnumType.STRING)
private Gearbox gearbox;
#ElementCollection(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
#CollectionTable(name="SECURITY")
#Column(name="TYPE")
private Set<Security> securityList = new HashSet<Security>();
#NotNull()
#Column(nullable = false)
private String description;
#OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Picture> pictureList = new ArrayList<Picture>();
// Getters and setters + help methods..
The Security enum is like:
public enum Security {
ABS("abs"),
AIRBAG("airbag"),
ANTISPIN("antispin"),
CENTRAL_LOCKING("centralLocking"),
REMOTE_ALARM("remoteAlarm"),
FOUR_WHEEL("fourWheel"),
PARKING_ASSISTANCE("parkingAssistance"),
SERVICE_MANUAL("serviceManual"),
STABILITY_CONTROL("stabilityControl"),
XENON_LIGHT("xenonLight");
private String label;
private Security(String label) {
}
public String getLabel() {
return label;
}
}
In the web application, I will create a search page, where the users is able to define required Securitiy parts and a manufacturer pattern (make field in Car class) . For instance, a user might search for Cars which have a make pattern according to "Volkswagen" and Security with at least ABS and REMOTE_ALARM.
My problem is that I am not sure how to create the query using the criteria API. I guess it should start like:
public List<Car> searchCars(String makePattern, Set<Security> requiredSecuirtySet) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> cq = cb.createQuery(Car.class);
Root<Car> _car = cq.from(Car.class);
// Give me some help here please =)
return em.createQuery(cq).getResultList();
}
Can you please help me? I also have a meta model over the Car class.
Best regards and thanks in advance!
You can use collections as parameters so maybe this will work:
TypedQuery<Car> q = em.createQuery("select c from Car c where c.make = :make and c.securityList in :secutiryList", Car.class);
q.setParameter("make", makePattern);
q.setParameter("securityList", requiredSecuirtySet);
return q.getResultList();
I haven't tested this so I'm not sure it will work. It is based on this question. I also haven't worked with the criteria API so I didn't know how to 'translate' it.
Here's a shot at the query with the criteria API:
public List<Car> searchCars(String makePattern,
Set<Security> requiredSecuirtySet)
{
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Car> query = builder.createQuery(Car.class);
Root<Car> car = query.from(Car.class);
query.select(car).where(
builder.equal(car.get("make"), makePattern),
car.get("securityList").in(requiredSecuirtySet));
return em.createQuery(query).getResultList();
}
Thanks siebz0r!
I was modifying your code a little bit since your code returns all Cars that has 1 or more security (and not all), i.e. returns all cars which has a securityList that contain at least a subset of the securityList.
Here is my code:
public List<Car> searchCars(String makePattern, Set<Security> requiredSecuirtySet) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> cq = cb.createQuery(Car.class);
Root<Car> car = cq.from(Car.class);
Predicate criteria = cb.conjunction();
for (Security security : carQueryData.getSecurityCriteria()) {
criteria = cb.and(criteria, car.get(Car_.securityList).in(security) );
}
// Add more predicates, for instance:
// for (Equipment equipment : carQueryData.getEquipmentsCriteria()) {
// criteria = cb.and(criteria, car.get(Car_.equipmentList).in(equipment) );
// }
Predicate makePredicate = cb.equal(car.get(Car_.make), makePattern);
cq.select(car).where(makePredicate, criteria);
return em.createQuery(cq).getResultList();
}
Best regards

Categories