Hibernate Tips: SINGLE_TABLE strategy without discriminator column


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


Hibernate Tips is a series of posts in which I describe a quick and easy solution for common Hibernate questions. If you have a question for a future Hibernate Tip, please post a comment below.

Question:

I need to map tables of a legacy database using the SINGLE_TABLE strategy. But these tables don’t contain a discriminator column, and I can’t change the table definition.

Is there any other way to define the mapping to a specific subclass?

Solution:

Yes, Hibernate provides a proprietary annotation that allows you to provide an SQL snippet that returns a discriminator value. So, you don’t necessarily need a discriminator column.

But let me start at the beginning.

The SINGLE_TABLE strategy maps records from the same database table to different entity classes of an inheritance hierarchy.

If you want to use this strategy with JPA, your database table needs to have a discriminator column. The value in this column identifies the entity class to which each record shall be mapped.

By default, Hibernate uses the same approach. But if your database table doesn’t contain a discriminator column, you can use the @DiscriminatorFormula annotation to provide an SQL snippet that returns the discriminator value. In most cases, this snippet consists of a CASE expression, which checks if one or more columns contain a specific value.

Let’s take a look at an example.

Example mapping

The Publication entity is the superclass of the Book and BlogPost entities.

The Publication entity defines the mapping of all shared attributes, like the id as a generated primary key, the title as a simple String, the publishingDate as a LocalDate and the many-to-many relationship to the Author entity.

The SQL snippet, provided by the @DiscriminatorFormula annotation, returns the discriminator value for each record. If the url field of the record is not null, the SQL snippet returns BlogPost; otherwise, it returns Book.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula("case when url is not null then 'BlogPost' else 'Book' end")
public class Publication {

	@Id
	@GeneratedValue
	private Long id;

	@Version
	private int version;

	private String title;

	private LocalDate publishingDate;
	
	@ManyToMany
	private Set<Author> authors = new HashSet<Author>();

	...
}

The subclasses only need to extend the superclass and add the mapping for their specific attributes.

The @DiscriminatorValue annotation is optional. It defines which discriminator value shall be mapped to this class. It doesn’t make any difference if this value is stored in a discriminator column or determined by a @DiscriminatorFormular.

If you don’t annotate your subclass with a @DiscriminatorValue annotation, Hibernate uses the name of the entity as a default.

@Entity
@DiscriminatorValue("BlogPost")
public class BlogPost extends Publication {


	private String url;

	...
}
@Entity
@DiscriminatorValue("Book")
public class Book extends Publication {

	private int numPages;

	...
}

Running a simple test

Let’s do a quick test and check if the @DiscriminatorFormula annotation works as expected. As you can see in the following code snippet, I use a simple JPQL query to select a Book entity with a given id.

// read the Book entity
em = emf.createEntityManager();
em.getTransaction().begin();

TypedQuery q = em.createQuery(
		"SELECT b FROM Book b WHERE b.id = :id", Book.class);
q.setParameter("id", 1L);
b = q.getSingleResult();
Assert.assertTrue(b instanceof Book);
Assert.assertEquals(new Long(1), ((Book) b).getId());

log.info(b);

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

If you activate the logging of SQL statements, you can see that Hibernate includes the SQL snippet, that I defined in the @DiscriminatorFormula annotation, in the WHERE clause of the SQL query. It compares the result of the CASE expression to the String Book to ensure that the query only returns Book entities.

06:21:59,234 DEBUG [org.hibernate.SQL] - 
    select
        book0_.id as id1_1_,
        book0_.publishingDate as publishi2_1_,
        book0_.title as title3_1_,
        book0_.version as version4_1_,
        book0_.numPages as numPages5_1_ 
    from
        Publication book0_ 
    where
        case 
            when book0_.url is not null then 'BlogPost' 
            else 'Book' 
        end='Book' 
        and book0_.id=?
06:21:59,246 INFO  [org.thoughts.on.java.model.TestInheritance] - Book title: Hibernate Tips - More than 70 solutions to common Hibernate problems

Learn more:

If you’re using an inheritance hierarchy in your domain model, you might also enjoy reading the following articles:

Hibernate Tips Book

Get more recipes like this one in my new book Hibernate Tips: More than 70 solutions to common Hibernate problems.

It gives you more than 70 ready-to-use recipes for topics like basic and advanced mappings, logging, Java 8 support, caching, and statically and dynamically defined queries.

Get it now!

One Comment

  1. Avatar photo mateo miller says:

    Nice blog post i like it your blog post

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.