📜 ⬆️ ⬇️

Hibernate Inheritance: Choosing a Strategy

Inheritance is one of the basic principles of OOP. At the same time, a significant number of enterprise applications are based on relational databases.

The main contradiction between the object-oriented and relational models is that the object model supports two kinds of relationships (“is a” means “is” and “has a” means “has”), while models based on SQL support only relations Has a.

In other words, SQL does not understand type inheritance and does not support it .
')
Therefore, at the stage of constructing entities and database schema, one of the main tasks of the developer will be the selection of the optimal strategy for representing the inheritance hierarchy.

There are 4 such strategies in total:

1) Use one table for each class and polymorphic default behavior.

2) One table for each specific class, with complete exclusion of polymorphism and inheritance relationships from the SQL schema (for polymorphic behavior at runtime, UNION queries will be used)

3) A single table for the entire hierarchy of classes. Possible only by denormalizing the SQL schema. Defining a superclass and subclasses will be possible by distinguishing strings.

4) One table for each subclass, where the “is a” relationship is represented as “has a”, i.e. - foreign key communication using JOIN.

There are 3 main factors that will affect your chosen strategy:

1) Productivity (we use “hibernate_show_sql” to see and evaluate all queries performed to the database)

2) Normalization of the scheme and data integrity guarantee (not every strategy guarantees the fulfillment of the NOT NULL constraint)

3) The possibility of the evolution of your scheme

Under the cat, each of these strategies will be discussed in detail, with an indication of the advantages and disadvantages, and recommendations will be given on the choice of strategy in specific cases.

A couple of words from the author and instructions for working with examples
This article is a squeeze from the book Java Resistance with Hibernate. Its authors are the founder of the Hibernate project, Gavin King (Gavin King) and Christian Bauer, a member of the Hibernate development team.
In the summer of 2017, it was translated and published in Russian.

I tried to simplify the presentation of the material, as well as work with examples. Feeling a strong dislike for examples with which you need to work an hour to start, I tried to make the work with them in this article as convenient as possible:
- All Java code you can just copy into your IDE. All changes to Java code when moving from one strategy to another are indicated in the spoilers, so when moving to a new strategy, you can simply delete the old class code and copy the new one. The Main and HibernateUtil classes will remain unchanged, and will work when considering all the examples.

- In the spoilers for each strategy, you will also find scripts for creating all the database tables. Therefore, after you have disassembled the next strategy, you can simply drop all the tables - in the next section you will find the actual scripts for creating new ones.

Code written using Java 1.7, Hibernate5, and PostgreSQL9

Enjoy reading!

Strategy 1


One table for each class


Situation:

We decided to overshadow the glory of eBay and create our own online auction application for this purpose. Each User can bet, and in the event that his bid was the largest - to pay online.

Actually, we will consider the payment process as a data model.
User can make payment in two ways: using a bank card, or using bank account details.

The class diagram is presented below:

image

Java code to run the example
pom.xml:

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hiber.jd2050</groupId> <artifactId>hiberLearn</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.7</java.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> <dependencies> <!-- PostgreSQL --> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.0-801.jdbc4</version> </dependency> <!-- Hibernate-JPA-2.1-API --> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <version>1.0.0.Final</version> </dependency> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.1</version> </dependency> <!-- Hibernate-core --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.0.5.Final</version> </dependency> </dependencies> </project> 

hibernate.cfg.xml

 <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.url">jdbc:postgresql://localhost:5432/dobrynin_db</property> <!-- BD Mane --> <property name="connection.driver_class">org.postgresql.Driver</property> <!-- DB Driver --> <property name="connection.username">postgres</property> <!-- DB User --> <property name="connection.password">filyaSl9999</property> <!-- DB Password --> <property name="dialect">org.hibernate.dialect.PostgreSQL9Dialect</property> <!-- DB Dialect --> <property name="hbm2ddl.auto">create-drop</property> <!-- create / create-drop / update --> <property name="show_sql">true</property> <!-- Show SQL in console --> <property name="format_sql">true</property> <!-- Show SQL formatted --> <property name="hibernate.current_session_context_class">thread</property> <mapping class="CreditCard"/> <mapping class="BankAccount"/> <mapping class="BillingDetails"/> </session-factory> </hibernate-configuration> 

 import javax.persistence.*; @MappedSuperclass public abstract class BillingDetails { private String owner; public BillingDetails() { } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "owner='" + owner + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @Table(name = "CREDIT_CARD") public class CreditCard extends BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; @Column(name = "card_number") private int cardNumber; @Column(name = "exp_month") private String expMonth; @Column (name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @Table(name = "BANK_ACCOUNT") public class BankAccount extends BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } } 

 import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { public static SessionFactory getSessionFactory() { return new Configuration().configure().buildSessionFactory(); } } 

