Hibernate (JPA) inheritance mapping of abstract super classes - java

My data model represents legal entities, such as a Business or a Person. Both are tax-paying entities, and both have a TaxID, a collection of phone numbers, and a collection of mailing addresses.
I have a Java model with two concrete classes that extend an abstract class. The abstract class has properties and collections that are common to both concrete classes.
AbstractLegalEntity ConcreteBusinessEntity ConcretePersonEntity
------------------- ---------------------- --------------------
Set<Phone> phones String name String first
Set<Address> addresses BusinessType type String last
String taxId String middle
Address Phone
------- -----
AbsractLegalEntity owner AbstractLegalEntity owner
String street1 String number
String street2
String city
String state
String zip
I'm using Hibernate JPA Annotations on a MySQL database, with classes like this:
#MappedSuperclass
public abstract class AbstractLegalEntity {
private Long id; // Getter annotated with #Id #Generated
private Set<Phone> phones = new HashSet<Phone>(); // #OneToMany
private Set<Address> address = new HashSet<Address>(); // #OneToMany
private String taxId;
}
#Entity
public class ConcretePersonEntity extends AbstractLegalEntity {
private String first;
private String last;
private String middle;
}
#Entity
public class Phone {
private AbstractLegalEntity owner; // Getter annotated #ManyToOne #JoinColumn
private Long id;
private String number;
}
The problem is that Phone and Address objects need to refer to their owner, which is an AbstractLegalEntity. Hibernate complains:
#OneToOne or #ManyToOne on Phone references an unknown
entity: AbstractLegalEntity
It seems like this would be a fairly common Java inheritance scenario, so I hope that Hibernate would support it. I've tried changing the mapping for AbstractLegalEntity based on a Hibernate forum question, no longer using #MappedSuperclass:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
However, now I get the following error. When reading up on this inheritance mapping type, it looks like I have to use SEQUENCE not IDENTITY, and MySQL doesn't support SEQUENCE.
Cannot use identity column key generation with <union-subclass>
mapping for: ConcreteBusinessEntity
I'm making more progress toward getting things working when I use the following mapping.
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name="entitytype",
discriminatorType=DiscriminatorType.STRING
)
I'm thinking I should continue down this path. My concern is that I'm mapping it as an #Entity when I really don't ever want an instance of AbstractLegalEntity to ever exist. I'd like to know if this is the right approach. What is the correct approach I should be taking for this situation?

Use:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
AbstractLegalEntity
In the database you will have one table for AbstractLegalEntity, and tables for classes, which extend AbstractLegalEntity class. You won't have instances of AbstractLegalEntity if it's abstract. Polymorphism can be used here.
When you use:
#MappedSuperclass
AbstractLegalEntity
#Entity
ConcretePersonEntity extends AbstractLegalEntity
This will create only one table in your database called ConcretePersonEntity, containing columns from both classes.

Add #Entity annotation to AbstractLegalEntity. Instance of AbstractLegalEntity will never exist - hibernate will load appropriate extending instances - ConcreteBusinessEntity or ConcretePersonEntity according to Id field.

You have to declare AbstracLegalEntity as an #Entity. Even with the #Entity annotation, your class remains abstract. consequently, you will only have instance of concrete subclasses.

Related

Relationship table mapped as entity in JPA

