Hibernate CreateCriteria for dependent class - java

I am working on an application where I have to display data/information about the vehicle details. I have 2 tables make and model with make_id being the foreign key in model table. I have 2 entity classes Make and Model as shown below:
#Entity
#Table(name = "make")
#PrimaryKeyJoinColumn(name="make_id")
public class Make {
#Id
#Column(name = "make_id")
private String makeId;
#Column(name = "make_name")
private String makeName;
#ManyToOne
#JoinColumn(name = "mfg_unit_id")
private MfgUnit mfgUnit;
// Getter and Setters
}
#Entity
#Table(name = "model")
public class Model {
#Id
#Column(name = "model_id")
private String modelId;
#Column(name = "model_creation_date")
private Date modelCreationDate;
#Column(name = "make_id")
private long makeId;
#ManyToOne
#JoinColumn(name = "make_id")
private Make make;
// Getter and Setters
}
I am able to retrieve all the Makes, but my requirement is to only retrieve the Makes for which the model_creation_date is between today and last 30 days. Can anyone help me with how to build the Hibernate criteria for this?

there can be another solution, but to achieve this you need to modify your Make class as following introducing a new relationship (#OneToMany) with Model class:
#Entity
#Table(name = "make")
#PrimaryKeyJoinColumn(name="make_id")
public class Make {
#Id
#Column(name = "make_id")
private String makeId;
#Column(name = "make_name")
private String makeName;
// ** introduced new relationship **
#OneToMany(mappedBy="make", fetch = FetchType.LAZY)
private List<Model> models;
#ManyToOne
#JoinColumn(name = "mfg_unit_id")
private MfgUnit mfgUnit;
// Getter and Setters
}
And keep the 'Model' class as it is:
#Entity
#Table(name = "model")
public class Model {
#Id
#Column(name = "model_id")
private String modelId;
// change the datatype as Date or similar Datatype
#Column(name = "model_creation_date")
private Date modelCreationDate;
#Column(name = "make_id")
private long makeId;
#ManyToOne
#JoinColumn(name = "make_id")
private Make make;
// Getter and Setters
}
Once you have the relationship with 'Model' from 'Make' you can execute the following code to get your result:
Calendar cal = Calendar.getInstance();
// today
Date toDate=cal.getTime();
cal.add( Calendar.DATE, -30 );
//date before 30 days
Date fromDate=cal.getTime();
Criteria criteria = session.createCriteria(Make.class , "m")
.createAlias("models", "s")
.add(Restrictions.between("s.modelCreationDate", fromDate, toDate))
// if the above condition dosen't give you exact result you may try following(2)
// try gt or lt ... as needed
//.add(Restrictions.ge("publicationDate", fromDate))
//.add(Restrictions.le("publicationDate", fromDate))
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
List<Make> ll2 = criteria.list();

Sorry I have no idea why did you declare date as String:
private String modelCreationDate;
Anyway, I assume you will declare modelCreationDate as Date/LocalDate or with similar Type. I haven't tested the code but I hope the following code will resolve your issue. Good luck!
Calendar cal = Calendar.getInstance();
// today
Date toDate=cal.getTime();
cal.add( Calendar.DATE, -30 );
//date before 30 days
Date fromDate=cal.getTime();
Criteria criteria = session.createCriteria(Model.class , "s")
.createAlias("make", "m")
.add(Restrictions.between("s.modelCreationDate", fromDate, toDate))
// if the above condition dosen't give you exact result you may try following(2)
// try gt or lt ... as needed
//.add(Restrictions.ge("publicationDate", fromDate))
//.add(Restrictions.le("publicationDate", fromDate))
.setProjection(
Projections.projectionList()
.add(Projections.groupProperty("m.makeId").as("makeId"))
.add(Projections.property("m.makeName").as("makeName"))
)
.setResultTransformer(Transformers.aliasToBean(Make.class));
List<Make> ll2 = criteria.list();

Alternative to user3169715's answer, I could also retrieve the records using a query
Calendar cal = Calendar.getInstance();
Date toDate = cal.getTime();
cal.add(Calendar.DATE, -30);
Date fromDate = cal.getTime();
String hql = "SELECT make FROM Model m where m.modelCreationDate between :from_date and :to_date";
Query query = session.createQuery(hql);
query.setParameter("from_date", fromDate);
query.setParameter("to_date", toDate);
List results = query.list(); // Result list

Related

Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff] when trying to select from db

