JPA 2.5 CriteriaQuery conditional child entities - java

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

Related

JPA Criteria: QuerySyntaxException for left join on treat entity

Model:
#Entity
public class User {
#Id
private Integer id;
#JoinColumn(name = "user_id")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Project> projects;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Type")
public abstract class Project {
#Id
private Integer id;
private String name;
}
#Entity
#DiscriminatorValue("Administrative")
public class AdminProject extends Project {
private String departmentName;
}
#Entity
#DiscriminatorValue("Design")
public class DesignProject extends Project {
private String companyName;
}
I am trying to use JPA's criteria api to query for User entities based on an attribute of an implementation of Project. For example, query all users that have a project with "SOME_NAME" department (that field does not exist on DesignProject).
I see there is a way of doing so via downcasting of the Project entity for the query. I am trying something similar to:
CriteriaBuilder cb...
Root<User> userRoot...
root = ((From) root).join("projects", JoinType.LEFT);
root = cb.treat(root, AdminProject.class);
root = root.get("departmentName");
Exception:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.departmentName' [select generatedAlias0 from io.github.perplexhub.rsql.model.User as generatedAlias0 left join generatedAlias0.projects as generatedAlias1 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0]; nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.departmentName' [select generatedAlias0 from io.github.perplexhub.rsql.model.User as generatedAlias0 left join generatedAlias0.projects as generatedAlias1 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0]
What am I missing? Is it something related to the join, or how the downcasting occurs afterwards?
Edit
After the answer by #K.Nicholas, I have managed to make the query work on an isolated scenario, but not on my app.
But, I noticed that the entityManager.createQuery(query) call throws the exception above when called for the first time, and it works if I call it again without changing the query object. Here is the query generated on the second call (this query finds the objects I want from the database):
select generatedAlias0 from User as generatedAlias0 left join generatedAlias0.projects as generatedAlias2 where treat(generatedAlias2 as io.github.perplexhub.rsql.model.AdminProject).departmentName=:param0
Why is the entity manager creating two different queries when called two consecutive times?
I would do the Entitys a little different, as you will see. The main concern is that you are using User as your root with a join to a list of Projects. This is a concern because you should have the foreign key on the Project class and use the projects field as a query only field. That is what I have done. It works better that way. It is also a concern because you have to do a join fetch instead of a join so that the projects get fetched along with the users.
So, first, the entities are like so:
#Entity
public class User {
#Id
private Integer id;
#OneToMany(mappedBy="user")
private List<Project> projects;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "Type")
public abstract class Project {
#Id
private Integer id;
private String name;
#ManyToOne
private User user;
}
#Entity
#DiscriminatorValue("Administrative")
public class AdminProject extends Project {
private String departmentName;
}
#Entity
#DiscriminatorValue("Design")
public class DesignProject extends Project {
private String companyName;
}
After a bit a digging I found a JPQL query that does the trick. This was a starting point:
List<User> users = entityManager.createQuery("select distinct(u) from User u join fetch u.projects p where TYPE(p) = 'Administrative' and p.departmentName = 'dept1'", User.class).getResultList();
After a bit more digging I found that the treat worked fine if you do it correctly and that with JPA 2.1 you should use an EntityGraph do get the join to do a fetch.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = builder.createQuery(User.class);
Root<User> root = query.from(User.class);
Join<User, Project> join = root.join("projects");
query.select(root).where(builder.equal(builder.treat(join, AdminProject.class).get("departmentName"), "dept1"));
EntityGraph<User> fetchGraph = entityManager.createEntityGraph(User.class);
fetchGraph.addSubgraph("projects");
users = entityManager.createQuery(query.distinct(true)).setHint("javax.persistence.loadgraph", fetchGraph).getResultList();
As a side note the queries generated as slightly different but I didn't look that closely at them. You should.

JPA #OneToMany Fetch Overrides CriteriaQuery Join Condition

I am quite new to Hibernate and the Criteria API, and am running into troubles using them.
There are two Entities:
#Entity
public class Product {
#Id
#GeneratedValue
private Integer id;
private String productName;
#OneToMany(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private List<ProductPrice> prices = new ArrayList<>();
}
#Entity
public class ProductPrice {
#Id
#GeneratedValue
private Integer id;
private BigDecimal price;
private String region;
private LocalDate startDate;
}
Products have multiple ProductPrices. Each ProductPrice belongs to a Region.
The goal is to query Products and all their historical Prices for a specific Region:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Product> cq = cb.createQuery(Product.class);
Root<Product> root = cq.from(Product.class);
ListJoin<Product, ProductPrice> productJoin = root.join(Product_.prices, JoinType.INNER);
productJoin.on(cb.equal(productJoin.get(ProductPrice_.region), "REGION1"));
List<Product> products = em.createQuery(cq.distinct(true)).getResultList();
This generates the following SQL Query:
select
distinct product0_.id as id1_1_,
product0_.productName as productN2_1_
from
Product product0_
inner join
(
Product_ProductPrice prices1_
inner join
ProductPrice productpri2_
on prices1_.prices_id=productpri2_.id
)
on product0_.id=prices1_.Product_id
and (
productpri2_.region=?
)
I tried that query and it seems to work, however as soon as getPrices() is called on one of the Products, the Product's Prices are lazily fetched, like so:
select
prices0_.Product_id as Product_1_2_0_,
prices0_.prices_id as prices_i2_2_0_,
productpri1_.id as id1_3_1_,
productpri1_.price as price2_3_1_,
productpri1_.region as region3_3_1_
from
Product_ProductPrice prices0_
inner join
ProductPrice productpri1_
on prices0_.prices_id=productpri1_.id
where
prices0_.Product_id=?
which makes sense, because of the association #OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL), but in this case, for this specific query, I don't want this behaviour. I did not find an example like that in the Hibernate UserGuide or here on stackoverflow so I guess I am missing something very obvious. But still, I couldn't find a solution to my problem.
Thanks!
As mentioned in the comments above, on specifies the columns needed for the join. In your situation, you need a where.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Product> cq = cb.createQuery(Product.class);
Root<Product> root = cq.from(Product.class);
ListJoin<Product, ProductPrice> productJoin = root.join(Product_.prices, JoinType.INNER);
cq.where(cb.equal(productJoin.get(ProductPrice_.region), "REGION1"));
List<Product> products = em.createQuery(cq).getResultList();
In addition, you should have a look whether your #OneToMany mapping is designed efficiently like this. This excellent article of Vlad Mihalcea describes how to map a #OneToMany efficiently: Either make it bidirectional or unidirectional with #ManyToOne.
For the issue of your lazy loads, have a look for lazy initialisation. I really like graphs for doing this.

How to get list from Entity in Hibernate using Criteria API?

Recently I've encountered problems using Criteria API. It is my very first contact with it. Here is part of my OfficeEntity.
#Entity
#Table(name = "office")
public class OfficeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column (name = "office_id")
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="office_id", referencedColumnName = "office_id")
private List<WorkerEntity> workers = new ArrayList<>();
GOAL - GET LIST OF WORKERS ENTITIES FROM SPECIFIC OFFICE ENTITY.
By now I've figured out how to achieve the goal by this:
#Override
public List<WorkerEntity> getWorkersByOffice(long officeId) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<OfficeEntity> query = cb.createQuery(OfficeEntity.class);
Root<OfficeEntity> office = query.from(OfficeEntity.class);
query.select(office).where(cb.equal(office.get("id"),officeId));
TypedQuery<OfficeEntity> typedQuery = entityManager.createQuery(query);
OfficeEntity foundOffice = typedQuery.getSingleResult();
return foundOffice.getWorkers();
}
And it works fine but I think I rely too much on Java List interface methods instead of Criteria API. Can I get this list of WorkerEntity (field "workers" in OfficeEntity) just by creating proper query in Criteria? If then, can any advise or proper solution be delivered?
Best regards,
Newbie to Hibernate & Criteria.
I am assuming there is two way binding between Office and Worked entities. If not create parent entity in Worked using #ManyToOne annotation. Then the below code should work
#Override
public List<WorkerEntity> getWorkersByOffice(long officeId) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<WorkerEntity> query = cb.createQuery(WorkerEntity.class);
Root<WorkerEntity> worker= query.from(WorkerEntity.class);
OfficeEntity office = new OfficeEntity();
office.setId(officeId);
query.select(worker).where(cb.equal(worker.get("office"),office));
TypedQuery<OfficeEntity> typedQuery = entityManager.createQuery(query);
List<WorkerEntity> workerList= typedQuery.getResultList();
return workerList;
}

Missing rows when ordering by nested field using Criteria Api

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

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.

Categories

Resources