The best way to use entity inheritance with JPA and Hibernate

Introduction

Recently, my friend Lukas Eder wrote the following message on Twitter:

Just like in any OOP (Object-Oriented Programming) language, entity inheritance is suitable for varying behavior rather than reusing data structures, for which we could composition. The Domain Model compromising both data (e.g. persisted entities) and behavior (business logic), we can still make use of inheritance for implementing behavioral software design pattern.

In this article, I’m going to demonstrate how to use JPA inheritance as a means to implement the Strategy design pattern.

Domain Model

Considering we have a notification system that needs to send both email and SMS to customers, we can model the notification relationships as follows:

Both the SmsNotification and EmailNotification inherit the base class Notification properties. However, if we use a RDBMS (relational database system), there is no standard way of implementing table inheritance, so we need to emulate this relationship. Usually, there are only two choices:

For this example, we are going to use the JOINED table approach which has the following database entity relationship diagram:

Bridging the gap

With JPA and Hibernate, mapping the OOP and the RDBMS models is straightforward.

The Notification base class is mapped as follows:

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

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Temporal( TemporalType.TIMESTAMP )
    @CreationTimestamp
    @Column(name = "created_on")
    private Date createdOn;

    //Getters and setters omitted for brevity
}

The SmsNotification and EmailNotification mappings looks like this:

@Entity
@Table(name = "sms_notification")
public class SmsNotification 
    extends Notification {

    @Column(
        name = "phone_number", 
        nullable = false
    )
    private String phoneNumber;

    //Getters and setters omitted for brevity
}
@Entity
@Table(name = "email_notification")
public class EmailNotification 
    extends Notification {

    @Column(
        name = "email_address", 
        nullable = false
    )
    private String emailAddress;

    //Getters and setters omitted for brevity
}

Business logic

So far, we only mapped the relationship between the OOP and the RDBMS data structures, but we haven’t covered the actual business logic which is required to send these notifications to our users.

For this purpose, we have the following NotificationSender Service components:

The NotificationSender has two methods:

  • appliesTo gives the entity that’s supported by this NotificationSender
  • send encapsulates the actual sending logic

The EmailNotificationSender is implemented as follows:

@Component
public class EmailNotificationSender 
    implements NotificationSender<EmailNotification> {

    protected final Logger LOGGER = LoggerFactory.getLogger( 
        getClass() 
    );

    @Override
    public Class<EmailNotification> appliesTo() {
        return EmailNotification.class;
    }

    @Override
    public void send(EmailNotification notification) {
        LOGGER.info( 
            "Send Email to {} {} via address: {}",
             notification.getFirstName(),
             notification.getLastName(),
             notification.getEmailAddress()
        );
    }
}

Of course, the actual sending logic was stripped away, but this is sufficient to understand how the Strategy pattern works.

However, the user does not have to interact with the NotificationSender directly. They only want to send a campaign, and the system should figure out the subscriber channels each client has opted for.

Therefore, we can use the Facade Pattern to expose a very simple API:

The NotificationSenderImpl is where all the magic happens:

@Service
public class NotificationServiceImpl 
    implements NotificationService {

    @Autowired
    private NotificationDAO notificationDAO;

    @Autowired
    private List<NotificationSender> notificationSenders;

    private Map<Class<? extends Notification>, NotificationSender> 
        notificationSenderMap = new HashMap<>();

    @PostConstruct
    @SuppressWarnings( "unchecked" )
    public void init() {
        for ( NotificationSender notificationSender : notificationSenders ) {
            notificationSenderMap.put( 
                notificationSender.appliesTo(), 
                notificationSender 
            );
        }
    }

    @Override
    @Transactional
    @SuppressWarnings( "unchecked" )
    public void sendCampaign(String name, String message) {
        List<Notification> notifications = notificationDAO.findAll();

        for ( Notification notification : notifications ) {
            notificationSenderMap
                .get( notification.getClass() )
                .send( notification );
        }
    }
}

There are several things to note in this implementation:

  • We make use of Spring List auto-wiring feature which I explained in my very first blog post. This way, we can inject any NotificationSender the user has configured in our system, therefore decoupling the NotificationService from the actual NotificationSender implementations our system us currently supporting.
  • The init method builds the notificationSenderMap which takes a Notification class type as the Map key and the associated NotificationSender as the Map value.
  • The sendCampaign method fetches a List of Notification entities from the DAO layer and pushes them to their associated NotificationSender instances.

