The best way to use JPA bidirectional sync methods

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 what are the best way to use JPA bidirectional sync methods for one-to-many, one-to-one, and many-to-many associations.

For an introduction to why you need sync methods for your bidirectional JPA associations, check out this article first.

Domain Model

For this article, we are going to use the following entities that are linked using bidirectional associations:

JPA bidirectional sync methods entities

  • the Post has a bidirectional one-to-many association with the PostComment child entity
  • the Post has a bidirectional one-to-one association with the PostDetails child entity
  • the Post has a bidirectional many-to-many association with the Tag entity

JPA bidirectional sync methods

In order for the bidirectional associations to work properly, we have defined several sync methods.

In the Post entity, the addComment and removeComment methods provide the synchronization of the bidirectional association:

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

public Post removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
    return this;
}

The setDetails method allows the Post and the PostDetails entities to be synchronized whenever the child entity is added or removed:

public Post setDetails(PostDetails details) {
    if (details == null) {
        if (this.details != null) {
            this.details.setPost(null);
        }
    }
    else {
        details.setPost(this);
    }
    this.details = details;
    return this;
}

And for the many-to-many association, we have the addTag and removeTag sync method:

public Post addTag(Tag tag) {
    tags.add(tag);
    tag.getPosts().add(this);
    return this;
}

public void removeTag(Tag tag) {
    tags.remove(tag);
    tag.getPosts().remove(this);
}

Now, let’s see when we should use these methods.

Cascading persist from a parent entity

If you have a root entity, it’s very convenient to cascade the persist entity state transition from the root entity, Post in our case, to the other entities, like this:

postRepository.persist(
    new Post()
        .setId(1L)
        .setTitle("High-Performance Java Persistence")
        .setDetails(new PostDetails().setCreatedBy("Vlad Mihalcea"))
        .addComment(
            new PostComment()
                .setReview("Best book on JPA and Hibernate!")
        )
        .addComment(
            new PostComment()
                .setReview("A must-read for every Java developer!")
        )
        .addTag(new Tag().setName("JDBC"))
        .addTag(new Tag().setName("Hibernate"))
        .addTag(new Tag().setName("jOOQ"))
);

By using the bidirectional add sync methods, we can ensure that the persist entity state transition is going to be propagated properly. Without synchronizing both sides of the JPA association, it’s not guaranteed that the entity state will be properly synchronized with the database.

Persisting a child entity without fetching the parent entity

If you need to persist a PostComment entity but you don’t need to fetch the Post entity, then you can do it like this:

postCommentRepository.persist(
    new PostComment()
        .setPost(postRepository.getReferenceById(1L))
        .setReview(
            "Very informative. Learned a lot, applied every day."
        )
);

When executing the code snippet above, Hibernate will generate the following SQL INSERT statement:

INSERT INTO post_comment (
    post_id, 
    review, 
    id
) 
VALUES (
    1, 
    'Very informative. Learned a lot, applied every day.',
    3
)

By using getRefernceById, you can fetch a parent entity Proxy that will set the Foreign Key column value without triggering an unnecessary entity fetch.

If you are interested in the difference between findById and getRefernceById, check out this article for more details.

Persisting a child entity when the parent entity is already fetched

If you already fetched the parent Post entity and want to persist a new PostComment, then you need to use the addComment sync method because both the parent and the child will be managed by the current Persistence Context:

Post post = postRepository.findByIdWithDetailsAndComments(1L);

post.addComment(
    new PostComment()
        .setReview(
            "Very informative. Learned a lot, applied every day."
        )
);

When executing the code snippet above, we can see that the INSERT follows the SELECT that we already executed previously.

SELECT 
    p.id,
    pc.post_id,
    pc.id,
    pc.review,
    pd.post_id,
    pd.created_by,
    pd.created_on,
    p.title 
FROM 
    post p 
JOIN 
    post_details pd ON p.id = pd.post_id 
JOIN 
    post_comment pc ON p.id = pc.post_id 
WHERE 
    p.id = 1

INSERT INTO post_comment (
    post_id, 
    review, 
    id
) 
VALUES (
    1, 
    'Very informative. Learned a lot, applied every day.',
    3
)

However, if you didn’t fetch the Post previously, then we don’t need to fetch it just to add or remove a single child entity.

The sync methods are useful when the entity propagating the state transitions is already fetched.

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.

Conclusion

When you have bidirectional associations, it’s important to create the sync methods that allow you to set both sides of the bidirectional association.

However, just because you created these methods, it doesn’t mean you need to call them every time you want to persist or remove a child entity. You only have to use them if you have already fetched the parent entity. Otherwise, you can simply add or remove the child entity without executing any extra SELECT statement.

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.