Main class with main () method:

 import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import java.util.*; public class Main { public static void main(String[] args) throws Exception { CreditCard creditCard = new CreditCard(); creditCard.setCardNumber(44411111); creditCard.setExpMonth("Jan"); creditCard.setExpYear("2017"); creditCard.setOwner("Bill Gates"); BankAccount bankAccount = new BankAccount(); bankAccount.setAccount(111222333); bankAccount.setBankName("Goldman Sachs"); bankAccount.setSwift("GOLDUS33"); bankAccount.setOwner("Donald Trump"); SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); Session session; Transaction transaction = null; try { session = sessionFactory.getCurrentSession(); transaction = session.beginTransaction(); session.persist(creditCard); session.persist(bankAccount); transaction.commit(); } catch (Exception e) { transaction.rollback(); throw e; } Session session1; Transaction transaction1 = null; try { session1 = sessionFactory.getCurrentSession(); transaction1 = session1.beginTransaction(); List billingDetails = session1.createQuery("select bd from BillingDetails bd").list(); for (int i = 0; i < billingDetails.size(); i++) { System.out.println(billingDetails.get(i)); } } catch (Exception e) { transaction1.rollback(); throw e; } } } 


The BankAccount and CreditCard classes are inherited from the common abstract ancestor of BillingDetails. As can be seen from the diagram, despite the similar functionality, their states are significantly different: for the card, the number and validity are important for us, and for the bank account - the details field.

The parent class stores only information for all descendants about the owner.
In addition, there can be carried out, for example, the Id field along with the type of generation (in this case, we did without it).

The scheme of our database for the first strategy will look like this:
image

Queries to create tables:

CREDIT_CARD
 create table credit_card ( id serial not null constraint bank_account_pkey primary key, cc_owner varchar(20) not null, card_number integer not null, exp_month varchar(9) not null, exp_year varchar(4) not null ) ; 


BANK_ACCOUNT
 create table bank_account ( id serial not null primary key, owner varchar(20), account integer not null, bank_name varchar(20) not null, swift varchar(20) not null ) ; 


Polymorphism in this case will be implicit. Each descendant class we can reflect using the Entity annotation.

IMPORTANT! Superclass properties will be ignored by default. To save them to a table of a specific subclass, you must use the @MappedSuperClass annotation.

The display of subclasses does not contain anything unusual. The only thing you should pay attention to is perhaps the unfamiliar for some annotation @AttributeOverride.
It is used to rename a column in a subclass table, if the ancestor’s names and the descendant's table do not match (in our case, so that the “owner” from BillingDetails maps to CC_OWNER in the CREDIT_CARD table).

The main problem with using this strategy is that it will not be possible to fully use polymorphic associations: they are usually represented in the database as foreign key access, and we simply do not have the BILLING_DETAILS table. And since each BillingDetails object will be associated with a specific User object in the application, each of the descendant tables will need a foreign key that refers to the USERS table.

In addition, polymorphic queries will also be a problem.

Let's try to fulfill the request

 SELECT bd FROM BillingDetails bd 

To do this (hereinafter) just run the main () method.

In this case, it will be executed as follows:

 Hibernate: select bankaccoun0_.id as id1_1_, bankaccoun0_.owner as owner2_1_, bankaccoun0_.account as account3_1_, bankaccoun0_.bank_name as bank_nam4_1_, bankaccoun0_.swift as swift5_1_ from BANK_ACCOUNT bankaccoun0_ Hibernate: select creditcard0_.id as id1_2_, creditcard0_.owner as owner2_2_, creditcard0_.card_number as card_num3_2_, creditcard0_.exp_month as exp_mont4_2_, creditcard0_.exp_year as exp_year5_2_ from CREDIT_CARD creditcard0_ 

In other words, for each specific subclass, Hibernate uses a separate SELECT query.

Another important issue when using this strategy will be the difficulty of refactoring. Changing the name of the fields in the superclass will cause the need to change the names in many tables and will require manual renaming (most IDE tools do not take into account @AttributeOverride). If in your scheme there are not 2 tables, but 50, this is fraught with large time costs.

This approach can be used only for the top of the class hierarchy, where:

a) Polymorphism is not needed (sampling for a particular subclass of Hibernate will perform in one query -> performance will be high)

b) Changes in the superclass are not expected.

For applications where requests will refer to the parent class BillingDetails this strategy will not work.

Strategy 2


One table for each class with unions (UNION)


BillingDetails will once again be the abstract class.
The database schema will also remain almost unchanged.

The only point is that the CC_OWNER field in the CREDIT_CARD table will have to be renamed OWNER, since this strategy does not support @AttributeOverride. From the documentation :
"If you’re looking at what’s up?”

The new @Inheritance annotation above the superclass will also be new, indicating the selected TABLE_PER_CLASS strategy.

IMPORTANT! As part of this strategy, the presence of an identifier in the superclass is a mandatory requirement (in the first example, we did without it).

IMPORTANT! According to the JPA standard, the TABLE_PER_CLASS strategy is not mandatory, so other implementations may not be supported.

Modified Java code
 import javax.persistence.*; @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private String owner; public BillingDetails() { } public int getId() { return id; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "id=" + id + ", owner='" + owner + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @Table(name = "CREDIT_CARD") public class CreditCard extends BillingDetails { @Column(name = "card_number") private int cardNumber; @Column(name = "exp_month") private String expMonth; @Column (name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @Table(name = "BANK_ACCOUNT") public class BankAccount extends BillingDetails { private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } } 


Our SQL schema still knows nothing about inheritance; there is no relationship between the tables.

The main advantage of this strategy can be seen by completing the polymorphic query from the previous example.

 SELECT bd FROM BillingDetails bd 

This time it will be done differently:

 Hibernate: select billingdet0_.id as id1_1_, billingdet0_.owner as owner2_1_, billingdet0_.card_number as card_num1_2_, billingdet0_.exp_month as exp_mont2_2_, billingdet0_.exp_year as exp_year3_2_, billingdet0_.account as account1_0_, billingdet0_.bank_name as bank_nam2_0_, billingdet0_.swift as swift3_0_, billingdet0_.clazz_ as clazz_ from ( select id, owner, card_number, exp_month, exp_year, null::int4 as account, null::varchar as bank_name, null::varchar as swift, 1 as clazz_ from CREDIT_CARD union all select id, owner, null::int4 as card_number, null::varchar as exp_month, null::varchar as exp_year, account, bank_name, swift, 2 as clazz_ from BANK_ACCOUNT ) billingdet0_ 

In this case, Hibernate uses FROM to extract all BillingDetails instances from all subclass tables. Tables are combined using UNION, and literals (1 and 2) are added to the intermediate result. Literals are used by Hibernate to create an instance of the correct class.

Merging tables requires the same column structure, so instead of non-existent columns, NULLs were inserted (for example, “null :: varchar as bank_name” in credit_card — there is no bank name in the credit card table).

Another important advantage over the first strategy will be the ability to use polymorphic associations. Now it will be possible to display associations between the User and BillingDetails classes without any problems.

Strategy 3


Single table for the entire class hierarchy


The class hierarchy can be completely selected in one table. It will contain columns for all fields in each hierarchy class. For each entry, a specific subclass will be determined by the value of the additional column with the selector .

Our scheme now looks like this:
image

Request to create
 create table billing_details ( id serial not null constraint billing_details_pkey primary key, bd_type varchar(2), owner varchar(20), card_number integer, exp_month varchar(9), exp_year varchar(4), account integer, bank_name varchar(20), swift varchar(20) ) ; create unique index billing_details_card_number_uindex on billing_details (card_number) ; 


Java class structure:

image

To create a single table mapping, you must use the SINGLE_TABLE inheritance strategy.
The root class will be mapped to the BILLING_DETAILS table. To distinguish between types, a selector column will be used. It is not an entity field and is created only for the needs of Hibernate. Its value will be strings - “CC” or “BA”.
IMPORTANT! If you do not explicitly specify a selector column in the superclass, it will receive the default name DTYPE and VARCHAR type.

Each hierarchy class can specify its selector value using the @DiscriminatorValue annotation.
Do not neglect the explicit indication of the name of the selector: by default, Hibernate will use the full name of the class or the name of the entity (depending on whether XML-Hibernate files or JPA / annotation xml files are used).

Modified Java code
 import javax.persistence.*; @Entity @Table(name = "BILLING_DETAILS") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "BD_TYPE") public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private String owner; public BillingDetails() { } public int getId() { return id; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "id=" + id + ", owner='" + owner + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @DiscriminatorValue("BA") public class BankAccount extends BillingDetails { private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @DiscriminatorValue("CC") public class CreditCard extends BillingDetails { @Column(name = "card_number") private int cardNumber; @Column(name = "exp_month") private String expMonth; @Column (name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } } 


For verification, we use the already familiar query in the main method.

 SELECT bd FROM BillingDetails bd 

In the case of a single table, this query will be executed as follows:

 Hibernate: select billingdet0_.id as id2_0_, billingdet0_.owner as owner3_0_, billingdet0_.card_number as card_num4_0_, billingdet0_.exp_month as exp_mont5_0_, billingdet0_.exp_year as exp_year6_0_, billingdet0_.account as account7_0_, billingdet0_.bank_name as bank_nam8_0_, billingdet0_.swift as swift9_0_, billingdet0_.BD_TYPE as BD_TYPE1_0_ from BILLING_DETAILS billingdet0_ 

If the query is executed to a specific subclass, the string “where BD_TYPE =“ CC ”” will simply be added.

Here is what the display in a single table will look like:
image

In the case when the schema was inherited, and it is impossible to add a selector column to it, the @DiscriminatorFormula annotation comes to the rescue, which must be added to the parent class. It must pass the expression CASE ... WHEN.

 import org.hibernate.annotations.DiscriminatorFormula; import javax.persistence.*; @Entity @Table(name = "BILLING_DETAILS") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorFormula("CASE WHEN CARD_NUMBER IS NOT NULL THEN 'CC' ELSE 'BA' END") public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; //................. } 

The main advantage of this strategy is performance. Requests (both polymorphic and non-polymorphic) are executed very quickly and can be easily written by hand. No need to use joins and joins The evolution of the scheme is also very simple.

However, the problems accompanying this strategy will often outweigh its benefits.

Chief among these is data integrity. Columns of those properties that are declared in subclasses may contain NULL. As a result, a simple software error can lead to a credit card with no number or expiration date in the database.

Another problem is the violation of normalization, and specifically the third normal form . In this light, the benefits of improved performance already look doubtful. After all, you will have to at least sacrifice the convenience of maintenance: in the long run, denormalized schemes do not promise anything good.

Strategy 4


One table for each class using joins


The layout of our classes will remain unchanged:

image

But in the database schema there have been some changes

image

Request to create a BILLING_DETAILS
 create table billing_details ( id integer not null constraint billing_details_pkey primary key, owner varchar(20) not null ) ; 


For CREDIT_CARD
 create table credit_card ( id integer not null constraint credit_card_pkey primary key constraint credit_card_billing_details_id_fk references billing_details, card_number integer not null, exp_month varchar(255) not null, exp_year varchar(255) not null ) ; create unique index credit_card_card_number_uindex on credit_card (card_number) ; 


For BANK_ACCOUNT
 create table bank_account ( id integer not null constraint bank_account_pkey primary key constraint bank_account_billing_details_id_fk references billing_details, account integer not null, bank_name varchar(255) not null, swift varchar(255) not null ) ; create unique index bank_account_account_uindex on bank_account (account) ; 


In Java code, you need to use the JOINED strategy to create such a mapping.

Modified Java code
 import javax.persistence.*; @Entity @Table(name = "BILLING_DETAILS") @Inheritance(strategy = InheritanceType.JOINED) public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private String owner; public BillingDetails() { } public int getId() { return id; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "id=" + id + ", owner='" + owner + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @Table(name = "CREDIT_CARD") public class CreditCard extends BillingDetails { @Column(name = "card_number") private int cardNumber; @Column(name = "exp_month") private String expMonth; @Column (name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @Table(name = "BANK_ACCOUNT") public class BankAccount extends BillingDetails { private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } } 


Now when you save, for example, a CreditCard instance, Hibernate will insert two records. The properties declared in the fields of the BillingDetails superclass will fall into the BILLING_DETAILS table, and the values ​​of the CreaditCard subclass fields will be written to the CREDIT_CARD table. These entries will be merged by the common primary key.

Thus, the scheme was brought to a normal state. The evolution of the scheme and the definition of integrity constraints are also simple.
Foreign keys allow you to represent a polymorphic association with a particular subclass.

Fulfilling request

 SELECT bd FROM BillingDetails bd 

, we will see the following picture:

 Hibernate: select billingdet0_.id as id1_1_, billingdet0_.owner as owner2_1_, billingdet0_1_.card_number as card_num1_2_, billingdet0_1_.exp_month as exp_mont2_2_, billingdet0_1_.exp_year as exp_year3_2_, billingdet0_2_.account as account1_0_, billingdet0_2_.bank_name as bank_nam2_0_, billingdet0_2_.swift as swift3_0_, case when billingdet0_1_.id is not null then 1 when billingdet0_2_.id is not null then 2 when billingdet0_.id is not null then 0 end as clazz_ from BILLING_DETAILS billingdet0_ left outer join CREDIT_CARD billingdet0_1_ on billingdet0_.id=billingdet0_1_.id left outer join BANK_ACCOUNT billingdet0_2_ on billingdet0_.id=billingdet0_2_.id 

BILLING_DETAILS

image

CREDIT_CARD

image

BANK_ACCOUNT

image

The CASE ... WHEN clause allows Hibernate to define a specific subclass for each record. It checks for the presence or absence of rows in the CREDIR_CARD and BANK_ACCOUNT subclass tables using literals.

Such a strategy will be very difficult to implement manually. Even implementing reports based on arbitrary requests will be much more difficult.
Performance may also be unacceptable for a particular project, since queries will require joining several tables or many sequential read operations.

Mixing inheritance mapping strategies


When working with TABLE_PER_CLASS, SINGLE_TABLE and JOINED strategies, a significant disadvantage is the fact that it is impossible to switch between them. The chosen strategy will have to adhere to the end (or completely change the scheme).
But there are tricks with which you can switch the display strategy for a particular subclass.

For example, by mapping a class hierarchy into a single table (strategy 3), you can choose a strategy for a particular subclass with a separate table and a foreign key (strategy 4).

image

image

Script to create BILLING_DETAILS
 create table billing_details ( id integer not null constraint billing_details_pkey primary key, owner varchar(20), account integer, bank_name varchar(20), swift varchar(20) ) ; 


For CREDIT_CARD
 create table credit_card ( card_number integer not null, exp_month varchar(255) not null, exp_year varchar(255) not null, id integer not null constraint credit_card_pkey primary key constraint fksf645frtr6h3i4d179ff4ke9h references billing_details ) ; 


Now we can map the CreditCard subclass to a separate table.
To do this, we need to apply the InheritanceType.SINGLE_TABLE strategy to the BillingDetails superclass, and the @SecondaryTable annotation will help us with the CreditCard class.

Modified Java code
 import javax.persistence.*; @Entity @Table(name = "BILLING_DETAILS") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "BD_TYPE") public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private String owner; public BillingDetails() { } public int getId() { return id; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "id=" + id + ", owner='" + owner + '\'' + '}'; } } 

 import javax.persistence.*; @Entity public class BankAccount extends BillingDetails { private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } } 

 import javax.persistence.*; @Entity @DiscriminatorValue("CC") @SecondaryTable(name = "CREDIT_CARD", pkJoinColumns = @PrimaryKeyJoinColumn(name = "ID")) public class CreditCard extends BillingDetails { @Column(table = "CREDIT_CARD",name = "card_number") private int cardNumber; @Column(table = "CREDIT_CARD",name = "exp_month") private String expMonth; @Column (table = "CREDIT_CARD",name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } } 


@SecondaryTable @Column , Hibernate, .

SINGLE_TABLE NULL. , ( — CreditCard).
, Hibernate BillingDetails .

Let's try:

 SELECT bd FROM BillingDetails bd 

Result:

 Hibernate: select billingdet0_.id as id2_0_, billingdet0_.owner as owner3_0_, billingdet0_.account as account4_0_, billingdet0_.bank_name as bank_nam5_0_, billingdet0_.swift as swift6_0_, billingdet0_1_.card_number as card_num1_1_, billingdet0_1_.exp_month as exp_mont2_1_, billingdet0_1_.exp_year as exp_year3_1_, billingdet0_.BD_TYPE as BD_TYPE1_0_ from BILLING_DETAILS billingdet0_ left outer join CREDIT_CARD billingdet0_1_ on billingdet0_.id=billingdet0_1_.ID 

image

image

, , . , SQL- .


. :

— №2 (TABLE_PER_CLASS UNION), . ( ) «select bd from BillingDetails bd», , BillingDetails, ( ).

— №3 (SINGLE_TABLE) :

) . , NOT NULL – №4 (JOINED). ,
) , ; .
, .

— №4 (JOINED) , , .

: JOINED TABLE_PER_CLASS , (, , ) .

, .

Thanks for attention!

Source: https://habr.com/ru/post/337488/


All Articles