Hypersistence Optimizer initial release
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
At the end of 2018, I got this idea of writing a tool which can automatically detect JPA and Hibernate issues by scanning your data access layer and provide you optimization tips.
At the beginning of February, Thodoris Chaikalis surprised me with this Facebook comment which reinforced the idea that having such a tool would be really awesome for Java developers working with JPA and Hibernate.
At the end of February, I got some time off, and I started working on it, and the reaction on social media exceeded my expectations:
Imagine having a tool that can automatically detect if you are using @Java Persistence and #Hibernate properly.
— Vlad Mihalcea (@vlad_mihalcea) February 26, 2019
No more performance issues, no more silly mistakes that can cost you a lot of time and money.
Soon, you can have this tool. Stay tuned for more details! pic.twitter.com/CRYx4tVPif
Today, I’m happy to announce to you that the initial version is ready.
Hypersistence Optimizer has finally arrived!
Testing time
Let’s assume our application defines four JPA entities: Post
, PostDetails
, PostComment
, and Tag
which are associated as follows:
JPA entity mapping
The Post
entity is mapped like this:
@Entity @Table(name = "post") public class Post { @Id @GeneratedValue private Long id; private String title; @OneToMany( mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true ) private List<PostComment> comments = new ArrayList<>(); @OneToOne( mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY ) private PostDetails details; @ManyToMany @JoinTable( name = "post_tag", joinColumns = @JoinColumn(name = "post_id"), inverseJoinColumns = @JoinColumn(name = "tag_id") ) private List<Tag> tags = new ArrayList<>(); //Getters and setters omitted for brevity public void addComment(PostComment comment) { comments.add(comment); comment.setPost(this); } public void removeComment(PostComment comment) { comments.remove(comment); comment.setPost(null); } public void addDetails(PostDetails details) { this.details = details; details.setPost(this); } public void removeDetails() { this.details.setPost(null); this.details = null; } }
The PostDetails
has a one-to-one relationship with the parent Post
entity and is mapped as follows:
@Entity @Table(name = "post_details") public class PostDetails { @Id @GeneratedValue private Long id; @Column(name = "created_on") private Date createdOn; @Column(name = "created_by") private String createdBy; @OneToOne private Post post; //Getters and setters omitted for brevity }
The PostComment
has a many-to-one relationship with the parent Post
entity and is mapped as illustrated by the following code snippet:
@Entity @Table(name = "post_comment") public class PostComment { @Id @GeneratedValue private Long id; @ManyToOne private Post post; private String review; //Getters and setters omitted for brevity }
The Tag
entity is mapped like this:
@Entity @Table(name = "tag") public class Tag { @Id @GeneratedValue private Long id; private String name; //Getters and setters omitted for brevity }
Hypersistence Optimizer Configuration
Now let’s instantiate the HypersistenceOptimizer
object by passing it the current Hibernate SessionFactory
and call the init
method:
new HypersistenceOptimizer( new HibernateConfig( sessionFactory() ) );
When checking the application log, we can see the following optimization tips:
ERROR [main]: Hypersistence Optimizer - CRITICAL - EagerFetchingEvent - The [post] attribute in the [io.hypersistence.optimizer.hibernate.mapping.association.PostDetails] entity uses eager fetching. Consider using a lazy fetching which, not only that is more efficient, but it is way more flexible when it comes to fetching data. ERROR [main]: Hypersistence Optimizer - CRITICAL - OneToOneWithoutMapsIdEvent - The [post] one-to-one association in the [io.hypersistence.optimizer.hibernate.mapping.association.PostDetails] entity is using a separate Foreign Key to reference the parent record. Consider using @MapsId so that the identifier is shared with the parent row. ERROR [main]: Hypersistence Optimizer - CRITICAL - EagerFetchingEvent - The [post] attribute in the [io.hypersistence.optimizer.hibernate.mapping.association.PostComment] entity uses eager fetching. Consider using a lazy fetching which, not only that is more efficient, but it is way more flexible when it comes to fetching data. ERROR [main]: Hypersistence Optimizer - CRITICAL - ManyToManyListEvent - The [tags] many-to-many association in the [io.hypersistence.optimizer.hibernate.mapping.association.Post] entity is using a List so it does not render very efficient SQL statements. Consider using a Set instead. ERROR [main]: Hypersistence Optimizer - CRITICAL - OneToOneParentSideEvent - The [details] one-to-one association in the [io.hypersistence.optimizer.hibernate.mapping.association.Post] entity is mapped as the parent-side of this relationship. The parent-side is fetched eagerly unless bytecode enhancement lazy loading is employed, and even then, there are limitations to how the one-to-one association can be mapped. You should consider mapping the child-side only with @MapsId so that you can always reference the parent entity while having a reference to the child.
How cool is that, right?
Now let’s review these optimization tips:
- As I explained in this article, EAGER fetching is very bad for performance since it can lead to N+1 query issues, and the EAGER fetching strategy cannot be overridden on a per-query basis.
- The parent-side of the
@OneToOne
association is also fetched eagerly unless bytecode enhancement is used. Check out this article for more details. - Only if we are using
@MapsId
on the client-side of the@OneToOne
association will the JPA association map a real one-to-one table relationship. Otherwise, we get a one-to-many table relationship where the Foreign Key is unique. - The
@ManyToMany
association works better withSet
collections. Using aList
will generate more queries than necessary. For more details, check out this article.
I'm running an online workshop on the 11th of October about High-Performance SQL.If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Stay tuned for more!
If you’re curious about how many issues your current project has, you can download and install the trial version.
There are also plenty of examples in the hypersitence-optimizer
GitHub repository you can use as well,
Now, this is just the initial release. The next releases are going to add support for Hibernate configuration, so stay tuned for more awesome performance optimizations.
