JPA Criteria Query with JOIN on Polymorphic Subclass - java

I have the following (simplified) data model:
#Entity
public class Person
{
#ManyToOne Animal likes;
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public class Animal{}
#Entity
public class Pet extends Animal
{
#ManyToOne Home home;
}
#Entity
public class Domestic extends Animal
{
#ManyToOne Farm farm;
}
#Entity public class Home {}
#Entity public class Farm {}
Now I want a group the persons by home and count on different homes:
CriteriaBuilder cB = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cQ = cB.createTupleQuery();
Root<Person> person = cQ.from(Person.class);
Join<Person,Pet> jPet = person.join("likes");
Join<Pet,Home> jHome = jPet.join("home");
Here I'm getting the error
Unable to locate Attribute with the the given name [home] on this ManagedType [Animal] the generated SQL query obviously explains why:
INNER JOIN animal a ON person.likes_id = animal.id. The next approach was to use .treat:
Join<Person,Pet> jPet = cB.treat(debit.join("likes), Pet.class);
Join<Pet,Home> jHome = jPet.join("home");
This seemed to be promising with the new .treat feature of JPA 2.1, but the generated SQL looks like this
INNER JOIN animal ON person.likes_id = animal.id
LEFT OUTER JOIN pet ON pet.id = animal.id
LEFT OUTER JOIN domestic ON domestic.id = animal
In fact it is joining all subclasses of Animal where I'm only interested in Pet. So I tried to limit to the specific class with a Predicate cB.equal(jPet.type(),Pet.class). This generates some interesting SQL (which I have never seen before and where I wonder what is inserted in END=?)
WHERE CASE
WHEN pet.id IS NOT NULL THEN 1
WHEN domestic.id IS NOT NULL THEN 2
WHEN animal IS NOT NULL THEN 0
END = ?
But it still does not work as expected. How can I count the different home with JPA criteria queries where I start from a Person?

You may use multiple roots (mine: I believe that is the most common scenario) in Criteria API query:
Create and add a query root corresponding to the given entity, forming a cartesian product with any existing roots.
So, your query might look like:
Root<Person> person = cQ.from(Person.class);
Root<Pet> pet = cQ.from(Pet.class);
cQ.where(cB.equal(pet, person.get("likes")));
Moreover, that is not required to define X-TO-Y associations in order to write a query - we may join "unrelated" entities as well.

Related

Limit the set of selected fields to those of the target subclass

I'm using single table inheritance, one parent entity:
#Entity
#Table(name = "parent")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type")
public class Parent {
// ...
}
and two child entities:
#Entity
#DiscriminatorValue("child1")
public class Child1 extends Parent {
#Column(name = "child1_property")
private Integer child1Property;
// ...
}
#Entity
#DiscriminatorValue("child2")
public class Child2 extends Parent {
#Column(name = "child2_property")
private Integer child2Property;
// ...
}
Now, if I query (HQL) directly from Child1 entity:
from Child1
it will generate an SQL that selects only the columns from Parent entity plus Child1 entity. If I select from Parent entity using a where type='child1'
from Parent p where type(p)='child1'
it will return only Child1 entities but the SQL query selects all columns from Parent, Child1 and Child2. Is there a possibility to query from Parent entity use the discriminator column (i.e. limit only to Child1 entities) and get a specific SQL query, i.e. that will select only columns from Parent and Child1.
A projection seems to work:
SELECT new fully.qualified.name.of.Child1(p.id, p.name, p.child1Property)
FROM Parent p
WHERE TYPE(p) = 'child1'
produces
select
parent0_.id as col_0_0_,
parent0_.name as col_1_0_,
parent0_.child1_property as col_2_0_
from
parent parent0_
where
parent0_.type='child1'
Hibernate User Guide excerpt:
There is a particular expression type that is only valid in the select clause. Hibernate calls this "dynamic instantiation". JPQL supports some of that feature and calls it a "constructor expression".
So rather than dealing with the Object[] (again, see Hibernate Query API) here, we are wrapping the values in a type-safe Java object that will be returned as the results of the query.
...
The projection class must be fully qualified in the entity query, and it must define a matching constructor.
The class here need not be mapped. It can be a DTO class.
If it does represent an entity, the resulting instances are returned in the NEW state (not managed!).
However, I'd just SELECT c FROM Child1 c. I was actually surprised that Hibernate didn't complain about the attribute child1Property missing for the entity Parent.

Retrieving #NamedQuery from interface of JPA Entity

I have a JPA 2.1 project, database has 2 tables. I have created an interface "UsedClasses.java" and have both Entities implement the interface., i.e.
#Entity
#Table(name = "runRanges")
#NamedQuery(name = "RunRange.findAll", query = "SELECT r FROM RunRange r")
public class RunRange extends AbstractTimestampEntity implements UsedClasses, Serializable ....
and
#Entity
#Table(name = "users")
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u")
public class User extends AbstractTimestampEntity implements UsedClasses, Serializable ...
I am curious to know if I can use the interface to grab the NamedQuery for either of the Entities.
What I am trying to accomplish if getting the results list according to the class.
public List<UsedClasses> getAllItems() {
open();
Query query = EntityManagerHandler.INSTANCE.getEntityManager().createQuery(UsedClasses.class.getResource(NamedQueries));
List<UsedClasses> aList = query.getResultList();
return aList;
}
You can do it but you should use reflection to retrieve the NamedQuery annotation and you would need to have the UsedClasses instance and not the interface like in your example :
Query query = EntityManagerHandler.INSTANCE.getEntityManager().createQuery(UsedClasses.class.getResource(NamedQueries));
Besides, if you add multiple annotations, it will not work any longer.
I think that a more simple and clean solution would be to add a method in your interface to get the namedQuery String name.
For example
public interface UsedClasses(){
String getNamedQueryForGetAll();
}
And you could implement it in this way :
#Entity
#Table(name = "users")
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u")
public class User implements UsedClasses(){
public String getNamedQueryForGetAll(){
return "User.findAll";
}
}
Then you can use it in this way :
Query query = EntityManagerHandler.INSTANCE.getEntityManager().createQuery(usedClassInstance.getNamedQueryForGetAll());
Now, I find that it creates a little of duplication and it is not straight readable.
NamedQuery brings a extremely tiny performance improvement.
I am not sure that the complexity/indirection level introduced in the code to gain so few in terms of execution time makes you winning.

JPA 2.0 / Hibernate InheritanceType TABLE_PER_CLASS and OneToMany/ManyToOne bidirectional relation

We have 2 tables (an active table and an archive table) which have the same structure (ex. Employee and EmployeeArchive). To be able to leverage common code to use results for both tables we have an abstract parent class that defines all the methods and annotations.
We like to be able to perform queries that will use the same query for both tables and union the results together.
We have another entity/table (ex. Organization) with a onetomany/manytoone bidirectional relationship with Employee; Organization has a List of Employees and every employee has an organization.
When getting the employees of an organization via the association we only want the employees from the active table not the archive.
Is there a way to achieve what we are attempting or a viable workaround?
We have tried various implementations of #MappedSuperclass, #Entity/#InheritanceType.TABLE_PER_CLASS to try to achieve what we want. Each implementation would nearly achieve what we want but not completely. For example to be able to query both tables we could have an abstract parent Entity with InheritanceType.TABLE_PER_CLASS but then we could not have the mappedBy relationship to Employee in the Organization. We can use a MappedSuperclass as the parent to be able to have the correct relationship but then we cannot query both the Archive and Active tables via the union.
Here is basically what we are trying to layout:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractEmployee {
#ManyToOne
#JoinColumn(name="employeeId", nullable=false)
Organization org;
...
}
#Entity
public class Employee extends AbstractEmployee {
}
#Entity
public class EmployeeArchive extends AbstractEmployee {
}
#Entity
public class Organization {
#OneToMany(cascade=ALL, mappedBy="org")
List<Employee> employees;
...
}
Code
public List<AbstractEmployee> getAllEmployees()
{
Query query = em.createQuery("SELECT e FROM AbstractEmployee e where e.name = ‘John’", AbstractEmployee.class);
return query.getResultList();
}
public List<Organization> getOrganizations()
{
Query query = em.createQuery("SELECT e FROM Organization o ", Organization.class);
List<Organization> orgs = query.getResultList();
// fetch or eager fetch the Employees but only get the ones from the active employee table
return orgs;
}
We also tried to have the parent class extend the MappedSuperclass and put the implementation and annotations in the MappedSuperclass but we get an AnnotationException for the relationship of the Organization
#MappedSuperclass
public abstract class AbstractMapped {
#ManyToOne
#JoinColumn(name="employeeId", nullable=false)
Organization org;
}
#Entity
#Inheritance(#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS))
public abstract class AbstractEmployee extends AbstractMapped {
... `Constructors` ...
}
On deployment we get the following exception:
Caused by org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: Employee.org in Organizaztion.employees
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:685)
You can do this by changing the mapping of Organization to Employee so that it uses a relationship table, rather than having the org field in the Employee table. See the example in the Hibernate documentation, which for you would look something like:
#Entity
public class Organization {
#OneToMany(cascade=ALL)
#JoinTable(
name="ACTIVE_EMPLOYEES",
joinColumns = #JoinColumn( name="ORGANIZATION_ID"),
inverseJoinColumns = #JoinColumn( name="EMPLOYEE_ID")
)
List<Employee> employees;
...
}
However, I have to say that I think having two tables to represent current vs archived Employees is a bad idea. This sounds to me like a 'soft delete' kind of situation, which is better handled with an in-table flag (IS_ACTIVE, or something). Then you don't have these odd abstract classes to do your queries, multiple tables with the same kind of data, etc etc. A bit of a description of this strategy is here.
Then you can use the non-join table mapping that you've already got, and use the #Where annotation to limit the employees in an organization to ones that have IS_ACTIVE set to true. An example of this approach is here.
This is one of the annoying things about hibernate. The way to do this is to have another abstract class, AbstractMapped, which simply looks like this:
#MappedSuperclass
public abstract class AbstractMapped {
}
and then have AbstractEmployee extend AbstractMapped. Then you have AbstractEmployee as both an Entity and a Mapped Superclass, even though the two tags are mutually exclusive.
AbstractEmployee should be the #MappedSuperClass, and should not be an #Entity, which creates a table for the class.
Organization should contain a List<AbstractEmployee> not of Employee.

Eclipselink lazy loading issue for objects with discriminator column

We have following hierarchy in our application:
#MappedSuperclass
public abstract class AbstractDemandOrMeasureBE {
}
#Entity
#Inheritance
#DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.INTEGER)
#Table(name = "V_VIEW2")
public abstract class AbstractDemandOrConcreteMeasureBE extends AbstractDemandOrMeasureBE {
#Column(name = "VC_ID")
private Long vcId;
}
#Entity
#DiscriminatorValue("2")
public class MinimalDemandBE extends AbstractDemandOrConcreteMeasureBE {
..
}
#Entity
#DiscriminatorValue("1")
#HasRelationsAnnotatedAsLazyLoaded
public class ValidationMeasureBE extends AbstractDemandOrConcreteMeasureBE {
..
}
In other object I am trying to load those entities like that:
#Table(name = "V_VIEW2")
public class VCBE extends SomeVeryAbstractBE {
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "VC_ID")
private List<ValidationMeasureBE> validationMeasures;
public transient static final String ATTRIBUTE_VALIDATION_MEASURES = "validationMeasures";
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "VC_ID")
private List<MinimalDemandBE> minimalDemands;
public transient static final String ATTRIBUTE_MINIMAL_DEMANDS = "minimalDemands";
There is a precompiled query to load all hierarchy, which load some other parent objects. There is also a hint for the query - eclipselink.left-join-fetch=PP.VCBE.validationMeasures (if this is changed to eclipselink.left-join-fetch=PP.VCBE.minimalDemands, then minimal demands are loaded, but validation measures (entries with discriminator 1) are also loaded into the minimal demands collection - but those should not be loaded).
Now, when query is executed validationMeasures collection if filled with objects, but all those object are actually minimal demands and have 2 as a discriminator value in the database.
The query, which gets executed is following:
SELECT * FROM V_VIEW1 t1
LEFT OUTER JOIN V_VIEW0 t0 ON (t0.PP_D = t1.ID)
LEFT OUTER JOIN V_VIEW2 t2 ON (t2.VC_ID = t0.ID)
WHERE (((t1.ID = ?) AND (t1.HP_ID = ?))
AND t1.HP_IS IN (SELECT t3.ID FROM V_VIEW t3 WHERE (t3.HWPG_ID = ?)))
bind => [3 parameters bound]
As I can see there is no DISCRIMINATOR constraint in the query, why?
Any ideas of such a behavior? And how can I tell eclipselink to load collection, depending on discriminator value?
Can you include the JPQL query and hints you use to get this SQL.
So, you are saying it works when you use a join-fetch, but not a left-join-fetch?
This seems like it may be a bug, that the inheritance discriminator expression is not being included when using an outer join. If this is the case, please log a bug for this and vote for it.
Your model is very odd though. Why split the two subclasses into two separate relationships? Having one would be much more efficient. Or if you do split them, you should be using different foreign keys, not the same one. Sharing the same foreign key for two different relationships is probably not a good idea.

