Simple where condition for JPA CriteriaQuery - java

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.

Related

Fetch join causes N+1 queries or throws org.hibernate.QueryException

I am trying to fetch with one query list of objects and its associations, unfortuantely, either I cause N+1 requests to database, or get hit with exception "org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list".
Please let me walk you through my case.
Below is my Data Model:
#Table(name = "first_table")
public class FirstObject {
#Id
#Column(nullable = false, name = "first_id")
private Long id;
#Column(nullable = false, name = "first_param")
private String param1;
#ManyToOne
#JoinColumn(nullable = false, name = "second_id")
private SecondObject second;
...other columns...
}
#Table(name = "second_table")
public class SecondObject {
#Id
#Column(nullable = false, name = "second_id")
private Long id;
#Column(nullable = false, name = "second_param")
private Long param2;
#ManyToOne
#JoinColumn(nullable = false, name = "third_id")
private ThirdObject third;
...other columns...
}
#Table(name = "third_table")
public class ThirdObject {
#Id
#Column(nullable = false, name = "third_id")
private Long id;
...other columns...
}
It is true to database relations, also exactly how I want it on FE.
All I am trying to achieve is to fetch all the associations with one query, giving 2 conditions:
ConditionBuilder condition = new ConditionBuilder()
.and(FirstObject.second.param2.eq(some_number))
.and(FirstObject.param1.eq(some_string));
return from(FirstObject)
.join(FirstObject.second).fetchJoin()
.join(FirstObject.second.third).fetchJoin()
.where(condition.generate())
.fetch();
Unfortunately this code throws exception:
org.hibernate.QueryException: query specified join fetching, but the
owner of the fetched association was not present in the select list
I can make it work, but with N+1 queries, but it is acceptable only for development phase, as will cause performance issue.
...
.join(FirstObject.second).fetchJoin()
.join(FirstObject.second.third)
...
same here:
...
.join(FirstObject.second)
.join(FirstObject.second.third)
...
What I am trying to figure out is how to make hibernate to create one simple query like that:
select
*
from
first_table table1
inner join
second_table table2
on table1.second_id=table2.second_id
inner join
third_table table3
on table2.third_id=table3.third_id
where
table1.first_param="some_string"
table2.second_param=some_number
All the help is very much appreciated, I've been fighting this for some time now, and really counting on community. Thank you very much.
You should be mapping both sides of the entity relationship:
for instance, in FirstObject you have this:
#ManyToOne
#JoinColumn(nullable = false, name = "second_id")
private SecondObject second;
So in SecondObject you should have this:
#OneToMany(mappedBy = "second") // this is the name of the field in the class that defines the join relationship
Collection<FirstObject> firstObjects;
In ThirdObject you should have this:
#OneToMany(mappedBy = "third") // this is the name of the field in the class that defines the join relationship
Collection<SecondObject> secondObjects;

Representing #EmbeddedId as SQL for H2 database

I am currently working on a Java project with Hibernate entities (more below). In order to test my data access object layers, I am using H2 database to populate an in-memory database and throwing queries at it. Until this point, everything is fine.
However, the problem comes when simulating the #EmbeddedId annotation.
#Entity
#Table(name = "BSCOBJ")
public class BasicObject extends AbstractDomainObject {
#EmbeddedId // This annotation here
private RestrainPK restrain;
#Embeddable
public static class RestrainPK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "CODI", nullable = false)
private String coDi;
#Column(name = "COGA", nullable = false)
private String coGa;
#Column(name = "TYOR", nullable = false)
private String tyOr;
public RestrainPK() {
}
... // Getters and setters
}
}
"Simply" creating the table BSCOBJ and populating it gives no value when fetching data (of course, I checked that the request would give result "normally"). How do I represent this nested class in a SQL table creation / value insertion request ? Is that even possible ?
Thanks in advance,
EDIT
As requested, here is some samples about the SQL / Hibernate ran.
Creation request:
CREATE TABLE BSCOBJ (CODI VARCHAR(5) NOT NULL, COGA VARCHAR(5) NOT NULL, TYOR VARCHAR(5) NOT NULL);
Insertion request:
INSERT INTO BSCOBJ (CODI, COGA, TYOR) VALUES
('HELLO', 'MAT', 'REF'),
('BONJ', 'SOME', 'DAIL'),
('SOPA', 'KDA', 'RATIO');
Request given by Hibernate when trying to run the test code:
select r.restrain.tyOr from mypackage.BasicObject r where r.restrain.coDi = :coDi and r.restrain.coGa = :coGa
With the following values:
coDi = "BONJ";
coGa = "SOME";
Throws a NoResultException. I am expecting DAIL, from the second line of the INSERT request.
I have used #EmbeddedId only one time, but I think that you need #AttributeOverrides under your #EmbeddedId
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "idpk", column = #Column(name="IDPK", nullable = false),
#AttributeOverride(name = "code", column = #Column(name="CODE")
})
and remove your #Column annotations from FormulePK