Because JPA offers polymorphic queries, the findAll DAO method can be implemented as follows:

@Override
public List<T> findAll() {
    CriteriaBuilder builder = entityManager
        .getCriteriaBuilder();
        
    CriteriaQuery<T> criteria = builder
        .createQuery( entityClass );
    criteria.from( entityClass );

    return entityManager
        .createQuery( criteria )
        .getResultList();
}

The system does not have to know which are the actual Notification implementation each client has chosen. The polymorphic query is figured out at runtime by JPA and Hibernate.

Testing time

If we created the following Notification entities in our system:

SmsNotification sms = new SmsNotification();
sms.setPhoneNumber( "012-345-67890" );
sms.setFirstName( "Vlad" );
sms.setLastName( "Mihalcea" );

entityManager.persist( sms );

EmailNotification email = new EmailNotification();
email.setEmailAddress( "vlad@acme.com" );
email.setFirstName( "Vlad" );
email.setLastName( "Mihalcea" );

entityManager.persist( email );

And now we want to send a campaign:

notificationService.sendCampaign( 
    "Black Friday", 
    "High-Performance Java Persistence is 40% OFF" 
);

Hibernate executes the following SQL query:

SELECT  
    n.id AS id1_1_,
    n.created_on AS created_2_1_,
    n.first_name AS first_na3_1_,
    n.last_name AS last_nam4_1_,
    n1_.email_address AS email_ad1_0_,
    n2_.phone_number AS phone_nu1_2_,
    CASE WHEN n1_.id IS NOT NULL THEN 1
         WHEN n2_.id IS NOT NULL THEN 2
         WHEN n.id IS NOT NULL THEN 0
    END AS clazz_
FROM    
    notification n
LEFT OUTER JOIN 
    email_notification n1_ ON n.id = n1_.id
LEFT OUTER JOIN 
    sms_notification n2_ ON n.id = n2_.id

And the following output is logged:

EmailNotificationSender - Send Email to Vlad Mihalcea via address: vlad@acme.com

SmsNotificationSender - Send SMS to Vlad Mihalcea via phone number: 012-345-67890

Cool, right?

If you enjoyed this article, I bet you are going to love my book as well.

Conclusion

Entity Inheritance is a very useful technique, but only when you use it along with a behavioral software design pattern, such as Strategy or Visitor pattern.

If you only need to propagate certain properties from a base class to all subclasses, you don’t need JPA entity inheritance. All you need is the @MappedSuperclass annotation, but that’s not entity inheritance since the object hierarchy is only visible in the OOP domain, not in the relationship model.

If you liked this article, you might want to subscribe to my newsletter too.

Advertisements

14 thoughts on “The best way to use entity inheritance with JPA and Hibernate

  1. Hey Vlad,

    As far as I know, entity inheritance is generally avoided due to the fact that it is impossible to lazy-load it when referencing a non-leaf item in the inheritance tree.
    Does hibernate solve that somehow? Is a join with the JTI always required?

      1. Absolutely! I was just hoping for a pre-existing solution, but I guess that a join would be unavoidable to prevent eager loading of proxies.

      2. Even for a simple direct fetching, if you have a JOINED inheritance, Hibernate will issue the JOIN when you fetch the parent entity. The only ways to deal with it are:

        • switch to SINGLE_TABLE
        • use native SQL
      3. Hibernate will issue the JOIN when you fetch the parent entity.

        My doubt is specifically on the referencing bit. Say you have an inheritance Base <- A and Base <- B, and an association C - many-to-one -> Base, any time you load a C, will Hibernate perform an additional query? If so, can this be avoided?

      4. The identifier is as clear as the sun, but what about the proxy type? It should be either Base or A or B: that information is only available after querying the discriminator in the STI, or the CASE in the JTI

      5. Ah, I see where this is going. It’s a Base only. I was so focused on the problem that I forgot that the problem does not exist in this context 🙂 Thanks for clarifying it!

  2. Hi Vlad,

    thanks for sharing, a very informative article.

    I have a question regarding the ER-diagram. My understanding is, that it shows an n:1 relationship between SMS|EmailNotification and the Notification “Base” table.

    Does this really correctly model the class diagram?
    Wouldn’t a 1:1 relationship be more suitable here, as otherwise a notification would have a list of email addresses/ phone numbers?

    Thanks!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s