How to order entity subclasses by their class type using JPA and Hibernate

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.

So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!

Introduction

In this article, we are going to see how to order entity subclasses when executing a JPA query with Hibernate.

Domain Model

Assuming you have the following entity relationship model:

Inheritance domain model

And, considering we have persisted the following entities:

Board board = new Board();
board.setName("Hibernate");

entityManager.persist(board);

Post post = new Post();
post.setOwner("John Doe");
post.setTitle("Inheritance");
post.setContent("Best practices");
post.setBoard(board);

entityManager.persist(post);

Announcement announcement = new Announcement();
announcement.setOwner("John Doe");
announcement.setTitle("Release x.y.z.Final");
announcement.setValidUntil(
	Timestamp.valueOf(LocalDateTime.now().plusMonths(1))
);
announcement.setBoard(board);

entityManager.persist(announcement);

Ordering entities

Now, the requirement is too order entities as follows:

List<Topic> topics = entityManager
.createQuery(
    "select t " +
    "from Topic t " +
    "where t.board = :board " +
    "order by t.class", Topic.class)
.setParameter("board", board)
.getResultList();

assertEquals(2, topics.size());
assertTrue(topics.get(0) instanceof Announcement);
assertTrue(topics.get(1) instanceof Post);

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

The mapping for the SINGLE_TABLE inheritance looks as follows:

@Entity(name = "Topic")
@Table(name = "topic")
@Inheritance(
    strategy = InheritanceType.SINGLE_TABLE
)
public class Topic {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String owner;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdOn = new Date();

    @ManyToOne(fetch = FetchType.LAZY)
    private Board board;

    //Getters and setters omitted for brevity
}

@Entity(name = "Post")
public class Post extends Topic {

    private String content;

    //Getters and setters omitted for brevity
}

@Entity(name = "Announcement")
public class Announcement extends Topic {

    @Temporal(TemporalType.TIMESTAMP)
    private Date validUntil;

    //Getters and setters omitted for brevity
}

By default, for SINGLE_TABLE inheritance, a discriminator column DTYPE will be used to differentiate between subclasses.

SINGLE_TABLE inheritance table relationship

Hence, when executing the ORDER BY JPQL query, Hibernate the following SQL query:

SELECT t.id AS id2_1_,
       t.board_id AS board_id8_1_,
       t.createdOn AS createdO3_1_,
       t.owner AS owner4_1_,
       t.title AS title5_1_,
       t.content AS content6_1_,
       t.validUntil AS validUnt7_1_,
       t.DTYPE AS DTYPE1_1_
FROM topic t
WHERE t.board_id=?
ORDER BY t.DTYPE

Therefore, we get the expected results.

However, if we override the SINGLE_TABLE inheritance @DiscriminatorColumn strategy and provide a specific @DiscriminatorValue for each entity class, then the ordering will not follow the class name, but the one provided explicitly during entity mapping.

@Inheritance(strategy = InheritanceType.JOINED)

When using the JOINED inheritance strategy, the entity mappings looks as follows:

@Entity(name = "Topic")
@Table(name = "topic")
@Inheritance(strategy = InheritanceType.JOINED)
public class Topic {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String owner;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdOn = new Date();

    @ManyToOne(fetch = FetchType.LAZY)
    private Board board;

    //Getters and setters omitted for brevity
}

The table diagram for the JOINED inheritance strategy looks like this:

Joined table relationship

Since, this time, there is no discriminator column, our ORDER BY query will not be guaranteed to work and Hibernate generates the following SQL query:

SELECT t.id AS id1_3_,
       t.board_id AS board_id5_3_,
       t.createdOn AS createdO2_3_,
       t.owner AS owner3_3_,
       t.title AS title4_3_,
       t1_.content AS content1_2_,
       t2_.validUntil AS validUnt1_0_,
       CASE
           WHEN t1_.id IS NOT NULL THEN 1
           WHEN t2_.id IS NOT NULL THEN 2
           WHEN t.id IS NOT NULL THEN 0
       END AS clazz_
FROM topic t
LEFT OUTER JOIN post t1_ ON t.id=t1_.id
LEFT OUTER JOIN announcement t2_ ON t.id=t2_.id
WHERE t.board_id=?
ORDER BY 
    CASE
        WHEN t1_.id IS NOT NULL THEN 1
        WHEN t2_.id IS NOT NULL THEN 2
        WHEN t.id IS NOT NULL THEN 0
    END

