The best way to use JPA bidirectional sync methods
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 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:

- the
Posthas a bidirectional one-to-many association with thePostCommentchild entity - the
Posthas a bidirectional one-to-one association with thePostDetailschild entity - the
Posthas a bidirectional many-to-many association with theTagentity
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
findByIdandgetRefernceById, 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
Postpreviously, 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.
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.






