JPA Criteria API - where condition with field on subclass - java

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

Related

Hibernate generates query with wrong type of #discriminatorvalues of subclasses

I have an inheritance relationship of entities with joined type.
#Entity
#Table(name = "MSM_SUBSCRIPTION")
#DiscriminatorColumn(name = "SUBSCRIPTIONTYPE", discriminatorType = DiscriminatorType.STRING, length = 100)
class subscription {
}
#DiscriminatorValue("com.xxx.XXXSubscription")
#Table(name = "XXX")
public class XXXSubscription extends Subscription implements Serializable {
}
When I'm trying to use a named query such as
SELECT s.class AS subscriptiontype,
FROM
Subscription s
It is resulting in the following query
select
case
when s1_.subscriptionId is not null then com.xxx.XXXSubscription
when s.subscriptionId is not null then 'Subscription'
end AS subscriptiontype,
from
MSM_SUBSCRIPTION s
left outer join
XXXSubscription s1_
on s.subscriptionId=s6_.subscriptionId
Which throws an error as below.
-ORA-00904: "COM"."xxx"."MMSSUBSCRIPTION": invalid identifier
As I noticed, there are no tags around the case statement in generated query, on manually firing this query with tags around the DiscriminatorValue 'com.xxx.XXXSubscription', the query is running fine.
Can someone please help ??
try this
you can define super class with #inheritance annotation
#Entity
#Table(name = "MSM_SUBSCRIPTION")
**#Inheritance(strategy = InheritanceType.SINGLE_TABLE)**
#DiscriminatorColumn(name = "SUBSCRIPTIONTYPE", discriminatorType = DiscriminatorType.STRING, length = 100)
class subscription {
}
#DiscriminatorValue("com.xxx.XXXSubscription")
#Table(name = "XXX")
public class XXXSubscription extends Subscription implements Serializable {
}

JPA Query on sublclass with SingleTable inheritance

Say I have the following entities:
#Entity
public class Container
{
#OneToMany
Set<AbstractElement> elements;
//getter setter and other attributes...
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "CLASS_CODE", discriminatorType = DiscriminatorType.STRING, length = 10)
public abstract class AbstractElement<T> extends AbstractEntity
{
#Transient
T value;
//getter setter and other attributes...
}
#DiscriminatorValue(value = "BOOL")
#Entity
public class BooleanElement extends AbstractElement<Boolean>
{
/**
* {#inheritDoc}
*/
#Column(name = "VALUE_BOOL")
#Override
public Boolean getValue()
{
return super.getValue();
}
}
The question is this:
How can I execute a jpa criteria query on the value of the BooleanElement, starting from the class CONTAINER?
What I actually have so far is this:
CriteriaQuery<Container> criteriaQuery = criteriaBuilder.createQuery(Container.class);
Root<Container> from = criteriaQuery.from(Container.class);
criteriaQuery.select(from);
from = from.join("elements");
Predicate pred = criteriaBuilder.equal(criteriaBuilder.treat(from ,BooleanElement.class).get("value"), FOO);
//etc......
The exception at this point is that "there is not VALUE attributes on the AbstractEntity".
Thanks in advance.
The Treat operator doesn't work in this specific case.
The as operator on the joins it's still not so clear how to implement: i get class cast exception at line 3 when doing this:
CriteriaQuery<AbstractElement> criteriaQuery = criteriaBuilder.createQuery(AbstractElement.class);
Root<AbstractElement> rootAbstract = criteriaQuery.from(AbstractElement.class);
Path predPath = (Path)rootAbstract.as(BooleanElement.class);
predPath.get("value");
The only way to execute this type of query is by executing a subquery
or creating an additional "from" clause:
Root<BooleanElement> from2 = criteriaQuery.from(BooleanElement.class);
Predicate joinPredicate = criteriaBuilder.equal(from, from2);
....

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.

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

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