Fluent API entity building with JPA and Hibernate
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
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.
I would be very happy to have your insight @vlad_mihalcea on https://t.co/2c9tylORh2
— JHipster (@jhipster) August 3, 2016
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.
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.
