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:
- Complete Guide: Inheritance strategies with JPA and Hibernate
- Composition vs. Inheritance with JPA and Hibernate
- Hibernate Tips: How to select a specific subclass from an inheritance hierarchy
- Hibernate Tips: How to Customize a Constructor Expression for Different Subclasses
- Hibernate Tips: How to override column mappings of a superclass
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.
Nice blog post i like it your blog post