Polymorphic CriteriaQuery without inverse relationship - java

I have the following EJB structure. Don't wonder about Animal and Inventory, these classes are only here to demonstrate the structure in a simplified way (Update: I have revised the class names to construct a better understandable example. Another implementation of IdTag might be a BarcodeId). Note that there is no inverse relationship from IdTag to Animal or Inventory, and let's assume the RfidTag.code is unique. I read Retrieving Polymorphic Hibernate Objects Using a Criteria Query and Hibernate polymorphic query but these discussions does not seem to answer my question.
public interface ItemWithIdTag
{
IdTag getIdTag();
void setIdTag(IdTag idTag);
}
#Entity public class Animal implements ItemWithIdTag,Serializable
{
#Id #GeneratedValue(strategy=GenerationType.AUTO) private long id;
#OneToOne(cascade = CascadeType.ALL)
private IdTag idTag;
}
#Entity public class Inventory implements ItemWithIdTag,Serializable
{
#Id #GeneratedValue(strategy=GenerationType.AUTO) private long id;
#OneToOne(cascade = CascadeType.ALL)
private IdTag idTag;
}
#Entity #Table(name = "IdTag") #Inheritance(strategy= InheritanceType.JOINED)
public class IdTag implements Serializable
{
#Id #GeneratedValue(strategy=GenerationType.AUTO) private long id;
private Date created;
}
#Entity #Table(name = "RfidTag")
public class RfidTag extends IdTag implements Serializable
{
private String code;
}
Now I want to query either Animal or Inventory for a given RfidTag.code like Animal ejb = bean.fEntityWithRfidTag(Animal.class,"myRfIdCode");
public <T extends ItemWithIdTag> T fOwner(Class<T> type, String catName)
{
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(type);
Root<T> from = criteriaQuery.from(type);
Path<Object> path = from.join("idTag").get("code");
CriteriaQuery<T> select = criteriaQuery.select(from);
select.where(criteriaBuilder.equal(path, catName));
TypedQuery<T> q = em.createQuery(select);
T result = (T)q.getSingleResult();}
return result;
}
Unfortuately I get the following errror:
javax.ejb.EJBException: java.lang.IllegalArgumentException:
Unable to resolve attribute [code] against path [null]
I assume that this is related to the inheritance IdTag -> RfidTag and Animal only knows about IdTag and not the RfidTag.code. Are queries like this possible?

If you are using EclipseLink, solution is simple. Modify the Path criteria to cast to RfIdTag:
Path<Object> path = ((Path) from.join("idTag").as(RfIdTag.class)).get("code");
If you are using Hibernate, replace your method with:
public static <T extends ItemWithIdTag> T fOwner(Class<T> type, String catName) {
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(type);
Root<T> fromType = criteriaQuery.from(type);
Root<RfIdTag> fromRfId = criteriaQuery.from(RfIdTag.class);
Path<Object> pathCode = fromRfId.get("code");
Path<Object> pathIdTagType = fromType.get("idTag");
Path<Object> pathIdTagRfId = fromRfId.get("id");
CriteriaQuery<T> select = criteriaQuery.select(fromType);
select.where(
criteriaBuilder.equal(pathCode, catName),
criteriaBuilder.equal(pathIdTagType, pathIdTagRfId));
TypedQuery<T> q = em.createQuery(select);
return q.getSingleResult();
}
This makes a "join" ("a filtered cartesian product") between "T" and "RfIdTag".

Related

Spring JPA Criteria API Query on OneToMany List Field

I want to load all the CustomerRequests for a specific Customer with the JPA/Hibernate Criteria API.
In specific: I want to load all the CustomerRequest for which a CustomerRequest2Customer entry with a specific customerId exists.
#Entity
public class CustomerRequest {
#Id
private int id;
private int priority;
#OneToMany(mappedBy = "customerRequestId")
private List<CustomerRequest2Customer> listCustomerRequestToCustomer; // <- Query this field
}
#Entity
public class CustomerRequest2Customer {
#Id
private int id;
#ManyToOne
private Customer customer; // <- Query this field
#ManyToOne
private CustomerRequest customerRequest;
}
#Entity
public class Customer {
#Id
private int id; // <- Query this field
private String name;
}
How I currently query other fields:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<CustomerRequest> cq =
cb.createQuery(CustomerRequest.class);
Root<CustomerRequest> root = cq.from(CustomerRequest.class);
cq.where(cb.equal(root.get("priority"), 1));
return entityManager.createQuery(cq).getResultList();
You have to join the entities like this:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<CustomerRequest> cq = cb.createQuery(CustomerRequest.class);
Root<CustomerRequest> root = cq.from(CustomerRequest.class);
Join<CustomerRequest, CustomerRequest2Customer> customerRequest2Customer = root.join("listCustomerRequestToCustomer");
Join<CustomerRequest2Customer, Customer> customer = customerRequest2Customer .join("customer");
cq.where(cb.equal(customer .get("id"), 1));
return entityManager.createQuery(cq).getResultList();

