Fluent API entity building with JPA and Hibernate

Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Wouldn’t that be just awesome?

Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, or Play Framework.

So, enjoy spending your time on the things you love rather than fixing performance issues in your production system on a Saturday night!

Introduction

In this article, we are going to see how we can build an entity in a fluent style API fashion when using JPA and Hibernate.

The JHipster development team wants to expose a 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:

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:

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:

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.

I'm running an online workshop on the 20-21 and 23-24 of November about High-Performance Java Persistence.

If you enjoyed this article, I bet you are going to love my Book and Video Courses 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.

Transactions and Concurrency Control eBook

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.