How to map an abstract class or interface in Hibernate HQL?

Imagine that I have a Debtor class. With Hibernate, I will define the class like that:
#Entity
#Table(name = "T_DEBTOR")
public class Debtor {
#Id
#Column(name = "ID_DEBTOR")
private String idDebtor;
...
My DAO will then looks like:
public class DebtorDaoImpl implements DebtorDao {
#PersistenceContext
private EntityManager em;
#SuppressWarnings("unchecked")
public List<Debtor> findAllDebtors() {
Query q = em.createQuery("select d from Debtor d");
return (List<Debtor>) q.getResultList();
}
This works well. However, I am in a configuration where I need to access differents schemas (as pointed here). Of course, in each schema the table that hosts the debtor list has not the same name. In addition to that, they may not have the exact same structure. That why I have x differents Debtor class (where x is the number of schemas I manipulate).
In the case where I have two differents schemas, I will have two different Debtor class: DebtorOne and DebtorTwo.
As I want to ease my developments, I created an Interface (or an Abstract class, it does not change my problem here) that is implemented by both DebtorOne and DebtorTwo:
public interface Debtor {
String getIdDebtor();
}
and:
#Entity
#Table(name = "T_DEBTOR_ONE")
public class DebtorOne implements Debtor {
#Id
#Column(name = "ID_DEBTOR")
private String idDebtor;
...
If I let my DAO as it is, I get the following error from Hibernate:
Caused by: org.hibernate.hql.ast.QuerySyntaxException: Debtor is not mapped [select d from Debtor d]
If I change my DAO to have this:
public List<Debtor> findAllDebtors() {
Query q = em.createQuery("select d from DebtorOne d");
return (List<Debtor>) q.getResultList();
}
then it works, but it is specific to DebtorOne schema...
One solution I see is to define a named query on the DebtorOne and DebtorTwo classes and call this named query from my DAO.
In others words:
#Entity
#Table(name = "T_DEBTOR_ONE")
#NamedNativeQueries( { #NamedNativeQuery(name = "findAllDebtors", query = "select d from DebtorOne d") })
public class DebtorOne implements Debtor {
and in the DAO:
#SuppressWarnings("unchecked")
public List<Debtor> findAllDebtors() {
Query q = em.createNamedQuery("findAllDebtors");
return (List<Debtor>) q.getResultList();
}
I didn't try it yet, but I think it will work...
EDIT I just tried, this will work... except that the NamedQuery must be named differently for DebtorOne and DebtorTwo...
However, I am wondering if there is a way to solve my problem without using the latter solution?
Edit regarding the first answer, which suggests to use the #MappedSuperclass. This annotation seems to be the perfect solution for me, but I think I forgot something, as I still get the same error.
The main Debtor:
#MappedSuperclass
public class Debtor {
#Id
#Column(name = "IDDEBTOR")
protected String idDebtor; // With getter and setter
}
one of the extended Debtor class:
#Entity
#Table(name = "DEBTOR_ONE")
public class DebtorOne extends Debtor {
...
and in my DAO:
public List<Debtor> findAllDebtors() {
return (List<Debtor>) em.createQuery("select d from Debtor d").getResultList();
}
is still returning me the error Caused by: org.hibernate.hql.ast.QuerySyntaxException: Debtor is not mapped [select d from Debtor d]
What did I miss this time?
I think this is not possible with an interface, but only with a common abstract base class, that will be annotated with #MappedSuperclass (see Hibernate documentation for further details)
I think for this to work you'd either have to actually map the Debtor table to a table or use the table per class strategy (union-subclass). #MappedSuperClass only seems to implement a very rudimentary mechanism to copy properties and wouldn't work because you can't query for instances of the superclass.
I take it from the link that you already have something in place to avoid mapping DebtorTwo in the hibernate session for the schema of DebtorOne (otherwise querying Debtor would pull in all records, including ones from the DebtorTwo table that wouldn't exist). In that case, follow the example from the documentation to map the subclass for either case.

Categories