Fluent API entity building with JPA and Hibernate

Introduction

The JHipster development team wants to expose Fluent Interface entity building methods for their JPA entities, so they asked me if this is going to work with JPA and Hibernate. While JPA is rather strict about entity getters and setter, Hibernate is more lenient in this regard.

JPA specification

The JPA 2.1 specification makes the following remark in regard to entity properties:

It is required that the entity class follow the method signature conventions for JavaBeans read/write
properties (as defined by the JavaBeans Introspector class) for persistent properties when property
access is used.

In this case, for every persistent property property of type T of the entity, there is a getter method, getProperty, and setter method setProperty. For boolean properties, isProperty may be used as an alternative
name for the getter method.[2]

For single-valued persistent properties, these method signatures are:

• T getProperty()
• void setProperty(T t)

The reason why we get such a requirement is because the JPA specification makes no assumption regarding how entities are going to be used. By adhering to the Java Bean specifications, entities can be introspected using Java Reflection by IDE tools or other frameworks that might expect this standard getter and setter signature.

Hibernate specification

For interoperability, Hibernate suggests using the Java Bean specification as much as possible. However, Hibernate is less strict about Java Bean method signatures, so we can design our setters so that they follow the Fluent Interface method signature.

Domain Model

Our Domain Model is going to use two entities: a parent (e.g. Post) and a child (e.g. PostComment), both using Fluent Interface setter-style methods.

The Post entity looks like this:

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

    @Id
    private Long id;

    private String title;

    public Post() {}

    public Post(String title) {
        this.title = title;
    }

    @OneToMany(
        cascade = CascadeType.ALL, 
        orphanRemoval = true, 
        mappedBy = "post"
    )
    private List<PostComment> comments = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public Post setId(Long id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }

    public List<PostComment> getComments() {
        return comments;
    }

    public Post addComment(PostComment comment) {
        comment.setPost(this);
        comments.add(comment);
        return this;
    }
}

And the PostComment entity looks as follows:

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {

    @Id
    @GeneratedValue
    private Long id;

    private String review;

    private Date createdOn;

    @ManyToOne
    private Post post;

    public Long getId() {
        return id;
    }

    public PostComment setId(Long id) {
        this.id = id;
        return this;
    }

    public String getReview() {
        return review;
    }

    public PostComment setReview(String review) {
        this.review = review;
        return this;
    }

    public Date getCreatedOn() {
        return createdOn;
    }

    public PostComment setCreatedOn(Date createdOn) {
        this.createdOn = createdOn;
        return this;
    }

    public Post getPost() {
        return post;
    }

    public PostComment setPost(Post post) {
        this.post = post;
        return this;
    }
}

Testing Time

With the Fluent Interface API in place, we can create a Post entity and three PostComment(s) like this:

doInJPA(entityManager -> {
    Post post = new Post()
    .setId(1L)
    .setTitle("High-Performance Java Persistence")
    .addComment(
        new PostComment()
        .setReview("Awesome book")
        .setCreatedOn(Timestamp.from(
            LocalDateTime.now().minusDays(1).toInstant(ZoneOffset.UTC))
        )
    )
    .addComment(
        new PostComment()
        .setReview("High-Performance Rocks!")
        .setCreatedOn(Timestamp.from(
            LocalDateTime.now().minusDays(2).toInstant(ZoneOffset.UTC))
        )
    )
    .addComment(
        new PostComment()
        .setReview("Database essentials to the rescue!")
        .setCreatedOn(Timestamp.from(
            LocalDateTime.now().minusDays(3).toInstant(ZoneOffset.UTC))
        )
    );
    entityManager.persist(post);
});

Fetching the Post and the PostComment entities works just fine:

doInJPA(entityManager -> {
    Post post = entityManager.find(Post.class, 1L);
    assertEquals(3, post.getComments().size());
});

The generic JPA alternative

If you worry about JPA portability, you could simply add the Fluent Interface method along Java Bean setters:

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

    @Id
    private Long id;

    private String title;

    public Post() {}

    public Post(String title) {
        this.title = title;
    }

    @OneToMany(
        cascade = CascadeType.ALL, 
        orphanRemoval = true, 
        mappedBy = "post"
    )
    private List<PostComment> comments = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Post id(Long id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Post title(String title) {
        this.title = title;
        return this;
    }

    public List<PostComment> getComments() {
        return comments;
    }

    public Post addComment(PostComment comment) {
        comments.add(comment.post(this));
        return this;
    }
}

@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {

    @Id
    @GeneratedValue
    private Long id;

    private String review;

    private Date createdOn;

    @ManyToOne
    private Post post;

    public Long getId() {
        return id;
    }

    public PostComment setId(Long id) {
        this.id = id;
        return this;
    }

    public String getReview() {
        return review;
    }

    public void setReview(String review) {
        this.review = review;
    }

    public PostComment review(String review) {
        this.review = review;
        return this;
    }

    public Date getCreatedOn() {
        return createdOn;
    }

    public void setCreatedOn(Date createdOn) {
        this.createdOn = createdOn;
    }

    public PostComment createdOn(Date createdOn) {
        this.createdOn = createdOn;
        return this;
    }

    public Post getPost() {
        return post;
    }

    public void setPost(Post post) {
        this.post = post;
    }

    public PostComment post(Post post) {
        this.post = post;
        return this;
    }
}

To take advantage of the fluent-style API, we just need to use the new Fluent Interface methods while avoiding the Java Bean setters which might be used by some other third-party tools:

doInJPA(entityManager -> {
    Post post = new Post()
    .id(1L)
    .title("High-Performance Java Persistence")
    .addComment(new PostComment()
        .review("Awesome book")
        .createdOn(Timestamp.from(
            LocalDateTime.now().minusDays(1).toInstant(ZoneOffset.UTC))
        )
    )
    .addComment(new PostComment()
        .review("High-Performance Rocks!")
        .createdOn(Timestamp.from(
            LocalDateTime.now().minusDays(2).toInstant(ZoneOffset.UTC))
        )
    )
    .addComment(new PostComment()
        .review("Database essentials to the rescue!")
        .createdOn(Timestamp.from(
            LocalDateTime.now().minusDays(3).toInstant(ZoneOffset.UTC))
        )
    );
    entityManager.persist(post);
});

In fact, this is exactly how the JHipster team has thought of adding Fluent Interface entities.

Although this generic alternative is better from a portability perspective, if your enterprise application does not rely on Java Bean compliant setters, you are better off changing the setter signature according to the Fluent Interface pattern requirements.

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

Conclusion

The Fluent Interface pattern works just fine with Hibernate, so one more reason to consider it the JPA provider of choice.

Code available on GitHub.

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

Advertisements

6 thoughts on “Fluent API entity building with JPA and Hibernate

  1. I didn’t know about this Fluent Interface feature. But I always try to instintively make the code as fluent it possible. Thanks for the article, I will study more about this approach and JHipster.

      1. You highlight a very good point, and thanks for pointing me to the articles.

        The Lombok @Builder and @Data just saves you a lot of boilerplate code, but you are still free to override the generated equals and hashCode methods as you will do with any other POJO.

        I think the important thing here is to understand that you need to think about equality for JPA entities, how you implement it is I guess is down to personal preference 🙂

      2. As long as the implementation is correct, it does not matter how you generate the methods. However, it might be that the auto-generated @EqualsAndHashCode is consistent to how equality should work for JPA entities 😉

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