steven _haines
Contributor

Inheritance relationships in JPA and Hibernate

tip
Apr 25, 20198 mins
APIsCareersDatabases

Choosing a JPA inheritance strategy that supports polymorphism

Inheritance is a common pattern in object-oriented programming, but it’s not easily replicated in a database. This Java tip shows you how to model inheritance relationships in JPA with Hibernate.

Learn the pros and cons of four different ORM inheritance strategies and get tips for choosing the one that best supports your application needs.

The inheritance pattern in ORM

Inheritance is an object-oriented pattern in which one class extends (or specializes) another class in order to borrow (or inherit) some of its functionality. For example, let’s say we have a class named Car that represents all cars, including a make, model, and year. But now we want to create some more specialized types of car, such as SportsCar and SportUtilityVehicle. These cars would have all the features of the original Car class, but with some additional functionality. A SportsCar might need special parameters for designating speed. A SportsUtilityVehicle might need flags for seating and towing capacity.

Databases have no notion of inheritance between entities, so JPA providers like Hibernate must provide special features for defining object-oriented inheritance relationships in the database.

JPA specifies four strategies for defining an inheritance relationship:

  • Mapped Superclass
  • Table Per Class
  • Single Table
  • Joined

We’ll look at each of these strategies as they’re implemented by Hibernate.

Inheritance strategy #1: Mapped Superclass

The Mapped Superclass is the simplest inheritance strategy. It allows you to define common attributes in a base class, then extend that superclass in your other classes. Here’s an example based on our Car class:


@MappedSuperclass
public abstract class Car {
    @Id
    @GeneratedValue
    private Integer id;
    private String make;
    private String model;
    private Integer year;

    ...
}

The Car class is annotated with the @MappedSuperclass annotation and defines common car attributes, such as the make, model, and year, as well as the autogenerated id of the car.

Now let’s see what happens when we define a SportsCar class and SportUtilityVehicle class extending Car:


@Entity
@Table(name = "SPORTSCAR")
public class SportsCar extends Car {
    private Integer topSpeed;
    private Double zeroToSixty;

    ...
}

@Entity
@Table(name = "SUV")
public class SportsUtilityVehicle extends Car {
    private Integer towingCapacity;
    private Boolean thirdRowSeating;

    ...
}

The Mapped Superclass strategy is simple but limited, in that both of these classes will map to tables that contain all of the car attributes: both the original Car attributes and new, specialized class attributes.

Using this pattern means Hibernate won’t be able to run polymorphic queries against individual cars. If we define a CarDealer class that manages different types of cars, there’s no way to maintain a list of Cars in the CarDealer; instead we would need to create a list of SportsCars and a list of SportUtilityVehicles.

We might do better with a slightly more complex inheritance strategy.

Inheritance strategy #2: Table Per Class

The next strategy for implementing inheritance is called Table Per Class. This strategy stores all data in its specialized class tables, just like we did with the mapped superclass, but it maintains the relationships between specialized classes and their base class.

In this case, the specialized classes, SportsCar and SportsUtilityVehicle, stay the same, but the base class is treated as an entity.


@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Car {
    @Id
    @GeneratedValue
    private Integer id;
    private String make;
    private String model;
    private Integer year;

    ...
}

We define the base class as an entity, using the @Entity annotation. We then introduce the @Inheritance annotation, specifying the InstanceType.TABLE_PER_CLASS strategy.

With this strategy, we’re able to execute polymorphic queries, so we can map a list of Cars to a CarDealer.

A complicated union

While Table Per Class provides the functionality we’re looking for, its generated queries are complex. There are also potential performance issues with this strategy, because we have to perform a union of all the Car subclass tables, then resolve the result set from that.

As an example, say we were to add two cars to our application code: one SUV and one sports car. The resultant database tables would contain the following:


Table: CAR
Table: SPORTSCAR
  {ID: 1, MAKE: Carrera, MODEL: Porche, YEAR: 2018, TOPSPEED: 150, ZEROTOSIXTY: 4.5}
Table: SUV
  {ID: 2, MAKE: CX-9, MODEL: Mazda, YEAR: 2017, THIRDROW: true, TOWINGCAPACITY: 3000}
  

Inheritance strategy #3: Single Table

The next strategy maps all fields from all entities to a single table, with a discriminator column to identify the instance type:


@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Car_Type")
public abstract class Car {
    @Id
    @GeneratedValue
    private Integer id;
    private String make;
    private String model;
    private Integer year;

    ...
}

@Entity
@Table(name = "SPORTSCAR")
@DiscriminatorValue("SportsCar")
public class SportsCar extends Car {
    private Integer topSpeed;
    private Double zeroToSixty;

    ...
}

@Entity
@Table(name = "SUV")
@DiscriminatorValue("SUV")
public class SportsUtilityVehicle extends Car {
    private Integer towingCapacity;
    private Boolean thirdRowSeating;

    ...
}