I'm trying to map one specific many to many table on my database as an entity in JPA (cause I have some specific attributes on my relationship table and I wanted to retrieve this as the class attributes two). But having issues while declaring the IDs.
#Data
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Entity
#Table(name = "user_plan")
public class UserPlan implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#OneToOne
private User user;
#Id
#OneToOne
private Plan plan;
private Integer billingDay;
#Enumerated(EnumType.STRING)
private BillingType billingType;
#Enumerated(EnumType.STRING)
private PlanStatus planStatus;
}
The application starts successfully but when I try to map some repository to manage this table, Hibernate throws an error:
java.lang.IllegalArgumentException: This class [class com.demo.domain.model.UserPlan] does not define an IdClass
How can I use the JPA entity annotation to manage this relationship table? Is it possible?
I cannot simply declare one property in the user class of Plan model and mark it as #ManyToMany, cause the plan table does not have the property that I need to execute some operations, which are declared on UserPlan class, also I cannot move these properties to Plan class, cause the Plan table is just a template of a plan, and the UserPlan have all the specific data (billingDay, billingType and planStatus).
JPA supports relationship tables as a Java class? Or it can be mapped only as a property?
Thanks
You are using multiple #Id annotations. To do so you need to create PrimaryKey class:
public class PrimaryKey implements Serializable {
private User user;
private Plan plan;
// Getter and Setter
}
And you need to add #IdClass(PrimaryKey.class) annotation to your entity class.
If you have a Repository don't forget to change id type to PrimaryKey:
public interface YourRepository
extends SomeRepositoryInterface<UserPlan, PrimaryKey> {
//...
}
Also check this question

Spring-Data Jpa Inheritance: Keeping Entity Id's in Children Entity

