How to generate a generic CriteriaQuery with foreign key property? - java

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

Related

Spring boot jpa specification filter records of child entity from parent entity

I have two entities A and B where A is the parent and B is the child. I want to filter records using JPA specifications like Specification.
Their relation is Entity B is having reference to A. I want to apply left join on Entity A using JPA Criteria Builder API. Is it possible to achieve that ?
Here is my Parent entity class
#Entity
public class Parent {
#Id
private Long parentId;
private String name;
private String description;
// constructor, getter and setters
}
Here is my child entity class
#Entity
public class Child {
#Id
private Long childId;
private String childName;
private String email;
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
// constructor, getter and setters
}
I want to achieve something like this below code, just want to filter each record based on the childName of the Child entity from Parent Specification.
#Component
public class ParentSpecification {
public Specification<Parent> getParentSpecification(Map<String, String> filterValues) {
return (root, query, criteriaBuilder) -> {
Root<Child> from = query.from(Child.class);
from.join("parent", JoinType.LEFT);
List<Predicate> predicates = new ArrayList<>();
filterValues.forEach((attribute, value) -> {
if (attribute.equalsIgnoreCase("childName") && value != null) {
predicates.add(criteriaBuilder.like(from.get("childName"), "%" + value + "%"));
}
});
return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getRestriction();
};
}
}
There are many resources online on this topic.
Example:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Author> root = cq.from(Author.class);
Join<Object, Object> book = root.join(Author_.BOOKS, JoinType.LEFT);
cq.multiselect(root, book);
ParameterExpression<String> pLastName = cb.parameter(String.class);
cq.where(cb.equal(root.get(Author_.LAST_NAME), pLastName));
TypedQuery<Tuple> q = em.createQuery(cq);
q.setParameter(pLastName, "Janssen");
List<Tuple> authorBooks = q.getResultList();
Reference: https://thorben-janssen.com/hibernate-tip-left-join-fetch-join-criteriaquery/#Defining_a_LEFT_JOIN_or_RIGHT_JOIN_clause

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

How to get distinct values of a single column in Criteria API(JPA)

Hi I am new for JPA & Criteria API.
I am trying to fetch distinct values of a single column (trying to get only distinct values of TestId).I have below table in my DB.
__________________________________
|TestId(Pk) | TestCol(PK) | TestEx|
__________________________________
I have below classes
Model Class
#Data
#Entity
#Table(schema = "TEST", name = "TEST_TYPE")
public class Test {
#EmbeddedId
private TestPK id;
#Column(nmae = "TestEX")
private double testEx
}
#Data
#Embeddable
public class TestPK {
#Column(name = "TestId")
private String testId;
#Column(name="TestCol")
private String testcol
}
Repository class
public class TestRepoImpl {
#PersistenceContext
EntityManager em;
#Override
public List<Test> getData() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Test> cq = cb.createQuery(Test.class);
Root<Test> root = cq.from(Test.class);
// cq.get(Test_.id).get(TestPK_.testId);
// cq.select(root);
cq.multiselect(root.get(Test_.id).get(TestPK_.testId));
cq.distinct(true);
// List<Tuple> ts = em.createQuery(cq).getResultList();
List<Test> data = em.createQuery(cq).getResultList();
return data;
}
}
I am getting below error.
Partial object queries are not allowed to maintain the cache or edited.
You must use dontMaintainCache().
Please try this option as mentioned in here ((org.eclipse.persistence.jpa.JpaQuery)query).getDatabaseQuery().dontMaintainCache();
Result is not a list of Test but String, private String testId;
Changing
CriteriaQuery<Test> cq = cb.createQuery(Test.class);
to
CriteriaQuery<String> cq = cb.createQuery(String.class);
and the rest of code correspondingly might help.

In a optional OneToOne join scenario, how to get entity A that does not have a B?

I have a class A, that has an optional link to an instance of a B class.
For the sake of user-friendliness, I want to filter the output of the grid displaying the instances of the A class, with a switch allowing to display all of As, only As with a B or only As without a B.
My abstract DAO is taking care of the process by dynamically building criteria queries according to the user desire for sorting/filtering.
Works great for the two first scenarios, however, the last one always returns an empty list.
Looking at the generated SQL code, I've got something like this (stripped down on purpose):
Select t0.id as id [...] from person t0, user_profile t1 where t1.person_id = t0.id and t1.people_id is null;
I see the logic behind this. However, this will definitely never work for my last case. I wonder how to get around this use case ? Any help ?
The criteria is built programmatically, here is how it would look like if built manually:
final CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<T> cq = getContextQuery(context); // Juste create the base Criteria
Root<T> root = findRoot(cq); // Retrieve the root.
Path p = root.join(part, JoinType.LEFT); // 'part' is the entity to join on, got from metamodel.
cq.where(cb.isNull(p));
return getEntityManager().createQuery(cq).getResultList();
What entities look like (stripped down to the meaningful part):
#Entity
#Table(name = "person")
#Getter
#Setter
public class Person extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected Integer id;
#XmlTransient
#OneToOne(optional = true, mappedBy = "person")
private UserProfile user;
}
#Entity
#Table(name = "user_profile")
public class UserProfile extends AbstractEntity implements Authenticated {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#OneToOne(optional = false)
#JoinColumn(name = "person_id", referencedColumnName = "id")
#Getter
#Setter
private Person person;
}
Using your entities asumming A = UserProfile and B = Person
I want to filter the output of the grid displaying the instances of the A class, with a switch allowing to display all of As,
CriteriaBuilder cb = ...
CriteriaQuery<UserProfile> q = cb.createQuery(UserProfile .class);
Root<UserProfile> userProfile= q.from(UserProfile.class);
q.fetch(userProfile.get("person"), JoinType.LEFT);
q.select(userProfile)
only As with a B
CriteriaBuilder cb = ...
CriteriaQuery<UserProfile> q = cb.createQuery(UserProfile .class);
Root<UserProfile> userProfile= q.from(UserProfile .class);
q.select(userProfile)
q.where(cb.isNotEmpty(userProfile.get("person"));
OR
CriteriaBuilder cb = ...
CriteriaQuery<UserProfile> q = cb.createQuery(UserProfile .class);
Root<UserProfile> userProfile= q.from(UserProfile .class);
Join<UserProfile, Person> person = userProfile.join("person", JoinType.INNER);
q.select(userProfile)
or only As without a B
CriteriaBuilder cb = ...
CriteriaQuery<UserProfile> q = cb.createQuery(UserProfile .class);
Root<UserProfile> userProfile= q.from(UserProfile .class);
q.select(userProfile)
q.where(cb.isEmpty(userProfile.get("person"));
OR
CriteriaBuilder cb = ...
CriteriaQuery<UserProfile> q = cb.createQuery(UserProfile .class);
Root<UserProfile> userProfile= q.from(UserProfile .class);
Join<UserProfile, Person> person = userProfile.join("person", JoinType.lEFT);
q.select(userProfile)
And then
return getEntityManager().createQuery(q).getResultList();
That should work.

Polymorphic CriteriaQuery without inverse relationship

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

Categories

Resources