JPA Criteria API - where condition with field on subclass

I have entity called Issue and entity called UserIssue. UserIssue extends Issue.
#Inheritance(strategy = InheritanceType.JOINED)
#Entity(name = "ISSUE")
public class Issue extends VersionedSequenceIdEntity {
... all fields
}
#Entity(name = "USER_ISSUE")
public class UserIssue extends Issue {
...
#Enumerated(EnumType.STRING)
#Column(name = "CATEGORY", nullable = false)
private IssueCategory category;
...
}
I need to do e.g. something like this:
Predicate predicate= root.get("category").in(IssueCategory.CATEGORY_1, IssueCategory.CATEGORY_2);
The problem is that root is instance of Root<Issue> but "category" field is defined on subclass UserIssue so the line of code obviously does not work.
Is there a way how to build a predicate that creates where condition for subclass field? I have only instance of Root<Issue>, CriteriaQuery and CriteriaBuilder.
Thank you,
Lukas
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Issue> issueQuery = cb.createQuery(Issue.class);
Root<Issue> issueRoot = issueQuery.from(Issue.class);
Subquery<UserIssue> subQuery = issueQuery.subquery(UserIssue.class);
Root<UserIssue> userIssueRoot = subQuery.from(UserIssue.class);
Predicate predicate= userIssueRoot.get("category")
.in(IssueCategory.CATEGORY_1, IssueCategory.CATEGORY_2);
subQuery.select(userIssueRoot).where(predicate);
issueQuery.select(issueRoot).where(issueRoot.get("id").in(subQuery));
em.createQuery(issueQuery).getResultList();

Querydsl: how to downcast a join entity?

I'm looking for a way to make a join on a one-to-many relation where the many-side is defined through inheritance and the right part of the join is restricted to a specific subclass (downcast).
Say I have the entities below (example taken from here):
#Entity
public class Project {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(cascade = CascadeType.ALL)
private List<Employee> employees;
.............
}
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#Entity
#DiscriminatorColumn(name = "EMP_TYPE")
public class Employee {
#Id
#GeneratedValue
private long id;
private String name;
.............
}
#Entity
#DiscriminatorValue("F")
public class FullTimeEmployee extends Employee {
private int annualSalary;
.............
}
#Entity
#DiscriminatorValue("P")
public class PartTimeEmployee extends Employee {
private int weeklySalary;
.............
}
#Entity
#DiscriminatorValue("C")
public class ContractEmployee extends Employee {
private int hourlyRate;
.............
}
I can easily build join queries that involve properties defined in the superclass Employee, like:
JPAQuery query = ...
QProject project = new QProject("p");
QEmployee employee = new QEmployee("e");
query.join(project.employees, employee);
query.where(employee.name.startsWith("A"));
But if I want to access a property of subclass, say FullTimeEmployee.annualSalary, and hence restrict the join to that sub-type, how do I do that?
How do I build the the equivalent of following JPQL:
SELECT DISTINCT p FROM Project p JOIN TREAT(p.employees AS FullTimeEmployee) e WHERE e.annualSalary > 100000
You can do it like this:
EntityManager em = ...;
QProject p = QProject.project;
QFullTimeEmployee e = QFullTimeEmployee.fullTimeEmployee;
List<FullTimeEmployee> emps = new JPAQuery<>(em)
.select(p)
.distinct()
.from(p)
.innerJoin(p.employees, e._super)
.where(e.annualSalary.gt(100000))
.fetch();
See also this post on the Querydsl forum: https://groups.google.com/d/msg/querydsl/4G_ea_mQJgY/JKD5lRamAQAJ

How to generate a generic CriteriaQuery with foreign key property?