The virtual discriminator (e.g. clazz_) is assigned during bootstrap and it’s based on how Hibernate entity classes got loaded. Although we can arrange the entity declarations so that the org.hibernate.mapping.Subclass.subclassId matches the entity class name in alphabetical order, this is not very reliable in the long run.

However, you can declare your own ORDER BY clause and provide a specific ordering criteria, as illustrated by the following example:

List<Topic> topics = entityManager
.createQuery(
    "select t " +
    "from Topic t " +
    "where t.board = :board " +
    "order by " +
    "   case " +
    "   when type(t) = Announcement then 10" +
    "   when type(t) = Post then 20 " +
    "   end", Topic.class)
.setParameter("board", board)
.getResultList();

When running the JPQL query above, Hibernate will generate the following SQL statement:

SELECT t.id AS id1_3_,
       t.board_id AS board_id5_3_,
       t.createdOn AS createdO2_3_,
       t.owner AS owner3_3_,
       t.title AS title4_3_,
       t1_.content AS content1_2_,
       t2_.validUntil AS validUnt1_0_,
       CASE
           WHEN t1_.id IS NOT NULL THEN 1
           WHEN t2_.id IS NOT NULL THEN 2
           WHEN t.id IS NOT NULL THEN 0
       END AS clazz_
FROM topic t
LEFT OUTER JOIN post t1_ ON t.id=t1_.id
LEFT OUTER JOIN announcement t2_ ON t.id=t2_.id
WHERE t.board_id=?
ORDER BY 
    CASE
        WHEN 
            CASE
                WHEN t1_.id IS NOT NULL THEN 1
                WHEN t2_.id IS NOT NULL THEN 2
                WHEN t.id IS NOT NULL THEN 0
            END = 2 
        THEN 10
        WHEN 
            CASE
                WHEN t1_.id IS NOT NULL THEN 1
                WHEN t2_.id IS NOT NULL THEN 2
                WHEN t.id IS NOT NULL THEN 0
            END = 1 
        THEN 20
    END

You can also use the same trick when you want to order entity subclasses by their entity name when using the SINGLE_TABLE inheritance strategy with an INTEGER discriminator column type.

@Inheritance(strategy = InheritanceType.JOINED) with @DiscriminatorColumn

To provide a specific discriminator column, we need to supply a @DiscriminatorColumn on the superclass entity mapping declaration:

@Entity(name = "Topic")
@Table(name = "topic")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn
public class Topic {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    private String owner;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdOn = new Date();

    @ManyToOne(fetch = FetchType.LAZY)
    private Board board;

    //Getters and setters omitted for brevity
}
[code lang="sql"]

Now, the base class table will contain a `DTYPE` column just like the `SINGLE_TABLE` entity inheritance strategy:

<a href="https://vladmihalcea.com/wp-content/uploads/2017/12/joinedwithdiscriminatorcolumn.png"><img src="https://vladmihalcea.com/wp-content/uploads/2017/12/joinedwithdiscriminatorcolumn.png" alt="Joined inheritance with discriminator column" class="alignnone size-large wp-image-8553" /></a>

So, when executing the ORDER BY JPQL query, Hibernate will generate the following SQL query:

[code lang="sql"]
SELECT t.id AS id2_3_,
       t.board_id AS board_id6_3_,
       t.createdOn AS createdO3_3_,
       t.owner AS owner4_3_,
       t.title AS title5_3_,
       t1_.content AS content1_2_,
       t2_.validUntil AS validUnt1_0_,
       t.DTYPE AS DTYPE1_3_
FROM topic t
LEFT OUTER JOIN post t1_ ON t.id=t1_.id
LEFT OUTER JOIN announcement t2_ ON t.id=t2_.id
WHERE t.board_id=?
ORDER BY t.DTYPE

Although normally you’d never need to use a @DiscriminatorColumn with the JOINED inheritance strategy, in this case, it is the only way to make sure you can order the entity subclasses by their class name.

Nevertheless, this implies adding a new column in the base class table which has to use the default STRING-based discriminator strategy.

I'm running an online workshop on the 20-21 and 23-24 of November about High-Performance Java Persistence.

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

As demonstrated many times on my blog, Hibernate is very flexible when it comes to addressing various data access requirements. In this article, you have seen that even the JOINED inheritance strategy can use a @DiscriminatorColumn as well as the use of CASE expressions based on the entity type.

Transactions and Concurrency Control eBook

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.