Query ElementCollection of Enum by using JPA Criteria API - java

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

Related

OneToMany return just specific rows

I have class with attribute
#Entity
public class Energy() {
#Id
private long id;
private Date date;
#OneToMany(fetch = FetchType.LAZY)
private List<Value> values;
}
and Value class have
private String obis;
private long value;
What is the fastest way to return just elements of list values where obis contains specific value?
Thanks!
It depends on the implementation you choose, for example with Criteria Api:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Energy> cq = cb.createQuery(Energy.class);
Root<Energy> root = cq.from(Energy.class);
Join<Energy,Value> join = root.join(Energy_.values);
//If you dont use metamodel change by root.join("values")
cq.select(root);
cq.where(cb.equal(join.get(Value_.value),VALUE));
//If you dont use metamodel change by join.get("value")
List<Energy> result = entityManager.createQuery(cq).getResultList();

How to use JPA CriteriaBuilder selectCase() so that it can have Predicate as result?

I needed to implement a criteria query which has if...then...else scenario.
I found javax.persistence.criteria.CriteriaBuilder.selectCase() is suitable for this purpose.It returns an Expression as result.
To run criteria query I needed to use where() which accepts an array of Predicates. This is because I have multiple predicates to include in the query.
Now, since selectCase() is returning Expression, I am not able to integrate it with existing list of Predicates.
Type mismatch: cannot convert from Expression<Object> to Predicate
How do I use selectCase() so that I can have Predicate as result?
Or any other better way of doing this?
Example:
To illustrate the problem, I have following implementation to "Get all users of particular age AND (from a particular country and city) else from India, by default"
if...then...else scenario
If from particular country
if from particular city
else
if from "India"
// Query implementation
#Override
public List<User> findUsersByAgeCountryAndCity(int age, String country, String city) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(age(user, age));
predicates.add(country(user, country, city));
cq.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(cq).getResultList();
}
private Predicate country(Root<User> user, String country, String city) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
return cb.selectCase() //
.when(cb.equal(user.get("address").get("country"), country),
cb.equal(user.get("address").get("city"), city))
.otherwise(cb.equal(user.get("address").get("country"), "India"));
}
private Predicate age(Root<User> entity, int age) {
return entityManager.getCriteriaBuilder().equal(entity.get("age"), age);
}
// User.java
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private LocalDate creationDate;
private LocalDate lastLoginDate;
private boolean active;
private int age;
#Column(unique = true, nullable = false)
private String email;
private Integer status;
#Embedded
private Address address;
#OneToMany
List<Possession> possessionList;
...
// Address.java
#Embeddable
public class Address {
private String city;
private String country;
...
If I read your question correctly, you want the following logic:
IF is_user_from_country
RETURN is_user_from_city
ELSE
RETURN is_user_from_india
Making it into a query is tricky, because predicates in SQL do not have an intrinsic boolean value you can return. In SQL terms, it will look something like:
CASE
WHEN user.country = :country THEN
CASE WHEN user.city = :city THEN 1 ELSE 0 END
WHEN user.country = 'India' THEN 1
ELSE 0
END
In Criteria API (note that I haven't tested it, there might be syntax errors):
cb.selectCase() //
.when(cb.equal(user.get("address").get("country"), country),
cb.selectCase()
.when(cb.equal(user.get("address").get("city"), city), cb.literal(true))
.otherwise(cb.literal(false))
))
.when(cb.equal(user.get("address").get("country"), "India"), cb.literal(true))
.otherwise(cb.literal(false));
I'm not entirely sure Criteria API supports nested CASE statements, though. If not, you can try making the logic more straightforward:
SELECT CASE
WHEN user.country = :country AND user.city = :city THEN TRUE
WHEN user.country = :country AND user.city <> :city THEN FALSE
WHEN user.country = 'India' THEN TRUE
ELSE FALSE
END

JPA Criteria API join

Help me plz with one moment. I read about 10 articles already, but don't understand join moment. I have 2 tables:
public class News implements Serializable {
#Id
#GeneratedValue (generator = "increment")
#GenericGenerator (name = "increment", strategy = "increment")
private int id;
#Column
private String name;
#Column
private Date created;
#Column
private String data;
#ManyToOne (cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn (name = "category_id")
private NewsCategoryDict category;
// getters, setters
}
and
public class NewsCategoryDict implements Serializable {
#Id
#GeneratedValue (generator = "increment")
#GenericGenerator (name = "increment", strategy = "increment")
private int id;
#Column
private String name;
#OneToMany (mappedBy = "category", cascade = CascadeType.ALL)
private List<News> news = new ArrayList<>();
}
I want a query works like
SELECT * FROM news, categorynews WHERE news.category_id = categorynews.id;
And then get the result in jsp with
<div id="list_news">
<c:forEach items="${news}" var="news">
<h5>${news.id} : ${news.name} - ${news.created} ; ${news.data} (${news.category.name})</h5>
</c:forEach>
</div>
And I just can't understand this JOIN with Criteria API. Can you help me ?
Try to use this snippet, but get a error
public List<News> getAll() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<News> cq = cb.createQuery(News.class);
Root<News> rootFromNews = cq.from(News.class);
Join<NewsCategoryDict, News> join = rootFromNews.join("category");
cq.select(join);
return em.createQuery(cq).getResultList();
}
PropertyNotFoundException: Property 'created' not found on type ru.r1k0.spring.model.NewsCategoryDict
Assuming you want to return an instance of News associated to an instance of NewsCategoryDict, your criteria query should look as follows:
public List<News> getAll() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<News> cq = cb.createQuery(News.class);
Root<News> rootFromNews = cq.from(News.class);
Join<News, NewsCategoryDict> join = rootFromNews.join("category"); // #1
cq.select(rootFromNews); // #2
return em.createQuery(cq).getResultList();
}
The modified lines are marked with #1 and #2 comments.
The query should return all News which have a matching NewsCategoryDict; but News records which are not associated to aNewsCategoryDict record will not be returned.
Your error has nothing to do with the join! What is actually happening is that in the JSP fragment you are trying to access ${news.created} which does not exist in the NewsCategoryDict. I believe the error is in the JSP fragment, not in the Criteria query.
The way I understand it is that you want to list the News object, but in the query you are selecting the NewsCategoryDict and this is why at the end you end up with missing attribute because the NewsCategoryDict does not contain ${news.created}