The single table strategy is implemented using the @Inheritance annotation with InstanceType.SINGLE_TABLE. We add to this a @DiscriminatorColumn annotation, which defines a column name we’ll use to discriminate between specific class types. The specialized classes, such as SportsCar and SportsUtilityVehicle, extend the base class and add a @DiscriminatorValue annotation, which specifies the name to identify its instances in the database.

The challenge with this implementation is that each table maintains all fields for all attributes for all specialized classes. You also lose the ability to define specialized fields to be non-null, because they will be null for other specialized classes. For example, a row representing a SportsCar will have topSpeed and zeroToSixty values, but will have null towingCapacity and thirdRowSeating column values.

Based on our implementation of this pattern, the contents of the database would be as follows:


Table: CAR
  {CAR_TYPE: SportsCar, ID: 1, MAKE: Carrera, MODEL: Porche, YEAR: 2018, TOPSPEED: 150, ZEROTOSIXTY: 4.5, THIRDROW: null, TOWINGCAPACITY: null},
  {CAR_TYPE: SUV, ID: 2, MAKE: CX-9, MODEL: Mazda, YEAR: 2017, TOPSPEED: null, ZEROTOSIXTY: null, THIRDROW: true, TOWINGCAPACITY: 3000},
  

Note that both cars are stored in the CAR table and each table [has a] CAR_TYPE attribute (from the @DiscriminatorColumn) that tells Hibernate the type of car (from the @DiscriminatorValue.) Also note that each row contains all column values, with the unused ones being set to null.

This strategy allows for efficient and polymorphic queries. On the downside, it could require maintaining many null and unnecessary columns.

Inheritance strategy #4: Joined

The final JPA inheritance strategy separates data between the base class and specialized classes into their own tables. The base class table contains all of the common attributes defined in the base class, while the specialized class tables contain only their specific attributes. We accomplish this separation by using the InstanceType.JOINED inheritance strategy:


@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Car {
    @Id
    @GeneratedValue
    private Integer id;
    private String make;
    private String model;
    private Integer year;

    ...
}

The effect of this type of inheritance is probably what you most expect when defining an inheritance relationship. For our example, the database contains the following values:


Table: CAR
  {ID: 1, MAKE: Carrera, MODEL: Porche, YEAR: 2018}
  {ID: 2, MAKE: CX-9, MODEL: Mazda, YEAR: 2017}
Table: SPORTSCAR
  {TOPSPEED: 150, ZEROTOSIXTY: 4.5, ID: 1}
Table: SUV
  {THIRDROW: true, TOWINGCAPACITY: 3000, ID: 2}
  

Using this strategy results in tables that don’t have a lot of null values, as they did with the SINGLE_TABLE strategy. You also don’t have to write the full set of table unions that the TABLE_PER_CLASS requires. On the downside, Hibernate has to perform a join between the base class table and the specialized class tables, which makes queries considerably more complex.

Choosing a JPA inheritance strategy

With four choices, the decision of which strategy to use depends on your use case. In general, I recommend avoiding the TABLE_PER_CLASS strategy because of the query overhead. I also suggest avoiding the MAPPED_SUPERCLASS strategy–or using it only for cases where you don’t need to use your classes polymorphically.

That leaves the SINGLE_TABLE and JOINED strategies.

  • If you need fast queries and don’t mind maintaining extra, unused null columns in a single table, then go with the SINGLE_TABLE strategy.
  • If you don’t want to maintain unused columns and you want to model your inheritance relationships more like you would when thinking about a database model, then use the JOINED strategy.

Personally, I almost always use JOINED for inheritance relationships in JPA.

steven _haines
Contributor

Steven Haines is a senior technologist, accomplished architect, author and educator. He is currently working at Turbonomic as a Principal Software Architect in their Advanced Engineering team, focused on cloud initiatives. Previously he worked in various principal and lead architect roles at Disney, spending the better part of the past six years in their architecture team building out the next generation of Disney's guest experience and leading various Disney initiatives, from a solutions perspective. His specializations have been in performance and scalability, cloud-based architectures, high-availability, fault tolerance, business analytics and integration with new and emerging technologies.

As an author, he has written two books on Java programming: Java 2 From Scratch (Que Publishing, 1999) and Java 2 Primer Plus (SAMS Publishing, 2002), and one on Java Performance Management: Pro Java EE 5 Performance Management and Optimization (Apress, 2006). He has written over 500 articles for publications such as JavaWorld, InformIT.com (Pearson Education), and Dr. Dobb's Journal. And he has written over a dozen white papers and ebooks on performance management and cloud-based architectures to empower companies relay their product value to the market.

As an educator, Steven has taught Computer Science and Java at both Learning Tree University as well as the University of California Irvine and maintains a website dedicated helping technologists grow in their knowledge: www.geekcap.com (by Geeks for Geeks).

The opinions expressed in this blog are those of Steven Haines and do not necessarily represent those of IDG Communications, Inc., its parent, subsidiary or affiliated companies.

More from this author