Missing rows when ordering by nested field using Criteria Api - java

I've encounterd weird behaviour of JPA (provider: EclipseLink) using order by functionality. I have TransactionData class, which has reference to CustomerData class:
#Entity
public class TransactionData {
//...
#ManyToOne
#JoinColumn(name = "CUSTOMER_ID")
private CustomerData customer;
//...
}
#Entity
public class CustomerData {
//...
#Column(name = "LAST_NAME")
private String lastName;
//...
}
In my project, there are some specific cases, where there are transactions, which are not assigned to any customer (called non-customer transactions).
I try to get list of all registered transactions (both cusotmer transactions and non-customer transactions) and sort them by customer's last name. To acheive this I've written following Criteria Api
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<TransactionData> criteriaQuery = criteriaBuilder.createQuery(TransactionData.class);
Root<TransactionData> from = criteriaQuery.from(TransactionData.class);
criteriaQuery.orderBy(criteriaBuilder.asc(from.get("customer").get("lastName"));
TypedQuery<TransactionData> query = entityManager.createQuery(criteriaQuery);
return query.getResultList();
I think I should get list of all transactions, of course, with those, where customer field is set to NULL value. But JPA behaviour is different, because it cut out all transactions, where reference to customer is empty.

from.get("customer").get("lastName") will do implicitly an INNER JOIN. If some transactions have no customer assigned then what you need is a LEFT JOIN:
Join<TransactionData , CustomerData> customer = from.join("customer", JoinType.LEFT);
criteriaQuery.orderBy(criteriaBuilder.asc(customer.get("lastName"));

Related

Hibernate Criteria: projection with joined entity

I'm trying to create a query with Criteria, but I don't succeed to map data from a joined entity.
With this Criteria query the id of the Order entity is override with the id of the ShippingCondition entity :
final Criteria criteria = session.createCriteria(Order.class, "o")
.createAlias("o.shippingCondition", "sc", JoinType.INNER_JOIN)
.setProjection(Projections.projectionList()
.add(Projections.property("o.id"), "id")
.add(Projections.property("o.orderNum"), "orderNum")
.add(Projections.property("o.notes"), "notes")
.add(Projections.property("sc.id"), "id"))
.add(Restrictions.eq("o.id", id))
.setResultTransformer(Transformers.aliasToBean(Order.class));
return (Order) criteria.uniqueResult();
My entities :
#Table(name = "order", schema = "myschema")
public class Order {
private Integer id;
private String orderNum;
private String notes;
private ShippingCondition shippingCondition;
...
}
#Table(name = "shipping_condition", schema = "myschema")
public class ShippingCondition {
private Integer id;
private String shippingCondition;
private Integer sorting;
...
}
I have tryed to replace .add(Projections.property("sc.id"), "id") by .add(Projections.property("sc.id"), "shippingCondition.id") but then I get a ClassCastException (java.lang.ClassCastException: entity.Order cannot be cast to java.util.Map)
Do you have any idea how I can do that ?
Thanks
Hibernate doesn't support nested projections. You will need to create DTO for that. You can extend DTO from Order class and add methods to set fields of ShippingCondition.
class OrderDto extends Order {
public OrderDto() {
setShippingCondition(new ShippingCondition());
}
public void setShippingConditionId(Integer id) {
getShippingCondition().setId(id);
}
}
You can use a special nested transformer if you don't want to use DTO
How to transform a flat result set using Hibernate
Additional notes
JPA doesn't support any transformers at all. And it is hard to implement such transformer by consistent way. For example, my transformer doesn't support child collections like #OneToMany, only single associations. Also, you can't use nested projections with HQL, because HQL doesn't support parent.child aliases.

How to add sub-select to select

I want to execute a query like this:
SELECT Table1.COL1,
Table1.COL2,
(SELECT SUM(Table2.COL3)
FROM Table2
WHERE Table2.UID = Table1.UID) SUMOF
FROM Table1;
How can I do it?
I usually create a Criteria add ProjectionList to it, to fill COL1 and COL2 only.
I have created a DetachedCriteria to calculate the sum...
Now, how to attach this detached criteria to the main one? My intuition says - it's some sort of Projection which needs to be added to the list, but I don't see how. Also, not sure how WHERE Table2.COL4 = Table1.COL5 of detached criteria will work.
Also, I'm sure this query might be written in different way, for example with join statement. It's still interesting if there's a way to run it like this.
DetachedCriteria and main Criteria
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Table2.class, "table2");
detachedCriteria
.setProjection(
Projections.projectionList()
.add(Projections.sum("table2.col3"), "sumCol3")
)
.add(Restrictions.eq("table2.uid", "table1.uid"))
;
Criteria criteria = session.createCriteria(Table1.class, "Table1");
criteria
.setProjection(
Projections.projectionList()
.add(Projections.property("Table1.col1"), "col1")
.add(Projections.property("Table1.col2"), "col2")
)
;
Entities (very short version)
#Entity
#Table(name = "Table1")
public class Table1 {
#Id
#Column(name = "uid")
public String getUid();
#Column(name = "col1")
public String getCol1();
#Column(name = "col2")
public String getCol2();
#Column(name = "col3")
public String getCol3();
#Column(name = "col4")
public String getCol4();
#Column(name = "col5")
public String getCol5();
}
#Entity
#Table(name = "Table2")
public class Table2 {
#Id
#Column(name = "uid")
public String getUid();
#Column(name = "col3")
public BigDecimal getCol3();
#Column(name = "col4")
public String getCol4();
#Column(name = "col5")
public String getCol5();
}
For a correlated subquery (like the one you presented above), you can use #Formula which can take an arbitrary SQL query. Then, you'll need to fetch the entity and the subquery will be executed.
However, a native SQL is more elegant if you only need this query for a single business requirement.
As for derived table queries (e.g. select from select), neither JPA nor Hibernate support derived table queries for a very good reason.
Entity queries (JPQL pr Criteria) are meant to fetch entities that you plan to modify.
For a derived table projection, native SQL is the way to go. Otherwise, why do you think EntityManager offers a createNativeQuery method?

JPA 2.1 - How to use dynamic join conditions with the Criteria API to fill OneToMany associations?

I'm using Glassfish 4.1 and JPA 2.1 powered by EclipseLink + Postgresql 9.4.1.
Let's assume we have a car rental company. A customer can rent a car, but the customer can rent
the same car only once. Now the goal is to return a list of all cars. However, for each car in the list
we want to tell the user whether the user ever rented this car before of not. This additional information
(for the UI) can be either a (transient?) boolean flag. In our case, I guess simply filling a corresponding association with the right data
fits exactly what we want (see code below). However, I am not very sure how to use a flag instead - any advice here? Anyway...
We have to use the Criteria API, as there are
many other dynamic filters which we need (irrelevant for this question), so using a NamedQuery with JPQL or
even a NamedNativeQuery is not possible and not in our favor.
In other words:
The list of cars should contain all available cars
Each car in the list ever rented by user 123456 should also have the corresponding rental (the length of this list would always be one then)
The Criteria API should generate exactly 1 native SQL query which uses the correct JOIN conditions
The association "rentals" for each car should be either empty or filled with exactly one Rental instance of the given user
Instead of the given association it would be possibe to use a boolean flag instead, i.e. "alreadyRented" - any idea?
I know how to do this outside of JPA directly on the DB. But I want to use JPA for this. Any I want JPA to fill
the association automatically using a single SELECT + LEFT JOIN query, however, things are not not as easy as I thought...
Any idea? Would you suggest a different data model?
Here is our Car Entity:
#Entity
public class Car {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
#NotNull
private String manufacturer; //simplified
#OneToMany(mappedBy="car", fetch=FetchType.LAZY)
private List<Rental> rentals;
//...
}
According to this mapping, the "rentals" attribute holds a list of all rentals ever made for a given car. Please note that this list is not per user!
And here is the Rental Entity, which basically holds data for all rentals for a given car (again, this is simplified).
#Entity
#Table(
name="RENTALS",
uniqueConstraints={
#UniqueConstraint(columnNames={"CUSTOMER_ID", "CAR_ID"})
}
)
public class Rental {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne(optional=false, fetch= FetchType.EAGER)
#JoinColumn(name="CUSTOMER_ID", nullable=false, updatable=false)
#NotNull
private Customer customer;
#ManyToOne(optional=false, fetch=FetchType.EAGER)
#JoinColumn(name="CAR_ID", nullable=false, updatable=false)
#NotNull
private Car car;
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
#Column(nullable=false)
#NotNull
private Date fromDate;
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
#Column(nullable=false)
#NotNull
private Date toDate;
//...
}
And here is finally the Customer Entity, which is used in our Rental Entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(nullable=false)
#NotNull
private String firstName;
#Column(nullable=false)
#NotNull
private String lastName;
//...
}
And here is finally my EJB, which uses the injected EntityManager to access the DB:
#Stateless
#Local
public class CarBean {
#PersistenceContext(unitName = "myPU")
private EntityManager em;
//...
public List<Car> getCarsForCustomer(Long userId) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> q = cb.createQuery(Car.class);
Root<Car> rootCar = q.from(Car.class);
List<Predicate> predicates = new ArrayList<Predicate>();
//...
//can't just do this because we need a different/dynamic JOIN condition!!
//rootCar.fetch("rentals", JoinType.LEFT);
//now let's try to create the dynamic join condition:
Predicate criteria = cb.conjunction();
Join<Car,Rental> rental = rootCar.join("rentals", JoinType.LEFT);
criteria = cb.and(criteria, cb.equal(rental.get("customer").get("id"), userId) );
rental.on(criteria);
q.select(rootCar).where(predicates.toArray(new Predicate[]{}));
return em.createQuery(q).getResultList();
}
}
All this will generate the following native SQL statement:
SELECT t1.ID, t1.MANUFACTURER
FROM CAR t1
LEFT OUTER JOIN RENTALS t0
ON ((t0.CAR_ID = t1.ID) AND (t0.CUSTOMER_ID = 123456))
As you can see from the generated statement the joined RENTALS are not part of the result set. Even if it would be part of the result set I'm not sure if JPA would use them to fill the rentals association.
Using a Fetch Join is not possible, as we cannot dynamically choose the join columns/conditions. However, when I uncomment the Fetch Join (see code) then I get the following native SQL statement that uses two JOINS which I don't want:
SELECT
t1.ID, t1.MANUFACTURER, t0.ID, t0.FROMDATE, t0.TODATE, t0.CAR_ID, t0.CUSTOMER_ID
FROM CAR t1
LEFT OUTER JOIN RENTALS t0 ON (t0.CAR_ID = t1.ID)
LEFT OUTER JOIN RENTALS t2 ON ((t2.CAR_ID = t1.ID) AND (t2.CUSTOMER_ID = 123456))
So the big question is how can I fill the rentals association by using "dynamic" join conditions? What am I doing wrong?

