Inheritance is one of the key concepts in Java, and most development teams prefer using it in their domain model. Unfortunately, relational table models don’t support the concept of inheritance. The JPA specification defines multiple mapping strategies to bridge the gap between the object-oriented and the relational world. I explained them in great detail in my Ultimate Guide to Inheritance Mappings.

When I recently taught these mappings strategies in an in-house workshop, I was asked if it’s possible to combine InheritanceType.SINGLE_TABLE with InheritanceType.JOINED. This is not an uncommon question, especially if the team is working on huge and complex enterprise applications. But the answer to that question is: No. Based on the JPA specification, persistence providers can support this, but they don’t have to. Hibernate doesn’t support the mix of multiple strategies.

But in most cases, you can combine your inheritance mapping with a @SecondaryTable mapping to achieve your mapping goals. In this article, I will show you how to map this inheritance hierarchy

to the following table model.

Defining your inheritance mapping

In the first step, you need to define your inheritance mapping. When using InheritanceType.SINGLE_TABLE, you’re mapping all classes of the inheritance hierarchy to the same database table. The type of each record gets stored in a discriminator column. I explained other mapping strategies in my guide to inheritance mappings.

To define this mapping, you need to annotate your superclass with @Entity and @Inheritance(strategy = InheritanceType.SINGLE_TABLE). You can also add the @DiscriminatorColumn annotation to define the name of your discriminator column.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
public abstract class ChessTournament { ... }

The definition of the subclasses is straightforward. They only need to extend the superclass, and you have to annotate them with @Entity.

@Entity
public class ChessSwissTournament extends ChessTournament { ... }

And the same is the case for all other levels of the inheritance hierarchy.

@Entity
public class ChessSwissTournamentForMen extends ChessSwissTournament { ... }
@Entity
public class ChessSwissTournamentForWomen extends ChessSwissTournament { ... }

All entity objects of this inheritance hierarchy will get mapped to the table defined for the superclass. If you don’t annotate it with a @Table annotation, your persistence provider will use the simple class name as the table name.

Combining inheritance mappings with a @Secondary Table

After you mapped all classes of your inheritance hierarchy to the same database table, you can define a secondary table for each of them. This distributes the attributes of the entity class to 2 or more database tables. By doing that, you get relatively close to the table mapping you would get using a combination of InheritanceType.SINGLE_TABLE and InheritanceType.JOINED.

Let’s add a @SecondaryTable annotation to the ChessSwissTournament, ChessSwissTournamentForMen and ChessSwissTournamentForWomen entity classes.

In the example of the ChessSwissTournament entity class, I want to store the maximum number of players allowed for this tournament in the maxPlayers attribute. I want to map it to a column with the same name in the ChessSwissTournament table. This requires a @SecondaryTable annotation on the class to define the name of the secondary database table. This annotation is repeatable, and you could define multiple secondary tables for your entity class. And you need to annotate the attribute with a @Column annotation and reference the name of the secondary table.

@Entity
@SecondaryTable(name = ChessSwissTournament.TABLE_NAME)
public class ChessSwissTournament extends ChessTournament { 
    static final String TABLE_NAME = "ChessSwissTournament";

    @Column(table = TABLE_NAME)
    private int maxPlayers;

    private int rounds;

    // getter and setter methods
}

The secondary table mapping of the ChessSwissTournament class gets inherited by all subclasses. On each subclass, you can define additional secondary tables using @SecondaryTable annotations. In this example, I use that to map the number of players with a Grand Master title playing in a ChessSwissTournamentForMen to a column in a separate table.

@Entity
@SecondaryTable(name = ChessSwissTournamentForMen.TABLE_NAME)
public class ChessSwissTournamentForMen extends ChessSwissTournament {

    static final String TABLE_NAME = "ChessSwissTournamentMen";

    @Column(table = TABLE_NAME)
    private int gm;

    // getter and setter methods
}

And for the ChessSwissTournamentForWomen entity, I want to map the number of players with a Woman Grand Master title to a column in a different, separate table.

@Entity
@SecondaryTable(name = ChessSwissTournamentForWomen.TABLE_NAME)
public class ChessSwissTournamentForWomen extends ChessSwissTournament {

    static final String TABLE_NAME = "ChessSwissTournamentWomen";

    @Column(table = TABLE_NAME)
    private int wgm;

    // getter and setter methods
}

Based on this mapping, Hibernate maps the entity classes to the table model that I showed you in the introduction of this article. Let’s use this mapping to persist a new ChessSwissTournamentForMen entity object.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

ChessSwissTournamentForMen chessSwissTournamentForMen = new ChessSwissTournamentForMen();
chessSwissTournamentForMen.setName("My local tournament");
chessSwissTournamentForMen.setMaxPlayers(64);
chessSwissTournamentForMen.setRounds(7);
chessSwissTournamentForMen.setGm(4);
em.persist(chessSwissTournamentForMen);

em.getTransaction().commit();
em.close();

After activating my recommended development configuration, you can see in the log output that Hibernate inserted new records into the:

  • ChessTournament table with all attributes defined by the ChessTournament class,
  • ChessSwissTournament table with all attributes added by the ChessSwissTournament class and
  • ChessSwissTournamentMen table with all attributes added by the ChessSwissTournamentMen class.
17:36:06,996 DEBUG SQL:144 - select nextval ('tournament_seq')
17:36:07,032 DEBUG SQL:144 - insert into ChessTournament (endDate, name, startDate, version, rounds, type, id) values (?, ?, ?, ?, ?, 'ChessSwissTournamentForMen', ?)
17:36:07,037 DEBUG SQL:144 - insert into ChessSwissTournament (maxPlayers, id) values (?, ?)
17:36:07,039 DEBUG SQL:144 - insert into ChessSwissTournamentMen (gm, id) values (?, ?)

Conclusion

As you saw in this article, even though Hibernate doesn’t support the mixing of inheritance mapping strategies, you can use the @SecondaryTable annotation to define additional tables to which your entity class gets mapped. This enables you to map your entity classes to a table structure similar to the combination of InheritanceType.SINGLE_TABLE and InheritanceType.JOINED.

When using this, please be aware that for every query that selects one of the subclasses Hibernate will include a JOIN clause to all secondary tables defined by that subclass and its superclasses. This increases the complexity of the SQL statement and slows down its execution.

Such a complex mapping also makes it much harder to understand and maintain your persistence layer. Therefore, I recommend simplifying your mapping as far as possible and not using a secondary table mapping on multiple levels of your inheritance hierarchy.