Restrict JPA polymorphic query with Criteria API [duplicate] - java

I'm relativley new to relational databases and I have some problems concerning the creation of queries. First I want to explain the situation shortly. I have several entity classes. All of them extend AbstractEntity or EntityProperty. So entities can have properties and properties have owning entities, so there is a bidirectional relation.
Now let's say ConcreteEntity extends AbstractEntity and I want to create queries like this: Get all entities of type ConcreteEntity which has at least on property with a name contained in the given list propertyNames. Until now I have the following working criteria query:
CriteriaQuery<AbstractEntity> cq = cb.createQuery(AbstractEntity.class);
Root<EntityProperty> property = cq.from(EntityProperty.class);
Join<EntityProperty, AbstractEntity> entity = property.join(EntityProperty_.owningEntities);
cq.where(property.get(EntityProperty_.name).in((Object[]) propertyNames));
cq.select(entity);
But now I want only those entities of type ConcreteEntity. How could I achieve this?
In JPQL I wrote "SELECT entity FROM EntityProperty property JOIN property.owningEntities entity" and here I also have no idea how to write it in the way that only a specific type is returned...
Thanks for answers in advance!
EDIT: moved the second question to criteria query: indistinct result lists and removed distinct in the code (that didn't work)

I know this is an old question but just in case someone stumbles upon the same problem, here is how it can be solved.
You can easily filter by entity type like this:
Predicate p = cb.equal(entity.type(), cb.literal(ConcreteEntity.class));
where entity can be a Path (Root and Join included), cb is a CriteriaBuilder object. So in your case it would be something like this:
CriteriaQuery<AbstractEntity> cq = cb.createQuery(AbstractEntity.class);
Root<EntityProperty> property = cq.from(EntityProperty.class);
Join<EntityProperty, AbstractEntity> entity = property.join(EntityProperty_.owningEntities);
cq.where(cb.and(
property.get(EntityProperty_.name).in((Object[]) propertyNames),
cb.equal(entity.type(), cb.literal.ConcreteEntity.class)
));
cq.select(entity);

The only way I found until now was to create an enumeration with a value for each class The resulting criteria query is
CriteriaQuery<AbstractEntity> cq = cb.createQuery(AbstractEntity.class);
Root<EntityProperty> property = cq.from(EntityProperty.class);
SetJoin<EntityProperty, AbstractEntity> entity =
property.join(EntityProperty_.owningEntities);
cq.where(property.get(EntityProperty_.name).in((Object[]) propertyNames),
entity.get(AbstractEntity_.entityType).in(suitableSubTypes));
cq.select(entity);
List<AbstractEntity> resultList = em.createQuery(cq).getResultList();
As you can see, every entity now has the attribute entityType. I also have to create the collection suitableSubTypes every time. Another problem is that the returned type is List<AbstractEntity>. What I wanted was a method signature like
public static <T extends AbstractEntity> List<T>
getEntities(Class<T> entityClass, String... propertyNames)
but for now I have
public static List<AbstractEntity>
getEntities(Collection<AbstractEntityType> suitableSubTypes,
String... propertyNames)
So I still hope there exists a better solution...

Related

Can I query in hibernate via DTO/Entity not by single fields

I have a question regarding querying in hibernate. If there is a way to search without specifying an where cause explicitly?
So what I mean is: let's say I have a search form with 10 columns that are bound to my dto fields. So the user can fill some of them and left the rest as nulls. And now I would like to search only by fields that are specified (filled) and left the nulls behind (they doesn't matter).
So the query would be like this:
select e
from entity e
where e.entity = e.searchedCriteriaEntityGivenInDTO
Or a better example via jpg: I'd like to have all sample entities without specifying "where number, where name, where firstanme", but over my dto by "where dtoFields". As mentioned the nulls should be ignored.
Thanks a lot in advance.
[EDIT]: Thanks to Dragon I have a great solution how to do it, but I have one more question: What about I have 2 row's, I can search? My query should look like:
select e
from example e,
where (e.entity = example) OR (e.entity = example2);
I tried to put the
session.createCriteria(MyEntity.class).add(Example.create(myEntityExample))
into an OR-Predicate but it seems it does not work.
Any suggestions?
No, you can't do it with DTOs, but you can use a prototype (example) entity instance for it:
MyEntity myEntityExample = new MyEntity();
myEntityExample.setNumber(12);
myEntityExample.setName("AA");
myEntityExample.setFirstName("BB");
List<MyEntity> results = session.createCriteria(MyEntity.class)
.add(Example.create(myEntityExample))
.list();