Spring-MVC, Hibernate : Creating DTO objects from Domain objects

I am working on a Spring-MVC application in which I am trying to search for List of GroupNotes in database. The mapping in my project is GroupCanvas has one-to-many mapping with GroupSection and GroupSection has one-to-many mapping with GroupNotes. Because of these mappings, I was getting LazyInitializationException. As suggested on SO, I should be converting the objects to a DTO objects for transfer. I checked on net, but couldnt find a suitable way to translate those.
I have just created a new List to avoid the error, but one field is still giving me an error. I would appreciate if anyone tells me either how to fix this error or convert the objects to a DTO objects so they can be transferred.
Controller code :
#RequestMapping(value = "/findgroupnotes/{days}/{canvasid}")
public #ResponseBody List<GroupNotes> findGroupNotesByDays(#PathVariable("days")int days, #PathVariable("canvasid")int canvasid){
List<GroupNotes> groupNotesList = this.groupNotesService.findGroupNotesByDays(days,canvasid);
List<GroupNotes> toSendList = new ArrayList<>();
for(GroupNotes groupNotes : groupNotesList){
GroupNotes toSendNotes = new GroupNotes();
toSendNotes.setMnotecolor(groupNotes.getMnotecolor());
toSendNotes.setNoteCreationTime(groupNotes.getNoteCreationTime());
toSendNotes.setMnotetag(groupNotes.getMnotetag());
toSendNotes.setMnotetext(groupNotes.getMnotetext());
toSendNotes.setAttachCount(groupNotes.getAttachCount());
toSendNotes.setNoteDate(groupNotes.getNoteDate());
toSendList.add(toSendNotes);
}
return toSendList;
}
GroupNotesDAOImpl :
#Override
public List<GroupNotes> searchNotesByDays(int days, int mcanvasid) {
Session session = this.sessionFactory.getCurrentSession();
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -days);
long daysAgo = cal.getTimeInMillis();
Timestamp nowMinusDaysAsTimestamp = new Timestamp(daysAgo);
GroupCanvas groupCanvas = (GroupCanvas) session.get(GroupCanvas.class,mcanvasid);
Query query = session.createQuery("from GroupSection as n where n.currentcanvas.mcanvasid=:mcanvasid");
query.setParameter("mcanvasid", mcanvasid);
List<GroupSection> sectionList = query.list();
List<GroupNotes> notesList = new ArrayList<GroupNotes>();
for (GroupSection e : sectionList) {
System.out.println("Section name is "+e.getMsectionname());
GroupSection groupSection = (GroupSection) session.get(GroupSection.class,e.getMsectionid());
Query query1 = session.createQuery("from GroupNotes as gn where gn.ownednotes.msectionid=:msectionid and gn.noteCreationTime >:limit");
query1.setParameter("limit", nowMinusDaysAsTimestamp);
query1.setParameter("msectionid",e.getMsectionid());
notesList.addAll(query1.list());
}
// I am getting the data below, but I get JSON errors.
for(GroupNotes groupNotes : notesList){
System.out.println("Group notes found are "+groupNotes.getMnotetext());
}
return notesList;
}
GroupCanvas model :
#Entity
#Table(name = "membercanvas")
public class GroupCanvas{
#OneToMany(mappedBy = "currentcanvas",fetch=FetchType.LAZY, cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupSection> ownedsection = new HashSet<>();
#JsonIgnore
public Set<GroupSection> getOwnedsection() {
return this.ownedsection;
}
public void setOwnedsection(Set<GroupSection> ownedsection) {
this.ownedsection = ownedsection;
}
}
GroupSection model :
#Entity
#Table(name = "membersection")
public class GroupSection{
#OneToMany(mappedBy = "ownednotes", fetch = FetchType.EAGER,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupNotes> sectionsnotes = new HashSet<>();
public Set<GroupNotes> getSectionsnotes(){
return this.sectionsnotes;
}
public void setSectionsnotes(Set<GroupNotes> sectionsnotes){
this.sectionsnotes=sectionsnotes;
}
}
GroupNotes model :
#Entity
#Table(name="groupnotes")
public class GroupNotes{
#Id
#Column(name="mnoteid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "mnote_gen")
#SequenceGenerator(name = "mnote_gen",sequenceName = "mnote_seq")
#org.hibernate.annotations.Index(name = "mnoticesidindex")
private int mnoticesid;
#Column(name = "mnotetext")
private String mnotetext;
#Column(name = "mnoteheadline")
private String mnotetag;
#Column(name = "mnotecolor")
private String mnotecolor;
#Column(name = "mnoteorder")
private double mnoteorder;
#Column(name = "attachmentcount")
private int attachCount;
#Column(name = "notedate")
private String noteDate;
#Column(name = "uploader")
private String uploader;
#Column(name = "activeedit")
private boolean activeEdit;
#Column(name = "notedisabled")
private boolean noteDisabled;
#Column(name = "noteinactive")
private boolean noteInActive;
#Column(name = "notecreatoremail")
private String noteCreatorEmail;
#Column(name = "prefix")
private String prefix;
#Column(name = "timestamp")
private Timestamp noteCreationTime;
#Transient
private boolean notRead;
#Transient
private String tempNote;
#Transient
private String canvasUrl;
#ManyToOne
#JoinColumn(name = "msectionid")
#JsonIgnore
private GroupSection ownednotes;
#JsonIgnore
public GroupSection getOwnednotes(){return this.ownednotes;}
public void setOwnednotes(GroupSection ownednotes){this.ownednotes=ownednotes;}
#JsonIgnore
public int getOwnedSectionId(){
return this.ownednotes.getMsectionid();
}
#OneToMany(mappedBy = "mnotedata",fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupAttachments> mattachments = new HashSet<>();
public Set<GroupAttachments> getMattachments() {
return this.mattachments;
}
public void setMattachments(Set<GroupAttachments> mattachments) {
this.mattachments = mattachments;
}
#OneToMany(mappedBy = "mhistory",fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<GroupNoteHistory> groupNoteHistorySet = new HashSet<>();
public Set<GroupNoteHistory> getGroupNoteHistorySet(){
return this.groupNoteHistorySet;
}
public void setGroupNoteHistorySet(Set<GroupNoteHistory> groupNoteHistorySet){
this.groupNoteHistorySet = groupNoteHistorySet;
}
#OneToMany(mappedBy = "unreadNotes",fetch = FetchType.LAZY,cascade = CascadeType.REMOVE)
#JsonIgnore
private Set<UnreadNotes> unreadNotesSet = new HashSet<>();
public Set<UnreadNotes> getUnreadNotesSet(){
return this.unreadNotesSet;
}
public void setUnreadNotesSet(Set<UnreadNotes> unreadNotesSet){
this.unreadNotesSet = unreadNotesSet;
}
//getters and setters ignored
}
Error log :
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.journaldev.spring.model.GroupNotes["ownedSectionId"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->com.journaldev.spring.model.GroupNotes["ownedSectionId"])
Kindly let me know what to do, as I am stuck on that error since some time.
What I think that happens is Jackson tries to serialize all fields in the hierarchy based on getter methods. In some situation NullPointerException is thrown in the following method:
#JsonIgnore
public int getOwnedSectionId(){
return this.ownednotes.getMsectionid();
}
replace it with the following method:
#JsonIgnore
public int getOwnedSectionId(){
if(this.ownednotes != null)
return this.ownednotes.getMsectionid();
return 1;
}
I don't have an explanation why jackson tries to serialize it when is market with #JsonIgnore but you can give a try with my proposal
I would appreciate if anyone tells me either how to fix this error or convert the objects to a DTO objects so they can be transferred.
We use DozerMapper at work for this purpose.
Instead of doing that mapping manually you might want to take a look at Blaze-Persistence Entity Views which can be used to efficiently implement the DTO pattern with JPA. Here a quick code sample how your problem could be solved
First you define your DTO as entity view
#EntityView(GroupNotes.class)
public interface GroupNoteView {
#IdMapping("mnoticesid") int getId();
String getMnotecolor();
String getMnotetag();
String getMnotetext();
String getNoteDate();
Timestamp getNoteCreationTime();
int getAttachCount();
}
Next you adapt your DAO to make use of it
#Override
public List<GroupNoteView> searchNotesByDays(int days, int mcanvasid) {
EntityManager entityManager = // get the entity manager from somewhere
CriteriaBuilderFactory cbf = // factory for query building from Blaze-Persistence
EntityViewManager evm = // factory for applying entity views on query builders
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_YEAR, -days);
long daysAgo = cal.getTimeInMillis();
Timestamp nowMinusDaysAsTimestamp = new Timestamp(daysAgo);
CriteriaBuilder<GroupNotes> cb = cbf.create(entityManager, GroupNotes.class, "note")
.where("noteCreationTime").gt(nowMinusDaysAsTimestamp)
.where("ownednotes.ownedcanvas.mcanvasid").eq(mcanvasid);
return evm.applySetting(EntityViewSetting.create(GroupNoteView.class), cb)
.getResultList();
}
And finally the calling code
#RequestMapping(value = "/findgroupnotes/{days}/{canvasid}")
public #ResponseBody List<GroupNoteView> findGroupNotesByDays(#PathVariable("days")int days, #PathVariable("canvasid")int canvasid){
return this.groupNotesService.findGroupNotesByDays(days, canvasid);
}

