The best way to map a @NaturalId business key with JPA and Hibernate

Introduction

As I explained in this free chapter of my book, Hibernate offers many benefits over standard JPA. One such example is the @NaturalId mapping.

In this article, you are going to see what is the best way to map a natural business key when using Hibernate.

Domain Model

considering we have the following Post entity:

The slug attribute is the business key for our Post entity. As I explained previously, we use a surrogate key as well because it’s much more compact and it puts less pressure on memory for both table and index pages.

The id property, being the entity identifier, can be marked with the JPA @Id annotation, but for the slug attribute, we need a Hibernate-specific annotation: @NaturalId.

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @NaturalId
    @Column(nullable = false, unique = true)
    private String slug;

    //Getters and setters omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) 
            return false;
        Post post = (Post) o;
        return Objects.equals(slug, post.slug);
    }

    @Override
    public int hashCode() {
        return Objects.hash(slug);
    }
}

As I explained previously, implementing equals and hashCode is straightforward when the entity defines a natural identifier.

If the entity does not define a natural identifier, implementing equals and hashCode should be done as I explained in this article.

Natural id fetching

Hibernate allows you to fetch entities either directly, via the entity identifier, or through a JPQL or SQL query.

Just like with the JPA @Id annotation, the @NaturalId allows you to fetch the entity if you know the associated natural key.

So, considering you have the following Post entity:

Post post = new Post();
post.setTitle("High-Performance Java persistence");
post.setSlug("high-performance-java-persistence");

entityManager.persist(post);

Knowing the natural key, you can now fetch the Post entity as follows:

String slug = "high-performance-java-persistence";

Post post = entityManager.unwrap(Session.class)
.bySimpleNaturalId(Post.class)
.load(slug);

If you have a single @NaturalId attribute defined in your entity, you should always use the bySimpleNaturalId method. However, in case you have a compound @NaturalId, meaning that you declared more than one @NaturalId properties, then you need to use the byNaturalId method instead:

Post post = entityManager.unwrap(Session.class)
.byNaturalId(Post.class)
.using("slug", slug)
.load();

That’s great because the slug attribute is what the client will see in the browser address bar. Since the post URL can be bookmarked, we can now load the Post by the slug attribute sent by the client.

However, to fetch the entity by its natural key, Hibernate generates the following SQL statements:

SELECT p.id AS id1_0_
FROM post p
WHERE p.slug = 'high-performance-java-persistence'

SELECT p.id AS id1_0_0_,
       p.slug AS slug2_0_0_,
       p.title AS title3_0_0_
FROM post p
WHERE p.id = 1

The first query is needed to resolve the entity identifier associated with the provided natural identifier.

The second query is optional if the entity is already loaded in the first or the second-level cache.

The reason for having the first query is because Hibernate already has a well-established logic for loading and associating entities by their identifier in the Persistence Context.

Optimizing the entity identifier retrieval

Just like you can avoid hitting the database to fetch an entity, you can skip the entity identifier retrieval by its associated natural key using the Hibernate @NaturalIdCache:

@Entity(name = "Post")
@Table(name = "post")
@org.hibernate.annotations.Cache(
    usage = CacheConcurrencyStrategy.READ_WRITE
)
@NaturalIdCache
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @NaturalId
    @Column(nullable = false, unique = true)
    private String slug;

    //Getters and setters omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) 
            return false;
        Post post = (Post) o;
        return Objects.equals(slug, post.slug);
    }

    @Override
    public int hashCode() {
        return Objects.hash(slug);
    }
}

We also annotated the entity using the Hibernate-specific @Cache annotation so that we declare a READ_WRITE Cache Concurrency Strategy.

This time, when running the previous example and fetch the Post entity, Hibernate generates zero SQL statements.

Because the READ_WRITE Cache Concurrency Strategy is write-through, the Post entity is cached during the persist operation, along with the natural key to identifier mapping.

If we were using NONSTRICT_READ_WRITE Cache Concurrency Strategy, the Post entity would be cached upon being accessed for the very first time.

However, for READ_WRITE, we don’t have to hit the database at all when fetching our Post entity. Cool, right?

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

Conclusion

The @NaturalId annotation is a very useful Hibernate feature that allows you to retrieve entities by their natural business key without even hitting the database.

Enter your email address to follow this blog and receive notifications of new posts by email.

Advertisements

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