JPA 2.0 CriteriaQuery with predicate on type being a class or any subclass of

Assuming the following entity classes and hierarchy:
#Entity
Class Ticket {
#ManyToOne(optional = true)
private Customer customer;
}
#Entity
Class Customer {}
#Entity
Class Person extends Customer {}
#Class CustomPerson extends Person {}
How can I query for all tickets which have customer of type Person or any subclass of Person (i.e. CustomPerson)?
I can easily create a predicate on type:
Predicate p = criteriaBuilder.equal(Ticket_...type(), criteriaBuilder.literal(Person.class));
but this will filter out CustomPerson.
Unless there is a generic way to handle this (in JPA 2.0), I could overcome the issue if I could determine at runtime all entities extending Person; in this scenario I could use
Predicate p = criteriaBuilder.in(Ticket_...type(), listOfPersonSubclasses);
JPA 2.0 doesn't offer a generic way, so you would have to list all the subclasses, or change the query so that it is based off your Person class instead. For example "Select t from Person p, ticket t where t.customer=p"
JPA 2.1 introduced 'treat' which allows you to downcast and operate on the entity as if it were a certain class. Since any subclass is also an instance, it filters exactly as you seem to require. https://wiki.eclipse.org/EclipseLink/Release/2.5/JPA21#Treat
You can use it as a join or in the where clause, but an example might be:
"Select t from Ticket t join treat(t.customer as Person) p"
If there is no easy way in newer versions of JPA, you could get the mapping metadata from the underlying Session and find all the mapped subclasses programmatically (you may do it lazily and cache the results).
To get the session:
org.eclipse.persistence.sessions.Session session =
((org.eclipse.persistence.internal.jpa.EntityManagerImpl)
em.getDelegate()).getSession();

Modify CriteriaQuery JOINs after they have been added to the CriteriaQuery

