I have mapped my inheritance hierarchy in Hibernate using InheritanceType.Single_Table and discriminator columns to distinguish between the different entities. All subclasses of the superclass store their fields into secondary tables. As an example:
#MappedSuperclass
public abstract class Base
{
#Id
private String id;
#Version
private long version;
}
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public class Parent extends Base
{
#Column(nullable=false)
private BigDecimal value;
}
#Entity
#DiscriminatorValue("child1")
#SecondaryTable(name = "Child1")
public class Child1 extends Parent
{
#Column(table="Child1")
private String name;
}
#Entity
#DiscriminatorValue("child2")
#SecondaryTable(name = "Child2")
public class Child2 extends Parent
{
#Column(table="Child2")
private String name2;
}
I now have an Entity that has a #OneToOne relationship with the Parent class. This Entity only needs to work with the value field from the Parent class. It will never need to work with any fields from any subclass of Parent
#Entity
public class AnotherEntity extends Base
{
#JoinColumn(name="parentId")
#OneToOne(fetch=FetchType.Lazy, optional=true, targetEntity=Parent.class)
private Parent parent;
}
What I want to happen is that only the fields of Parent.class are selected when the relationship to parent is loaded from the database. What I'm seeing is that Hibernate attempts to load all properties of the entities that extend Parent. It also left joins all of the Secondary tables. This is problematic as I have rougly 30 entities that extend Parent. This makes fetching the Parent entity non-viable as the query performs 30 joins.
As an example, this is the type of query I am seeing:
Hibernate:
select
parent.id as id3_0_,
parent_.version as version3_0_,
parent.name1 as name110_3_0_,
parent.name2 as name24_3_0_,
parent.type as type3_0_
from
Parent parent0_
left outer join
Child1 parent0_2_
on parent0_.id=parent0_2_.id
left outer join
Child2 parent0_3_
on parent0_.id=parent0_3_.id
I don't understand why Hibernate decides to select a superset of all properties defined in the subclasses of Parent and join all of the secondary tables? I could understand it joining the secondary table for entity defined by the discriminator value of the parent being referenced, but otherwise I am confused.
My question is, how do I go about achieving my requirement of only having the fields from the Parent class loaded when I retrieve the Parent relationship in the AnotherEntity class?
Thanks.
A secondary table is normally used to map the content of a single entity to two tables. It doesn't allow for lazy/select fetching using standard JPA annotations. You may use a proprietary Hibernate annotation to load it using a separate select, and only if necessary, though. See http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/#mapping-declaration-join:
fetch: If set to JOIN, the default, Hibernate will use an inner join
to retrieve a secondary table defined by a class or its superclasses
and an outer join for a secondary table defined by a subclass. If set
to SELECT then Hibernate will use a sequential select for a secondary
table defined on a subclass, which will be issued only if a row turns
out to represent an instance of the subclass. Inner joins will still
be used to retrieve a secondary defined by the class and its
superclasses.
So setting the fetch attribute of the Hibernate #Table annotation to SELECT will do what you want : an additional select clause will be issued to select the values from just the appropriate secondary table.
If you want lazy fetching, then a secondary table is not what you want. You'll have to do it using associations.
Related
I have a model like this:
class Group {
#Id
#GeneratedValue
Long id
#OneToMany()
#JoinColumn()
List<Base> baseClasses;
}
This is the base class:
#MappedSuperclass //tried to add strategy as well table per super class as well but it does not work
abstract class Base{
//some fields here
}
The base class has two levels of children:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
class FirstLevel extends Base{
#Id
#GeneratedValue
Long id
//some fields here
}
#Entity
class SecondLevel extends FirstLevel{
#Id
#GeneratedValue
Long id
//some fields here
}
I am using spring data (hibernate). What I would like to achieve is map this class hierarchy into database structure like this:
table Group which has one to many reference to specific base class
specific base class - SecondLevel table
I would like to map this two level of Base hierarchy into one table which next will be accessible through #OneToMany mapping inside group table.
When I have #MappedSuperclass I am getting the following error:
is org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany
targeting an unmapped class:
When I replace MappedSuperclass with Inheritance strategy -> TABLE_PER_CLASS
Hibernate requires two tables - FirstLevel and SecondLevel which I dont want.
To sum up - How do I map polymorphic relation oneToMany to multiple level of inheritance hierarchy ?
I have and issue with searching in entities that are extended from #MappedSuperclass. I created a class PhoneBook and extended 2 entities from it: FirstPhoneBook and SecondPhoneBook. The structure looks the following:
#MappedSuperclass
public abstract class PhoneBook {
...
#Entity
#Table(name = "first_phone_book")
public class FirstPhoneBook extends PhoneBook {
...
#Entity
#Table(name = "second_phone_book")
public class SecondPhoneBook extends PhoneBook {
...
These tables are absolutely similar. I discribe all fields in PhoneBook class, childs have only default constructor in it. External system sends a phone number as a parameter. Depending on whether tables contain such number or not my system responds with a word.
The question is: how can I search separately in each table that is extended from #MappedSuperclass without hardcoding each child class name?
I could only find variants of search by value like that:
currentSession.get(Employee.class, theId);
but there is explicit call to entity class. I want this to be extendable without need to write new DAO for each new entity added. Current method signature looks the following:
public <T extends PhoneBook> T findByNumber(String number);
What you describe is polymorphic queries, i.e. queries that reference the parent class. The Hibernate documentation says this is not well supported when using #MappedSuperclass inheritance:
Because the #MappedSuperclass inheritance model is not mirrored at the database level, it’s not possible to use polymorphic queries referencing the #MappedSuperclass when fetching persistent objects by their base class.
If polymorphic queries are frequently used, it's better to use the table per class inheritance strategy:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class PhoneBook {
...
#Entity
#Table(name = "first_phone_book")
public class FirstPhoneBook extends PhoneBook {
...
#Entity
#Table(name = "second_phone_book")
public class SecondPhoneBook extends PhoneBook {
...
You can then fetch an entity using the superclass:
PhoneBook phoneBook = currentSession.get(PhoneBook.class, theId);
and Hibernate would typically use a UNION to do the query with both tables.
This being said, even with #MapperSuperclass, Hibernate can still query all tables for classes that extend the parent class. You can use the following JPA query (note that it uses the fully qualified class name of the parent class):
Query<PhoneBook> query = currentSession.createQuery("from " + PhoneBook.class.getName() +
" where id = :id", PhoneBook.class);
query.setParameter("id", theId);
The difference is that here it's not querying an entity, but just all classes that extend a parent class. Also in this case, unlike with the table-per-class strategy, Hibernate will not use a UNION, but send a query to each table, in this case two separate SQL queries instead of one.
#OneToMany annotation, by default, creates a join table, unless the mappedBy element is specified.
What is the reason for this behaviour? For example, with the following entities:
#Entity
public class User {
// ...
#OneToMany
private List<UserDocument> documents;
// ...
}
#Entity
public class UserDocument {
// ...
#ManyToOne
private User user;
// ...
}
For the User entity, why doesn't Hibernate simply:
Find the field with type User in UserDocument by doing reflection on UserDocument entity.
Infer the value of mappedBy for the #OneToMany annotation by itself?
What is the reason for not doing this and generating a join table as the default behaviour? Why is Hibernate (or JPA) is designed this way?
A simple reason behind this is that Hibernate cannot known for sure that a filed of type User inside of UserDocument is corresponding to the specific User-UserDocument relation. Without a mappedBy property, Hibernate can only create a join table or insert a generated column in UserDocument table. However, the latter alters data model and introduces more problem than it may resolve ( distinguish generated or declared column; table schema mismatch model class; etc.). Thus Hibernate use a join table to store the mapping.
For example, if you want to track the last one who modifies a document, you may need another many-to-one relation in UserDocument. This cannot be infered and resolved just using reflection.
#Entity
public class UserDocument {
// ...
#ManyToOne
private User user;
#ManyToOne
private User lastModifiedBy;
// ...
}
A possible answer to my question is located here:
How can I retrieve the foreign key from a JPA ManyToOne mapping without hitting the target table?
However, the preferable solution (property access) does not work in my case (I got missing column exception - why?)
The model looks like this: Entities Parent and Child. Table parent has column child_id which is PK of child table so it is typical #ManyToOne relation.
Now the point is, if I fetch Parent entities, I need to have access to FK value (aka. PK of Child entity) without fetching Child entities. How can I do that?
I use Annotations and my mapping looks as follows:
#Entity
#Table(name = "parent")
public class Parent extends AbstractEntity {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "patent_id", nullable = true)
private Child child;
#Column(name="child_id",insertable=false,updatable=false)
private Integer childId;
public Child getChild() {
return patent;
}
public void setChild(Child child) {
this.child = child;
}
public Integer getChildId(){
return childId;
}
}
And what I want to do is call parent.getChild().getId() without extra fetches of Child entity from DB.
According to the answer I have mentioned above, If I moved annotations from field to getter (in Parent entity am I right?), requested behavior would be out of the box. However, when I move annotations to getter, I get a validation exception that child column is missing (curious, why child not child_id as declared?)
PS: Shown workaround to declare a FK column as separate field works fine, but I don't think that this is the way it should be done.
OK, after reading following article http://256stuff.com/gray/docs/misc/hibernate_lazy_field_access_annotations.shtml
I have realized, that property access should be to the property I want to fetch, not the actual child object. So changing id access of AbstractEntityfrom field to property, makes the trick.
I have a table defined as this:
#Entity
#Table
#Inheritance(strategy=InheritanceType.JOINED)
public class Table implements Serializable {
#Id
#GeneratedValue
private Long id;
...
}
Then I have an inherited table:
#Entity
#Table
public class SubTable extends Table {
...
}
Hibernate correctly creates two table in my Postgres database but it defines on-delete action between the two tables as "NO ACTION".
How can I define in Hibernate, that I want CASCADE action to be defined on delete? For example when I manually delete a row from table Table I want to automatically get the corresponding row in table SubTable to be deleted. When I try to delete a row from Table now, it returns me foreign key constraint violation error.
Hibernate can take care of the cascade deletes if you always use it to perform the deletes.
So removing a SubTable entity will succeed and it will remove both the subclass table record and the associated base class row as well.
If you want to use SQL level deletes, assuming you are using hbm2ddl (which you shouldn't do since you should use Flyway), then you need to annotate the SubTable with the #OnDelete annotation:
#Entity
#Table
#OnDelete(action = OnDeleteAction.CASCADE)
public class SubTable extends Table {
...
}