I have a simple select using sqlQuery and I am trying to map it to an entity. I do not understand what the problem is or what can I do about it and I tried multiple sources from this site without any result
function that takes the records
public List<MYTABLE> getRecords(int first, int pageSize, String sortField, Map<String, Object> filters) throws DBEDatabaseException {
try {
EntityManager em = getMyEmOrThrow();
SQLQuery q = em.unwrap(SessionImpl.class).createSQLQuery("SELECT * FROM MYTABLE A where A.status = '1000'");
q.addEntity("A", MYTABLE.class);
q.setFirstResult(first);
q.setMaxResults(pageSize);
List<MYTABLE> results = q.list();
return results;
}catch(RuntimeException e){
throw new MyCustomException("Failure in getting records from table MYTABLE : ", e);
}
```
Entity - **getters and setters and rest of the columns omitted**
#Entity(name = "MYTABLE")
#Table(schema = "MYSCHEMA", name = "MYTABLE")
public class MYTABLE implements Serializable{
#Column(name = "TIMESTAMP", columnDefinition = "TIMESTAMP (6)") // this column is the problem
private Timestamp timestamp;
}
```
THIS DOESN'T WORK AS WELL
#Column(name = "TIMESTAMP", columnDefinition = "TIMESTAMP (6)")
#Temporal(TemporalType.TIMESTAMP)
private Date timestamp;
[Records in db look like this][1]
[1]: https://i.stack.imgur.com/ahDmJ.png
for formatting date output you should use SimpleDateFormat,
also you could define formatter method to your entity

Failed to convert value of type 'java.lang.String' to required type 'java.sql.Date'

There is a Spring MVC app where the customer has the option to place an order. You can set the date and time of the order in the order. I don't have a frontend at all so I'm testing methods via swagger. When I enter a date in the swagger field how should it be stored in the database by default 2020-03-11 09:25:00.000000 returns a json error, and in the application console, it issues a warning. I don't know what to do. Please help me solve this problem.
JSON:
{
"timestamp": "2020-05-21T07:57:50.511+0000",
"status": 400,
"error": "Bad Request",
"message": "Failed to convert value of type 'java.lang.String' to required type 'java.sql.Date'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.sql.Date] for value '2020-03-11 09:25:00.000000'; nested exception is java.lang.IllegalArgumentException",
"path": "/customer/make/order"
}
Class Order:
import java.sql.Date;
#Data
#Entity
#Table(name = "pg_order", schema = "public")
public class Order {
public Order() {
}
// Поля
private #Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#Column(name = "address")
private String address;
#Column(name = "phone_number")
private String phoneNumber;
#Column(name = "date_order")
private Date dateOrder;
#Column(name = "order_status")
private boolean orderStatus;
// Relationships
//
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Customer customer;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "basket_id", referencedColumnName = "id") // Join without Cook in User class
private Basket basket;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Cook cook;
}
CustomerService:
public void makeOrder(String address, String phoneNumber,
Long cookId, Long basketId, Date dateInput) {
double coast = calculateCoast(basketId);
Customer customer = customerRepository
.findByIdAndUserRole(User.getCurrentUser().getId(), User.ROLE_CUSTOMER);
if (coast <= customer.getWallet()) {
customer.setWallet(customer.getWallet() - coast);
} else {
throw new MainIllegalArgument("There is not enough money in the account!");
}
Order order = new Order();
order.setPhoneNumber(phoneNumber);
order.setAddress(address);
java.sql.Date date= new java.sql.Date((dateInput).getTime());
order.setDateOrder(date);
order.setOrderStatus(true);
order.setCustomer(customerRepository.findByIdAndUserRole(User.getCurrentUser().getId(), User.ROLE_CUSTOMER));
order.setCook(cookRepository.findByIdAndUserRole(cookId, "COOK"));
order.setBasket(basketRepository.getById(basketId));
orderRepository.save(order);
}
CustomerController:
#PostMapping("/make/order")
public void makeOrder(String address, String phoneNumber,
Long cookId, Long basketId, Date dateInput) {
customerService.makeOrder(address, phoneNumber, cookId, basketId, dateInput);
}
My Test datas:
Im ny project I use: Spring Boot + Spring MVC + Spring Security + Jpa + Hibernate + PostgreSQL
You are missing the #Temporal annotation at your date fields
#Column(name = "date_order")
#Temporal(TemporalType.TIMESTAMP)
private Date dateOrder;
Temporal.DATE : This provides the SQL Date without the time
Temporal.TIME : Provides the time of the day with hours minutes and seconds
Temporal.TIMESTAMP : Provides the timestamp (date and time ) SQL type upto nanosecond precision
You can do the conversion at the controller level
#PostMapping("/make/order")
public void date(#RequestParam("dateInput")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date dateInput, //other request params.) {
// Call to service.
}
You can use your own format as well #DateTimeFormat(pattern="yyyy-MM-dd") Date fromDate Or you can format the date in your service layer. For more information, read here.
You might need to add the #Temporal annotation to your date attributes
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "date_order")
private Date dateOrder;

