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.
Related
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.
Group by entities is possible in HQL, but it seems that by entities mapped with joined inheritance doesn't.
select a, count(r) from Root r join r.a a group by a
Executing the above query results in a sql with insufficient columns in group by:
select
suba1_.id as col_0_0_,
count(root0_.id) as col_1_0_,
suba1_.id as id1_12_,
suba1_1_.super_data as super_da2_12_,
suba1_.sub_data as sub_data1_10_
from root root0_
inner join suba suba1_ on root0_.a_id=suba1_.id
inner join supera suba1_1_ on suba1_.id=suba1_1_.id
group by suba1_.id
Which gives the following error message:
o.h.engine.jdbc.spi.SqlExceptionHelper: expression not in aggregate or GROUP BY columns: SUPERA1_.SUPER_DATA in statement
The entities are described as followed:
#Entity
public class Root {
private #Id Integer id;
#ManyToOne
private SubA a;
}
#Entity
public class SubA extends SuperA {
private String subData;
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class SuperA {
private #Id Long id;
private String superData;
}
Also if changed the type of Root::a to SuperA, the sql generated by the hql will have the same problem.
So is group by entities with joined inheritance type possible or am I missing something?
PS. This hql query does work in case if SuperA is mapped with table per class inheritance type but then fails with the same problem if type of Root::a is changed to SuperA as well.
Hibernate version: org.hibernate:hibernate-core:jar:5.4.32.Final:compile
Yes, with a bit of trickery, to anwser my own question. The missing column for the group by in the sql is the column id of SuperA, so the solution would be to include it. I found that possible by mapping the superclass's id with an extra column and reference it in the group by along side the entity.
So, in SuperA an extra column mapping superId would be added:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class SuperA {
private #Id Long id;
#Column(name="id", updatable = false, insertable = false)
private Long superId;
private String superData;
}
And referenced in the group by
select a, count(r) from Root r join r.a a group by a, a.superId
This solution was tested only for HSQLDB and PostgreSQL.
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.
I'm working on a JPA project in which I have some different Entities extending a super-class annotated as Entity:
#Entity
#Table(name = "export_profiles")
#NamedQueries({
#NamedQuery(name = "ExportProfile.getAll", query = "select ep from PersistentExportProfile ep"),
#NamedQuery(name = "ExportProfile.getByName", query = "select ep from PersistentExportProfile ep where ep.profileName = :name") })
public abstract class PersistentExportProfile extends AbstractExportProfile {
// other mappings...
}
I'd like to inherit mappings defined in my PersistentExportProfile into each sub-class.
Is it possible? What do I have to change in my super-class, and what do I have to add in my sub-entities?
NOTE all the sub-classes will be mapped on the same table.
If the sole purpose of your superclass is to define common mappings for the sub classes, but is not persistent itself, you would be better off using the #MappedSuperclass annotation or the <mapped-superclass> for xml mappings. There is an example here.
This case may be a good start in Postgres.
By example
CREATE TABLE "public"."abstract_export_profile" (
"label" TEXT
) WITHOUT OIDS;
CREATE TABLE "public"."persistent_export_profile" (
"id" BIGSERIAL,
"value" TEXT,
CONSTRAINT "mandant_pkey" PRIMARY KEY("id")
) INHERITS ("public"."abstract_export_profile")
WITHOUT OIDS;
The Name Class:
#MappedSuperclass
public class AbstractExportProfile { ... }
And the
#Entity
#Table(name= "mandant")
public class PersistentExportProfile extends AbstractExportProfile { ... }
The best solution for me was to add #Inheritance(strategy=InheritanceType.SINGLE_TABLE) and #DiscriminatorColumn(name="export_type", discriminatorType=DiscriminatorType.STRING) to my abstract class, then in my concrete classes I added #DiscriminatorValue to define the value of the DiscriminatorColumn.
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.