I'm dealing with a couple of Entities with Tree like structures that were getting more complicated so I decided to create an abstract class for it so code was a bit more mainainable:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class TreeStructure<T extends TreeStructure>
{
#ManyToOne
protected T parent;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
protected Set<T> children = new HashSet<>();
//...
Then I have two Entities which extend it:
#Entity(name = "TreeStructureOne")
public class TreeStructureOne extends TreeStructure<TreeStructureOne>
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonProperty("TreeStructureOne_id")
private long id;
And I basically want the database to be completely unaware of this TreeStructure abstraction and save all of the fields in each Entities tableand expected InheritanceType.TABLE_PER_CLASS to deal with that. But it seems I need to define the Id in the TreeStructure Entity at least or I get:
Invocation of init method failed; nested exception is org.hibernate.AnnotationException: No identifier specified for entity: TreeStructure
And I don't want to add an ID into the abstract class since this makes three tables in the database called: HT_TREE_STRUCTURE, HT_TREE_STRUCTURE_ONE and HT_TREE_STRUCTURE_TWO with one field ID each one.
Is there any solution to that?
Since TreeStructure is not an #Entity use only #MappedSuperclass
#MappedSuperclass
public abstract class TreeStructure<T extends TreeStructure> {
instead of #Entity and #Inheritance for the parent class.
You can find #MappedSuperclass in the Oracle JEE API documentation.

Is there a way of inheriting Hibernate's #Where annotation?

We have a DB table user that evolved quite a bit and we don't want to load legacy users into the app. Legacy user is identified by user_type column.
If I use following mapping then everything works as expected:
#Entity
#Table(name="user")
#Where("user_type = 2") // 1 is legacy
class User {
#Column(name="user_type")
int type;
}
I need to map user table multiple times and I want to stay DRY. So I thought I can extract #Where bit to a super class and inherit it like so:
#Where("type = 2") // 1 is legacy
abstract class BaseUser {
}
#Entity
#Table(name="user")
class User extends BaseUser {
}
I have a following test (I hope it's self-explanatory enough) that fails though:
#Test
#DbUnitData("legacy_user.xml") // populates DB with 1 user (id=1) with type=1
public void shouldNotGetLegacyUser() {
assertThat(em.find(User.class, 1L)).isNull();
}
Is there a way of inheriting a class with Hibernate's #Where annotation?
What you are really looking for is not the #Where but the #DiscriminatorColumn and #DiscriminatorValue. These annotations allow you to map two #Entity objects to the same table based on a #DiscriminatorColumn.
The Hibernate manual has a paragraph on it:
Mapping inheritance
You would basically create a superclass, BaseUser and two Sub classes, LegacyUser and User:
#Entity
#Table(name = "COM_ORDER")
#DiscriminatorColumn(name = "COM_ORDER_TYPE", discriminatorType = DiscriminatorType.INTEGER)
public class BaseUser {
#Id
private Long id;
<Enter your generic columns here, you do not need to add the user_type column>
}
#Entity
#DiscriminatorValue("1")
public class LegacyUser extends BaseUser {
<Enter your legacy specific fields here>
}
#Entity
#DiscriminatorValue("2")
public class LatestUser extends BaseUser {
<Enter your new and improved user fields here>
}
With this setup, you can easliy expand the number of user types by creating new classes which extend the BaseUser class. You need to keep in mind that the fields on the actual table can only be not-null for fields in the BaseUser class. Fields in the UserType related classes should always be nullable in the database since they will only ever be used by a specific user type.
Edit:
I've edit the example to conform to the setup I'm currently using in my own project. This setup works fine for me.

JPA - Assign different unique constrains for two subclass of a SINGLE_TABLE hierarchy abstract class

I have 3 classes that are:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public abstract class Tag {
#Id
private String id;
private String name;
}
#Entity
#Table(uniqueConstraints=
#UniqueConstraint(columnNames={"name"}))
public class SystemTag extends Tag {
}
#Entity
#Table(uniqueConstraints=
#UniqueConstraint(columnNames = {"name", "user"}))
public class CustomTag extends Tag{
#ManyToOne
private User user;
}
so I want to use unique name for system tags, and unique name-user pair for custom tags (multiple users can create same tags) But I get two warnings as below:
<timestamp> WARN AnnotationBinder:601 - HHH000139: Illegal use of #Table in a subclass of a SINGLE_TABLE hierarchy: <domain>.CustomTag
<timestamp> WARN AnnotationBinder:601 - HHH000139: Illegal use of #Table in a subclass of a SINGLE_TABLE hierarchy: <domain>.SystemTag
And it allows me to create two system tags with same name and two custom tags with same name for same user.
How can I handle this?
If you are using a single table that is obviously not going to work.
Switch to using JOINED strategy
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public abstract class Tag {
#Id
private String id;
private String name;
}
and you will then have a table for CustomTag and table for SystemTag with unique constraints as expected.

Mapping Multiple Classes to a Table in Hibernate, Without a DTYPE Column

I have two hibernate classes: a base class, and an extended class that has additional fields. (These fields are mapped by other tables.)
For example, I have:
#Entity
#Table(name="Book")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
public class B extends A {
public String node_ID;
// ...
}
public class Node {
public String ID; // maps to B.node_ID
// ...
}
How do I map this in Hibernate? The hibernate documentation states three types of inheritence configurations: one table per class, one table with a type column, and a join table -- none of which apply here.
The reason I need to do this is because class A is from generic framework that's reused over multiple projects, and class B (and Node) are extensions specific to one project -- they won't be used again. In the future, I may have perhaps a class C with a house_ID or some other field.
Edit: If I try the above pseudo-code configuration (two entities mapped to the same table) I get an error that the DTYPE column doesn't exist. The HQL has a "where DTYPE="A" appended.
This is possible by mapping the #DiscriminatorColumn and #DiscriminatorValue to the same values for both classes; this can be from any column you use that has the same data regardless of which type (not sure if it works with null values).
The classes should look like so:
#Entity
#Table(name="Book")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="published")
#DiscriminatorValue(value="true")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
#DiscriminatorValue(value="true")
public class B extends A {
public String node_ID;
// ...
}
For anyone who got here like me and does not want to have the dtype column but instead want to use the same table for more than one entity as is I would recommend using this
Basically you can create a Base like this
#MappedSuperclass
public abstract class BaseBook<T extends BaseBook> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
... any other variables, getters + setters
}
#Entity
#Table(name= "book")
public class BookA extends BaseBook<BookA>{
//Default class no need to specify any variables or getters/setters
}
#Entity
#Table(name= "book")
public class BookB extends BaseBook<BookB>{
#Column(name = "other_field")
private String otherFieldInTableButNotMapedInBase
... Any other fields, getter/setter
}
From the above we have created base super class which does not have any entity or table mapping. We then create BookA to be default with the Entity + Table mapping. From there we can create other Entities all extending from BaseBook but pointing to one table

Categories