The best way to map multiple entities on the same table
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 is the best way to map multiple entities on the same table.
There are several advantages to mapping multiple entities on the same database table:
- To avoid loading large columns (e.g., JSON)
- To avoid N+1 query issues for bidirectional
@OneToOneassociations
Before we start investigating the best way to map multiple entities on the same table, if you wonder why you even need to use one-to-one table relationships, then check out this article first.
Domain Model
Let’s assume we are using the following JPA entities:

The Post entity is mapped like this:
@Entity
@Table(name = "post")
public class Post {
⠀
@Id
private Long id;
⠀
private String title;
⠀
@OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private PostDetails details;
}
The PostDetails entity uses the @MapsId annotation to map the one-to-one table relationship by sharing the Primary Key with the parent post table:
@Entity
@Table(name = "post_details")
public class PostDetails {
⠀
@Id
private Long id;
⠀
@Column(name = "created_on")
private LocalDateTime createdOn = LocalDateTime.now();
⠀
@Column(name = "created_by")
private String createdBy;
⠀
@OneToOne(fetch = FetchType.LAZY)
@MapsId
@JoinColumn(name = "id")
private Post post;
}
Fetching the parent entity
The problem with the bidirectional @OneToOne association is that when you fetch the parent entity:
Post post = entityManager.find(Post.class, 1L);
The child entity is fetched using a secondary query in spite of the FetchType.LAZY strategy:
SELECT
p.id,
p.title
FROM
post p
WHERE
p.id = 1
SELECT
pd.id,
pd.created_by,
pd.created_on
FROM
post_details pd
WHERE
pd.id = 1
ERROR [main]: Hypersistence Optimizer - CRITICAL - SecondaryQueryEntityFetchingEvent -
The [io.hypersistence.optimizer.hibernate.session.merge.jpa.extra.PostDetails] entity
with the identifier value of [1] has been loaded using a secondary query
instead of being fetched using a JOIN FETCH clause.
The SecondaryQueryEntityFetchingEvent is reported by Hypersistence Optimizer because fetching the parent entity triggered a secondary query to fetch the child entity as well.
Mapping an additional entity on the post table
To avoid the secondary query that is triggered by fetching the parent entity, we have two options:
For this article, we are going to choose the second option and map the following PostSummary entity:
@Entity
@Table(name = "post")
public class PostSummary {
⠀
@Id
private Long id;
⠀
private String title;
}
Because we avoid mapping the details association, when fetching the PostSummary entity:
PostSummary postSummary = entityManager.find(PostSummary.class, 1L); ⠀ assertNoEventTriggered(SecondaryQueryEntityFetchingEvent.class);
A single SQL query is generated this time:
SELECT
p.id,
p.title
FROM
post p
WHERE
p.id = 1
However, now we risk fetching both the Post and the PostSummary in the same Persistence Context and end in a Last Writer Wins situation:
Post post = entityManager.find(Post.class, 1L);
PostSummary postSummary = entityManager.find(PostSummary.class, 1L);
⠀
post.setTitle("High-Performance Java Persistence, 2nd edition");
postSummary.setTitle("High-Performance Java Persistence, second edition");
⠀
assertEventTriggered(1, TableRowAlreadyManagedEvent.class);
Luckily, if Hypersistence Optimizer is enabled, a TableRowAlreadyManagedEvent will be triggered to notify you about this issue:
ERROR [main]: Hypersistence Optimizer - CRITICAL - TableRowAlreadyManagedEvent -
The table row associated with the
[io.hypersistence.optimizer.hibernate.session.merge.jpa.extra.PostSummary] entity
with the identifier value of [1] is already managed by the
[io.hypersistence.optimizer.hibernate.session.merge.jpa.extra.Post] entity
in the current Hibernate Session.
UPDATE
post
SET
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
UPDATE
post
SET
title = 'High-Performance Java Persistence, second edition'
WHERE
id = 1
So, if you are using JPA and Hibernate, Hypersistence Optimizer will surely help you get the best out of your data access layer.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
When mapping a one-to-one table relationship, it’s better to avoid mapping the association on the parent side.
However, if you inherit a legacy application and cannot just drop the parent-side association, then you can use a secondary entity that doesn’t map the child entity association. Nevertheless, you will have to make sure that you don’t end up fetching both entities that map the same table record.