Example entities:
#Entity
public class Employee {
#Id
#Column(name="EMP_ID")
private long id;
...
#OneToMany(mappedBy="owner")
private List<Phone> phones;
...
}
#Entity
public class Phone {
#Id
private long id;
...
#ManyToOne
#JoinColumn(name="OWNER_ID")
private Employee owner;
...
}
I have a generic class where queries are generated based on the type of the entity:
public class Repository<T>
{
private Class<T> type;
public List<T> select(String property, Object value) {
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> q = cb.createQuery(type);
Root<T> root = q.from(type);
Path<Object> path = root.get(property);
query.where(path.in(value));
query.select(q.select(root));
TypedQuery<A> query = em.createQuery(q);
return query.getResultList();
}
}
I want to generate the following query
SELECT * FROM PHONE WHERE OWNER_ID = ?
by executing
Repository<Phone> repository;
List<Phone> phones = repository.select("owner.id", 1);
but it doesn't work, because "owner.id" can't be found. Though the statement
em.createQuery("SELECT p FROM Phone p WHERE p.owner.id = :id")
is actually working.
How can i create a generic CriteriaQuery based on type T resulting in the specified statement without knowing the type of Employee?
You have to split your property and then join Employee. For one level of join it could look like this:
String[] splitProperty = property.split(".");
Join<Object, Object> owner = root.join(splitProperty[0]);
Path<Object> path = owner.get(splitProperty[1]);
q.where(path.in(value));
...
Of course if you want more generic solution you have to iterate over the split parts and join other tables.
You can join to the Parent (Employee) from the Child entity (Phone) and ask the joned entity for its id property.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Child> cq = cb.createQuery(Child.class);
Root<Child> cr = cq.from(Child.class);
Join<Child, Parent> pj = cr.join("parent");
cq.where(cb.equal(pj.get("id"), 1));
List<Child> rl = em.createQuery(cq).getResultList();
System.out.println(rl);

Join generic type with JPA and Criteria API

i'm trying to map the following entity:
I have an Order entity that contains different types of OrderData(can also be shared between multiple Order entities), depending on the actual order(different produckts, product variants etc):
//Simplified example
#Entity
#IdClass(OrderPK.class)
#Table(name = "tablename"
public class Order<T extends OrderData> {
#Id
#ManyToOne
#JoinColumn(name = "whatever")
private T orderData;
// Might be complicating stuff
#Id
#ManyToOne
#JoinColumn(name = "somecolumn")
private Account buyerAccount;
// ...
}
// OrderData base class
#Entity
#Table(name = "thatothertable"
#Inheritance(strategy=InheritanceType.JOINED)
public class OrderData {
#Id
#Column(name = "id")
private Long id;
// extra stuff
}
Now the problem is: how can i get this to join the right subclass of OrderData?
I want to be able to write something like this:
List<Order<CustomOrderData>> ordersWithCustomOrderData = this.orderDAO.findAllOrders();
and get all Order entities with CustomOrderDatas.
Is there a way to achieve this?
So far my DAO codes looks like this:
public List<Order> findAllOrders() {
CriteriaBuilder cb = this.em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> root = cq.from(Order.class):
cq.select(root):
return this.em.createQuery(cq).getResultList();
}
How would i have to change the DAO code to support the generic type?
Is it actually possible? If not, is there any alternative design to achieve a comparable structure and functionability (Orders with different OrderDatas, ability to search for Orders with specific subclass of OrderData)?
// Not working pseudo code of what i want
public <T extends OrderData> List<Order<T>> findAllOrders() {
CriteriaBuilder cb = this.em.getCriteriaBuilder();
// With createQuery(Order.class) i will get CrtieriaQuery<Order>
CriteriaQuery<Order<T>> cq = cb.createQuery(Order.class);
// as above
Root<Order<T>> root = cq.from(Order.class):
cq.select(root):
return this.em.createQuery(cq).getResultList();
}
1. Idea
Why do you need a generic Attribute? Generic entities are not possible in JPA but you could just put the reference to a Orderdata as:
private OrderData orderData;
2. Idea
You can save this enum in your entity and then compare OrderDataType.getClazz() to your OrderData1.getClass() and you can query for this types. If you are using JPA 2.1 you can save the full classname in the database column with a custom Fieldconverter and then query for the class.
public enum OrderDataType {
Order1(Order1.class);
private Class<? extends OrderData> clazz;
private OrderDataType(Clazz<? extends OrderData> clazz) {
this.clazz = clazz;
}
public Class<? extends OrderData> getClazz() {
return clazz;
}
}

Categories

Resources