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 Subclass Strategy in Hibernate
Explanation: Table per Subclass Strategy
The Table per Subclass strategy is a database inheritance mapping technique where each concrete subclass in an inheritance hierarchy is mapped to its own table in the database. The table for each subclass contains columns for the subclass-specific properties as well as a foreign key referencing the primary key of the base class table. This creates a separate table for each type of entity, allowing for specific columns for each subclass without polluting the base class table.
Detailed Explanation
In this strategy, the base class has its own table, and each subclass has its own table as well. The subclass tables store only the properties specific to that subclass, and they have a foreign key that refers to the primary key of the base class table. When querying for objects of the base class, Hibernate needs to perform a JOIN operation with each of the subclass tables to retrieve all instances.
Advantages:
- Clear separation of subclass-specific attributes into their own tables.
- No NULL columns in the base table for subclass-specific properties.
- Better performance for queries involving only subclass-specific properties.
Disadvantages:
- More complex SQL queries when retrieving objects of the base class, requiring JOIN operations across multiple tables.
- More complex database schema due to the multiple tables for each subclass.
- Potential performance overhead when querying for all objects of the base class due to the required JOINs.
Implementation using Hibernate
We will demonstrate the Table per Subclass strategy using Hibernate annotations. Let's consider a scenario with a base class Vehicle
and two subclasses: Car
and Truck
.
1. Base Class (Vehicle)
import javax.persistence.*;
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "vehicle")
public class Vehicle {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String model;
public Vehicle() {}
public Vehicle(String model) {
this.model = model;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
}
Explanation:
@Entity
: Marks the class as a persistent entity.@Table(name = "vehicle")
: Specifies the table name in the database.@Inheritance(strategy = InheritanceType.JOINED)
: Specifies the inheritance strategy as "Table per Subclass".JOINED
means that each subclass will have its own table joined to the superclass table.@Id
: Marks theid
field as the primary key.@GeneratedValue(strategy = GenerationType.IDENTITY)
: Configures the primary key to be auto-generated by the database using an identity column.
2. Subclass (Car)
import javax.persistence.*;
@Entity
@Table(name = "car")
@PrimaryKeyJoinColumn(name = "vehicle_id")
public class Car extends Vehicle {
private int numberOfDoors;
public Car() {}
public Car(String model, int numberOfDoors) {
super(model);
this.numberOfDoors = numberOfDoors;
}
public int getNumberOfDoors() {
return numberOfDoors;
}
public void setNumberOfDoors(int numberOfDoors) {
this.numberOfDoors = numberOfDoors;
}
}
Explanation:
@Entity
: Marks the class as a persistent entity.@Table(name = "car")
: Specifies the table name for the Car class.@PrimaryKeyJoinColumn(name = "vehicle_id")
: Specifies the foreign key column in thecar
table that references the primary key of thevehicle
table. This establishes the relationship between the tables. Thevehicle_id
column in the `car` table will be a foreign key to the `id` column in the `vehicle` table.
3. Subclass (Truck)
import javax.persistence.*;
@Entity
@Table(name = "truck")
@PrimaryKeyJoinColumn(name = "vehicle_id")
public class Truck extends Vehicle {
private double loadCapacity;
public Truck() {}
public Truck(String model, double loadCapacity) {
super(model);
this.loadCapacity = loadCapacity;
}
public double getLoadCapacity() {
return loadCapacity;
}
public void setLoadCapacity(double loadCapacity) {
this.loadCapacity = loadCapacity;
}
}
Explanation:
@Entity
: Marks the class as a persistent entity.@Table(name = "truck")
: Specifies the table name for the Truck class.@PrimaryKeyJoinColumn(name = "vehicle_id")
: Specifies the foreign key column in thetruck
table that references the primary key of thevehicle
table.
4. Hibernate Configuration (hibernate.cfg.xml)
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/your_database</property>
<property name="connection.username">your_username</property>
<property name="connection.password">your_password</property>
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<property name="hbm2ddl.auto">update</property> <!-- Use 'create' for initial schema creation -->
<property name="show_sql">true</property> <!-- Optional: Display SQL queries -->
<property name="format_sql">true</property><!-- Optional: Format SQL queries -->
<mapping class="Vehicle"/>
<mapping class="Car"/>
<mapping class="Truck"/>
</session-factory>
</hibernate-configuration>
Explanation:
- Replace
your_database
,your_username
, andyour_password
with your actual database credentials. - The
<mapping class="..."/>
elements tell Hibernate about the entity classes. - The
hbm2ddl.auto
property is set toupdate
. This will automatically create the database tables based on the entity mappings if they don't exist, or update them if they do. Use `create` to drop and recreate the schema each time the application runs.
5. Example Usage (Java Code)
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class HibernateTablePerSubclassExample {
public static void main(String[] args) {
Configuration configuration = new Configuration().configure("hibernate.cfg.xml");
SessionFactory sessionFactory = configuration.buildSessionFactory();
try (Session session = sessionFactory.openSession()) {
Transaction transaction = session.beginTransaction();
// Create and save a Vehicle
Vehicle vehicle = new Vehicle("Generic Model");
session.save(vehicle);
// Create and save a Car
Car car = new Car("Sedan", 4);
session.save(car);
// Create and save a Truck
Truck truck = new Truck("Heavy Duty", 5000.0);
session.save(truck);
transaction.commit();
} catch (Exception e) {
e.printStackTrace();
}
try (Session session = sessionFactory.openSession()) {
// Query all Vehicles
System.out.println("All Vehicles:");
session.createQuery("from Vehicle", Vehicle.class).list().forEach(v -> System.out.println(v.getClass().getSimpleName() + ": " + v.getModel()));
// Query all Cars
System.out.println("\nAll Cars:");
session.createQuery("from Car", Car.class).list().forEach(c -> System.out.println(c.getModel() + ", Doors: " + c.getNumberOfDoors()));
// Query all Trucks
System.out.println("\nAll Trucks:");
session.createQuery("from Truck", Truck.class).list().forEach(t -> System.out.println(t.getModel() + ", Capacity: " + t.getLoadCapacity()));
} catch (Exception e) {
e.printStackTrace();
} finally {
sessionFactory.close();
}
}
}
Explanation:
- The code creates a Hibernate
SessionFactory
from the configuration file. - It then creates a
Session
and aTransaction
. - Instances of
Vehicle
,Car
, andTruck
are created and saved to the database. - The transaction is committed.
- Finally, the code demonstrates querying for all vehicles, all cars, and all trucks, printing their details to the console.