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.

Facebook Comment about a tool like Hypersistence Optimizer

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:

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:

Hypersistence Optimizer All Table Relationship Entities

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 with Set collections. Using a List 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.

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.