Currently, I have a UserDao class which creates a query using CriteriaQuery, for example to retrieve an instance of User from the database using its ID (let's call that method findById). So far, it's pretty straightforward and that works fine.
However, I also have a GenericDao, which is extended by UserDao. Before userDao.findById returns its results, it passes the criteriaQuery to the GenericDao so I can add some restrictions to the criteriaQuery.
Example:
public class GenericDao
{
private EntityManager entityManager;
protected Object executeCriteriaQuery(CriteriaQuery criteriaQuery)
{
return entityManager.createQuery(prepareQuery(criteriaQuery)).getSingleResult();
}
private CriteriaQuery prepareQuery(CriteriaQuery criteriaQuery)
{
// add restrictions to criteriaQuery
Predicate predicate = ... // some predicate which will be appended to the already formed criteriaQuery
criteriaQuery.where(criteriaBuilder.and(criteriaQuery.getRestriction(), predicate));
return criteriaQuery;
}
}
public class UserDao extends GenericDao
{
public User findById(String id)
{
CriteriaQuery query = criteriaBuilder.createQuery(entityClass);
Root<P> entity = query.from(entityClass);
query.select(entity);
query.where(criteriaBuilder.equal(entity.get("id"), id.toUpperCase()));
return executeCriteriaQuery(query);
}
}
The thing is: the query that is built in the DAOs that extend my GenericDao may contain a JOIN. I want to add a predicate to a JOIN that is added to the criteriaQuery.
From what I can see, I can retrieve the JOINs in the query by using From.getJoins(). However, according to the Javadoc for that method:
Return the joins that have been made from this bound type. Returns empty set if no joins have been made from this bound type. Modifications to the set do not affect the query.
Returns: joins made from this type
How can I add a predicate to JOIN clauses in my criteriaQuery?
After some time, I decided I'm going to implement a different approach to this particular problem. Since I couldn't find any options on how to do this, I attempted to solve it on a JPA implementation level. Since I use Hibernate, I used Hibernate filters. For Eclipselink users: you can use Eclipselink's Multi tenancy feature.
Unfortunately I haven't been able to find a solution on a higher level, so I still have to write a new implementation for this if I were to switch implementations. On the other hand, I probably won't switch to new implementations on a regular basis so that probably won't be an issue.

Building JPA Criteria API query - sorting by number of elements in collection

I am having problem with building a query with JPA Criteria API.
Entities and significant properties:
#Entity
public class Post {
#Id int id;
#OneToMany (mappedBy = "post") Set<Comment> comments;
//...
}
#Entity
public class Comment {
#Id int id;
#ManyToOne Post post;
//...
}
I need a query that will return all posts from db ordered by number of comments (OneToMany in Post).
At first I thought this can be implemented with JPQL like:
SELECT p
FROM Post p
ORDER BY SIZE(p.comments) DESC
But function SIZE(...) can not be used to be ordered by it in JPQL.
So, I found about JPA Criteria API, and tried following:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Post> cq = cb.createQuery(Post.class);
Root<Post> p = cq.from(Post.class);
cq.select(p);
cq.orderBy(cb.desc(p.get("comments")));
List<Post> resultList = em.createQuery(cq).getResultList();
With this query I am not getting proper results. I am aware that I am missing getting size of the set 'comments', but don't know how to add that part. I am not really familiar with JPA Criteria API. How should this query look to get all posts ordered by size of its comments field(set)?
CriteriaBuilder.size(Expression) returns an Expression<Integer> that you may use in the ORDER BY clause. This line of code:
p.get("comments")
..returns a Path<X> which extends Expression<X> so it is safe to use the returned value as an argument to Collection.size().
However, the previously quoted line of code is using a particular overloaded version of Path.get() which will make it impossible for the compiler to infer type parameter X. Instead, the type argument will be assumed to be Object. But Collection.size() has declared his Expression-parameter to be a "parameterized type" with an "upper bound" of Collection (this is not reflected accurately in the first reference to CriteriaBuilder.size() in my answer, StackOverflow insist on erasing the type from the method signature. Please see the JavaDocs instead!). So we must provide the type argument explicitly.
Try this:
cq.orderBy(cb.desc(cb.size(p.<Collection>get("comments"))));

jpa criteria query duplicate values in fetched list

I'm observing what I think is an unexpected behaviour in JPA 2 when fetching a list attribute with a criteria query.
My query is as follows (an extract of it):
CriteriaBuilder b = em.getCriteriaBuilder();
CriteriaQuery<MainObject> c = b.createQuery(MainObject.class);
Root<MainObject> root = c.from(MainObject.class);
Join<MainObject, FirstFetch> firstFetch = (Join<MainObject, FirstFetch>) root.fetch(MainObject_.firstFetch);
firstFetch.fetch(FirstFetch_.secondFetch); //secondFetch is a list
c.select(root).distinct(true);
(So let's say I'm fetching a list as a property of the property of an object.)
The thing is when the query returns multiple results, secondFetch values are duplicated as many times as rows are returned. Each firstFetch should have just one secondFetch but has n instead.
The only particularity i see in this case is all MainObjects happen to have the same FirstFetch instance.
So my guess is the join is being crossed, which is normal, but then JPA fails to assign its secondFetch object to each one of the firstFetchs.
Mappings shouldn't be too special, the're more or less like this
#Entity
#Table(name="mainobject")
public class MainObject{
//...
private FirstFetch firstFetch;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="mainObject_column")
public FirstFetch getFirstFetch() {
return firstFetch;
}
}
and
#Entity
#Table(name="firstFetch")
public class FirstFetch{
//...
private List<SecondFetch> secondFetch;
#OneToMany(mappedBy="secondFetch")
public List<SecondFetch> getSecondFetch() {
return secondFetch;
}
}
& finally
#Entity
#Table(name="secondFetch")
public class SecondFetch {
//....
private FirstFetch firstFetch; //bidirectional
#ManyToOne
#JoinColumn(name="column")
public FirstFetch getFirstFetch() {
return firstFetch;
}
}
I've been looking for some sort of distinct sentence to apply to the fetch but there's none (would have been a 'patch' anyway...)
If i change
List<SecondFetch>
for
Set<SecondFetch>
i'll get the expected result thanks to Sets' Keys, so I do feel this is kind of a misbehaviour in JPA's lists.
I'm not an expert, though, so i could perfectlly be making some mistake in the mappings or query.
Any feeback is very welcome to help clear this out.
Thanks.
I had the exact same problem though I was using JPA criteria API to do the query.
After some research I found a solution which you already mentioned (but was not available, since your not using criteria API): Using distinct.
With JPA criteria it would look like this:
CriteriaQuery<FirstFetch> query = cb.createQuery(FirstFetch.class);
Root<AbschnittC> root = query.from(FirstFetch.class);
root.fetch(FirstFetch_.secondFetch, JoinType.LEFT);
query.distinct(true);
Without using query.distinct(true); the resultset was multiplied with the amount of objects in the secondFetch list.
Hibernate does have something like DISTINCT_ROOT_ENTITY which sound more adequate than just setting a query distinct. But I have not further investigated this. I am also using Hibernate as the JPA provider. Maybe setting the query distinct in JPA ends up using the same code as Hibernates DISTINCT_ROOT_ENTITY would?

Categories

Resources