How to find every instance was created in today when field is LocalDateTime in JPA?

I have a entity like this
#Entity
#Table(name = "PAYMENT")
public class PaymentSummary {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "payment_id", nullable = false, unique = true)
private Long id;
#Column(name = "date_created", nullable = false)
private LocalDateTime createdDate;
#OneToOne
#JoinColumn(name = "webOrder_id")
private WebOrder webOrder;
#Enumerated(EnumType.STRING)
#Column(name = "payment_type")
private PaymentType paymentType;
#Column(name = "amount")
private Double amount;
#Column(name = "username")
private String username;
}
and i have repository for this entity
#Repository
public interface PaymentRepository extends JpaRepository<PaymentSummary, Long> {
List<PaymentSummary> findAllByCreatedDate(LocalDateTime localDate);
}
Later i want to retrieve every payment was created in today
List<PaymentSummary> payments = paymentRepository.findAllByCreatedDate(LocalDateTime.now());
And it return null, i know because i pass LocalDateTime.now(), so it will find exact by Date-minus-second . I want to list all today payment , and still want to keep LocalDateTime createdDate, how can i handle this situation , do i need to write native query , or JPA support this ?
Thank you.
If you only need to store the date of the payment (year, month, day in month), and don't need time information (hour, minutes,...), you need to change LocalDateTime for LocalDate on your entity.
This way, you would have:
List<PaymentSummary> findAllByCreatedDate(LocalDate localDate);
And, using:
List<PaymentSummary> payments = paymentRepository.findAllByCreatedDate(LocalDate.now());
Would work.
If you need to store the date, and also the time information, you would need to use something like this:
List<PaymentSummary> findAllByCreatedDateBetween(LocalDateTime startDateTime, LocalDateTime endDateTime);
And call it with something like:
// 2020-04-12T00:00.000
LocalDateTime startDateTime = LocalDateTime.of(LocalDate.now() , LocalTime.MIN);
// 2020-04-12T23:59.999
LocalDateTime endDateTime = LocalDateTime.of(LocalDate.now() , LocalTime.MAX);
List<PaymentSummary> payments = paymentRepository.findAllByCreatedDateBetween(startDateTime, endDateTime);
It will return null because LocalDateTime.now() will create the current date and time. In your case I think you need to return by date only

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