convert criteria to detached criteria for self join

I would like to know if i can convert this criteria into a detached criteria. I am not understanding detached criteria correctly. can some one help.
Criteria crit = sessionC.createCriteria(OP_DOCTOR_VISIT.class, "OPDV1");
crit.createAlias("OPDV1.OP_VISIT", "OPDV2", JoinType.LEFT_OUTER_JOIN, Restrictions.and(Restrictions.eq("OPDV2.FORM", "NEW"), Restrictions.ge("OPDV2.USER_DATETIME", fromdate), Restrictions.le("OPDV2.USER_DATETIME", todate)));
crit.add(Restrictions.ge("OPDV1.USER_DATETIME", fromdate));
crit.add(Restrictions.le("OPDV1.USER_DATETIME", todate));
ProjectionList p1 = Projections.projectionList();
p1.add(Projections.alias(Projections.count("OPDV1.OP_VISIT_ID"), "TOTAL"));
p1.add(Projections.count("OPDV2.FORM"));
p1.add(Projections.alias(Projections.sqlGroupProjection("date(this_.USER_DATETIME) as createdDate", "createdDate", new String[]{"createdDate"}, new Type[]{StandardBasicTypes.DATE}), "DAT"));
crit.setProjection(p1);
Is it possible to rewrite the above so that I could avoid using "#OneToMany" in my POJO given below.
POJO
#Entity
#Table(name = "OP_DOCTOR_VISIT")
#SQLDelete(sql = "UPDATE OP_DOCTOR_VISIT SET DELETED = 'DELETED' WHERE OP_VISIT_ID = ? and VERSION_UPDATES = ?")
#Where(clause = "DELETED <> 'DELETED'")
public class OP_DOCTOR_VISIT implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "OP_VISIT_ID")
private Long OP_VISIT_ID;
#OneToMany(mappedBy = "OP_VISIT_ID", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#LazyCollection(LazyCollectionOption.EXTRA)
#Fetch(FetchMode.SELECT)
private List<OP_DOCTOR_VISIT> OP_VISIT;
public Long getOP_VISIT_ID() {
return OP_VISIT_ID;
}
public void setOP_VISIT_ID(Long OP_VISIT_ID) {
this.OP_VISIT_ID = OP_VISIT_ID;
}
public List<OP_DOCTOR_VISIT> getOP_VISIT() {
return OP_VISIT;
}
public void setOP_VISIT(List<OP_DOCTOR_VISIT> OP_VISIT) {
this.OP_VISIT = OP_VISIT;
}
}
Only the first line where you create your criteria object.
DetachedCriteria allows you to create a query without session. So you do not require session while builiding your query. DetachedCriteria is same as a Criteria except you can create your queries without session.
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(OP_DOCTOR_VISIT.class);
Finally when you have a session object is available you may execute your query
`criteria .getExecutableCriteria(session).
DetachedCriteria crit = DetachedCriteria.forClass(OP_DOCTOR_VISIT.class, "OPDV1");
The detached criteria allows you to create the query without Session. Then you can execute the search in an arbitrary session.
In fact you should think carefully when use detached criterias using another, or a new, session (no cache, and creation of the session).
They are most useful for make some join conditions, subselects, and to query outside the current session.
Another common use is for code reuse.
If you are using Spring and choose to use HibernateTemplate, it doesn't provide createCriteria() method.
You will find only **DetachedCriteria.

JPA 2.5 CriteriaQuery conditional child entities

I'm have an entity
#javax.persistence.Table(name = "CONTACT")
public class ContactEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "contact")
private List<PhoneEntity> phones;
...
}
#Table(name = "CONTACT_PHONE")
#Entity
public class PhoneEntity {
#Column(name = "DEVICE_TYPE")
private String deviceType;
...
}
When I select my entity using CriteriaBuilder, I want to filter phones owned by Contact by some perticular field. For example, by deviceType. Is it possible using Criteria API?
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<ContactEntity> criteriaQuery = criteriaBuilder.createQuery(ContactEntity.class);
Root<ContactEntity> root = criteriaQuery.from(ContactEntity.class);
List<ContactEntity> contactSnapshotEntities =
entityManager.createQuery(criteriaQuery).getResultList();
If you ask a Contact entity for its phones, you'll always get all the phones of this Contact entity, whatever the way you obtained the Contact entity. That's the contract of your class: a Contact has many phones, available using the getPhones() method.
If you want a subset of the phones of a Contact entity, then you should not get the Contact and get its phones. You should use a query which returns another collection, which only contains the phones matching the criteria. For example:
select p from Phone p where p.contact = :contact and p.deviceType = :deviceType
(or the same query obfuscated by the criteria API).

Categories

Resources