Hibernate Inheritance Mapping
This lesson covers the different strategies for mapping inheritance hierarchies to database tables in Hibernate. We will explore strategies like Table per Class Hierarchy, Table per Subclass, and Table per Concrete Class.
Table per Concrete Class Strategy in Hibernate
Explanation of Table per Concrete Class Strategy
The "Table per Concrete Class" strategy is a inheritance mapping strategy in Hibernate where each concrete (non-abstract) class in an inheritance hierarchy is mapped to its own database table. Each table contains all the properties defined in the corresponding class and all properties inherited from its superclasses. There is no table representing the base (abstract) class in the hierarchy.
Advantages
- Simple Queries: Queries are generally straightforward, as each table contains all the necessary data for a specific type. There's no need to join tables to retrieve data for a concrete class.
- No Nullable Columns: Each table contains only columns relevant to its associated class, so there are no null values resulting from attributes present in subclasses but not the superclass.
- Good Performance for Single-Table Queries: Retrieves for a specific entity type are efficient because all the data is in a single table.
Disadvantages
- Data Redundancy: Attributes inherited from the superclass are duplicated in each table, leading to data redundancy.
- Difficult to Query Across All Subclasses: Querying across all entities of the inheritance hierarchy is complex and requires a
UNION
of all the concrete class tables. - Schema Changes Can Be Tedious: Adding a new attribute to the superclass requires modifying all tables corresponding to the concrete classes.
- Potential for Data Inconsistency: Updating the inherited attributes across multiple tables increases the risk of data inconsistency.
Implementation using Hibernate Annotations
Here's how to implement the "Table per Concrete Class" strategy using Hibernate annotations. We'll create a simple inheritance hierarchy with a base class BillingDetails
and two concrete classes CreditCard
and BankAccount
.
Base Class: BillingDetails
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BillingDetails {
@Id
@GeneratedValue(strategy = GenerationType.TABLE) //Important for Table per Class strategy
private Long id;
private String owner;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
Concrete Class: CreditCard
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "credit_card")
public class CreditCard extends BillingDetails {
private String cardNumber;
private String expiryDate;
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
public String getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(String expiryDate) {
this.expiryDate = expiryDate;
}
}
Concrete Class: BankAccount
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "bank_account")
public class BankAccount extends BillingDetails {
private String accountNumber;
private String bankName;
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getBankName() {
return bankName;
}
public void setBankName(String bankName) {
this.bankName = bankName;
}
}
Explanation of Annotations:
@Entity
: Marks the class as a JPA entity.@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
: Specifies the inheritance strategy as "Table per Concrete Class". This is placed on the root of the inheritance hierarchy (BillingDetails
).@Table(name = "...")
: Specifies the table name for each concrete class.@Id
: Marks theid
field as the primary key.@GeneratedValue(strategy = GenerationType.TABLE)
: Configures primary key generation.GenerationType.TABLE
is crucial here. It uses a separate table to generate unique IDs across all tables in the hierarchy. Without this, you may encounter primary key conflicts.AUTO
is NOT suitable here.
Implementation using Hibernate XML Configuration (hibernate.cfg.xml)
While annotations are the preferred method, you can also use XML configuration. The equivalent XML mapping would be as follows:
hibernate.cfg.xml (example)
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.url">jdbc:h2:mem:testdb</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.H2Dialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create-drop</property>
<!-- Mappings -->
<mapping class="BillingDetails"/> <mapping class="CreditCard"/> <mapping class="BankAccount"/> </session-factory>
</hibernate-configuration>
BillingDetails.hbm.xml
<hibernate-mapping>
<class name="BillingDetails" abstract="true">
<id name="id">
<generator class="increment"/> <!-- Important for Table per Class -->
</id>
<property name="owner"/>
<subclass name="CreditCard" discriminator-value="credit_card">
<property name="cardNumber"/>
<property name="expiryDate"/>
</subclass>
<subclass name="BankAccount" discriminator-value="bank_account">
<property name="accountNumber"/>
<property name="bankName"/>
</subclass>
</class>
</hibernate-mapping>
Example Code Demonstrating Mapping and Querying
This example shows how to persist and retrieve data using the defined entities.
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class HibernateExample {
public static void main(String[] args) {
Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
try (Session session = sessionFactory.openSession()) {
Transaction transaction = session.beginTransaction();
// Create and persist a CreditCard
CreditCard creditCard = new CreditCard();
creditCard.setOwner("Alice Smith");
creditCard.setCardNumber("1234-5678-9012-3456");
creditCard.setExpiryDate("12/24");
session.save(creditCard);
// Create and persist a BankAccount
BankAccount bankAccount = new BankAccount();
bankAccount.setOwner("Bob Johnson");
bankAccount.setAccountNumber("9876543210");
bankAccount.setBankName("Example Bank");
session.save(bankAccount);
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
}
//Querying
try (Session session = sessionFactory.openSession()) {
//Query for a specific type
CreditCard retrievedCard = session.get(CreditCard.class, 1L); //Assuming ID 1 exists and is a CreditCard
if (retrievedCard != null) {
System.out.println("Retrieved Credit Card: " + retrievedCard.getOwner() + ", " + retrievedCard.getCardNumber());
}
BankAccount retrievedAccount = session.get(BankAccount.class, 2L); //Assuming ID 2 exists and is a BankAccount
if (retrievedAccount != null) {
System.out.println("Retrieved Bank Account: " + retrievedAccount.getOwner() + ", " + retrievedAccount.getAccountNumber());
}
} catch (Exception e) {
e.printStackTrace();
}
sessionFactory.close();
}
}
This code demonstrates creating instances of CreditCard
and BankAccount
, persisting them to the database, and retrieving specific entities. Note the use of session.get(CreditCard.class, 1L)
and `session.get(BankAccount.class, 2L) to retrieve instances of the concrete classes. You retrieve based on the *concrete* class and its ID, and the session goes directly to the corresponding table.
Important Considerations for querying:
- Querying all BillingDetails: As the
BillingDetails
class is abstract and has no corresponding table, you cannot directly query for allBillingDetails
instances. Instead, you would have to query each concrete class separately and combine the results. This is a significant drawback of this strategy. - Using HQL with UNION: To query across all subclasses, you would need to write an HQL query that uses
UNION
to combine results from each table. This can be complex and potentially inefficient:
SELECT bd FROM CreditCard bd UNION SELECT ba FROM BankAccount ba