Simple where condition for JPA CriteriaQuery

So this is my first attempt to use JPA and a CriteriaQuery.
I have the following (simplified) entities:
#Entity
#Table(name = "hours")
#XmlRootElement
public class Hours implements Serializable
{
#EmbeddedId
protected HoursPK hoursPK;
#Column(name = "total_hours")
private Integer totalHours;
#JoinColumn(name = "trainer_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Trainer trainer;
public Hours()
{
}
... getter and setter for the attributes
}
#Embeddable
public class HoursPK implements Serializable
{
#Basic(optional = false)
#Column(name = "date_held", nullable = false)
#Temporal(TemporalType.DATE)
private Date dateHeld;
#Basic(optional = false)
#Column(name = "trainer_id", nullable = false, length = 20)
private String trainerId;
#Column(name = "total_hours")
private Integer totalHours;
public HoursPK()
{
}
... getter and setter ...
}
#Entity
#Table(name = "trainer")
public class Trainer implements Serializable
{
#Id
#Basic(optional = false)
#Column(name = "id", nullable = false, length = 20)
private String id;
#Basic(optional = false)
#Column(name = "firstname", nullable = false, length = 200)
private String firstname;
#Basic(optional = false)
#Column(name = "lastname", nullable = false, length = 200)
private String lastname;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "trainer", fetch = FetchType.LAZY)
private List<Hours> hoursList;
... more attributes, getters and setters
#XmlTransient
public List<Hours> getHoursList() {
return hoursList;
}
public void setHoursList(List<Hours> hoursList) {
this.hoursList = hoursList;
}
}
Essentially a Trainer holds trainings and the hours spent in the trainings are stored in the Hours entity. The PK for the hours table is (trainer_id, date_held) as each trainer only holds one training per day.
I am trying to create a CriteriaQuery to fetch all hours of a trainer for a specific month. This is my attempt:
EntityManagerFactory emf = ...
EntityManager em = emf.createEntityManager();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Hours> c = builder.createQuery(Hours.class);
Root<Hours> root = c.from(Hours.class);
Calendar cal = Calendar.getInstance();
cal.set(2014, 0, 1);
Expression<Date> from = builder.literal(cal.getTime());
cal.set(2014, 1, 1);
Expression<Date> to = builder.literal(cal.getTime());
Predicate who = builder.equal(root.get(Hours_.trainer), "foobar"); // it fails here
Predicate gt = builder.greaterThanOrEqualTo(root.get(Hours_.hoursPK).get(HoursPK_.dateHeld), from);
Predicate lt = builder.lessThan(root.get(Hours_.hoursPK).get(HoursPK_.dateHeld), to);
c.where(gt,lt,who);
c.orderBy(builder.asc( root.get(Hours_.hoursPK).get(HoursPK_.dateHeld) ));
TypedQuery<Hours> q = em.createQuery(c);
List<Hours> resultList = q.getResultList();
I'm using Hibernate 4.3.1 as the JPA provider and the above code fails with the exception:
Exception in thread "main" java.lang.IllegalArgumentException: Parameter value [foobar] did not match expected type [persistence.Trainer (n/a)]
at org.hibernate.jpa.spi.BaseQueryImpl.validateBinding(BaseQueryImpl.java:885)
Apart from the fact that this seems awfully complicated for a query that even a SQL newbie could write in a few minutes, I have no clue, how I can supply the correct value for the trainer_id column in the hours table in the above query.
I also tried:
Predicate who = builder.equal(root.get("trainer_id"), "foobar");
But that fails with the exception:
java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [trainer_id] on this ManagedType [persistence.Hours]
It works, when I obtain an actual entity instance that maps to the "foobar" id:
CriteriaQuery<Trainer> cq = builder.createQuery(Trainer.class);
Root<Trainer> trainerRoot = cq.from(Trainer.class);
cq.where(builder.equal(trainerRoot.get(Trainer_.id), "foobar"));
TypedQuery<Trainer> trainerQuery = em.createQuery(cq);
Trainer foobarTrainer = trainerQuery.getSingleResult();
....
Predicate who = builder.equal(root.get(Hours_.trainer), foobarTrainer);
But that seems a pretty stupid (and slow) way to do it.
I'm sure I'm missing something really obvious here, but I can't find it.
First of all, JPA queries always use class and field names. Never column names. So trying to use trainer_id won't work.
builder.equal(root.get(Hours_.trainer), "foobar");
You're trying to compare the trainer field of the Hours entity with the String "foobar". trainer is of type Trainer. A Trainer can't be equal to a String. Its ID, it firstName, or its lastName, all of type String, can be compared to a String. SO you probably want
builder.equal(root.get(Hours_.trainer).get(Trainer_.id), "foobar");
That said, as you noticed, the Criteria API is extremely complex and leads to unreadable, hard to maintain code. It's useful when you have to dynamically compose a query from several optional criteria (hence the name), but for static queries, you should definitely go with JPQL, which is even easier and shorter than SQL:
select h from Hours h
where h.trainer.id = :trainerId
and h.hoursPK.dateHeld >= :from
and h.hoursPK.dateHeld < :to
order by h.hoursPK.dateHeld
I would strongly advise against using composite keys, especially when one of its components is a functional data (dateHeld) that could have to change. Use numeric, single-column, autogenerated primary keys, and everything will be much simpler, and more efficient.

Categories