"Fail to convert to internal representation" while accessing changed column data with audit query

I am using envers in my project to audit data.
Now I want to access changed data with audit query.
My pojo class for table is below
#Entity
#Audited(withModifiedFlag=true)
#Table(name = "INSTRUMENT", uniqueConstraints = #UniqueConstraint(columnNames = "INSTRUMENT_NAME"))
public class Instrument implements java.io.Serializable {
private long instrumentId;
private String instrumentName;
private WorkflowState workflowState;
#Id
#Column(name = "INSTRUMENT_ID", unique = true, nullable = false, precision = 22, scale = 0)
public long getInstrumentId() {
return this.instrumentId;
}
public void setInstrumentId(long instrumentId) {
this.instrumentId = instrumentId;
}
#Column(name = "INSTRUMENT_NAME", unique = true, nullable = false, length = 50)
public String getInstrumentName() {
return this.instrumentName;
}
public void setInstrumentName(String instrumentName) {
this.instrumentName = instrumentName;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STATUS", nullable = false)
public WorkflowState getWorkflowState() {
return this.workflowState;
}
public void setWorkflowState(WorkflowState workflowState) {
this.workflowState = workflowState;
}
}
Now I tried to access this with audit query as
AuditQuery query = reader.createQuery().forRevisionsOfEntity(Instrument.class, false, true)
.add(AuditEntity.property("status").hasChanged());
List list= query.getResultList();
So at the time of accessing getResultList() , Its throwing Exception as follows
SqlExceptionHelper: Fail to convert to internal representation
I figured it out, this is because in my db Instrument.status column is as String data Type. While here I am using Join.
So please tell me how to write query to resolve this problem
PROBLEM is How to write Audit Query if my table has foreign key (class property have join dependency).
Join table WorkflowState discription is as follows
public class WorkflowState implements java.io.Serializable {
private BigDecimal stateId;
private Workflow workflow;
private String stateName;
//getters and setters
And it has a join column too i.e "workflow" .
Use workflowState rather than status. The API is based on you specifying the property name and not the column name.

org.hibernate.QueryException: could not resolve property: composite key in query

I am trying to fetch the list of records from a view which has a composite primary key with three columns.
I tried to embed the composite key in the entity class. But I am getting the below mentioned errors. The columns of the views (VW_ALERTS) are C_ID, MAT_ID, P_MONTH, CO_TYPE, CO_SUBTYPE.
Here the composite keys are C_ID, MAT_ID, P_MONTH. I am making the property of them in the embeddable class.
Please help to resolve the issue
org.hibernate.QueryException: could not resolve property: coreId of: com.sp.cpem.dto.VwAlerts [FROM com.ct.cpem.dto.VwAlerts d ORDER BY d.cId ASC]
This following code is used to execute the hql.
Session session = sessionFactory.openSession();
String hql = "FROM VwAlerts d ORDER BY d.coId ASC";
Query query = session.createQuery(hql);
return query.list();
The entity class :
#SuppressWarnings("unchecked")
#Entity
#Table(schema = "TIGER", name = "VW_ALERTS")
public class VwAlerts {
#Embedded
private VwAlertsPK vwAlertsPK;
#Basic
#Column(name = "CO_TYPE", nullable = true)
private String coType;
#Basic
#Column(name = "CO_SUBTYPE", nullable = true)
private String coSubType;
Class used to get the composite key
#Embeddable
public class VwAlertsPK implements Serializable {
#Basic
#Column(name = "C_ID", nullable = false)
private BigDecimal cId;
#Basic
#Column(name = "MAT_ID", nullable = true)
private BigDecimal matId;
#Basic
#Column(name = "P_MONTH", nullable = true)
private BigDecimal pMonth;
I am expecting to get all the records from the view.
I tried with the #Id column in the entity class, it failed by returning only the duplicate records of the first row from the view.
Your entity VwAlerts has only 3 properties --> vwAlertsPK, coType, coSubType
but in your HQL you are trying to access a property coreId which does not exist in your entity.
FROM com.ct.cpem.dto.VwAlerts d ORDER BY d.coreId ASC
So add the property coreId to your entity or else just update the ORDER BY clause so you are pointing to correct properties of your entity.

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