Mapping 1-1 relationship in Hibernate ORM
Update notes:
30/08/2025: add use case for 1-1 mapping in RDBMS.
Context
- Hibernate uses
@OneToOneannotation to model 1-1 relationship in RDBMS. When we want to travel through entity relationship, we model these relationships as bidirectional ones. But withone-to-onerelationship,hibernatewill trigger additional query to fetch child entity if we map the relationship like others relationship. - In hibernate context, an object will be identified by
idandentity type(aka class) [1].
Solution
When we model relationship as regular bidirectional one-to-one relationship like two entity below:
public class QuizEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = LAZY, mappedBy = "quiz", optional = false)
private QuizResultEntity result;
// other fields
}
public class QuizResultEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = LAZY, optional = false)
@JoinColumn(name = "quiz_id")
private QuizEntity quiz;
// other fields
}
and fetch an entity of QuizEntity type, hibernate will trigger an additional query to check whether the associated QuizResultEntity object
existing or not to provide appropriate strategy to initialize relationship. If QuizResultEntity object exists, hibernate will create
a proxy, otherwise this field is set to null.
So if we add optional = false properties to @OneToOne annotation, will hibernate trigger additional query?
Unfortunately, based on [1], hibernate will still trigger additional query because the identifier of QuizResultEntity is unknown.
Query log:
select q1_0.id from quiz q1_0 where q1_0.id=?
select q1_0.id,q1_0.quiz_id from quiz_result q1_0 where q1_0.quiz_id=?
-> Use @OneToOne mapping with @MapIds at QuizResultEntity side can solve this problem. This will work because at QuizEntity side, hibernate knows the ID of associated QuizResult entity when it queries data.
public class QuizResultEntity {
@Id
// Note: we don't use auto generate strategy here.
// @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(fetch = LAZY)
@MapsId
@JoinColumn(name = "quiz_id")
private QuizEntity quiz;
// other fields
}
Query log:
select q1_0.id from quiz q1_0 where q1_0.id=?
Use case
With these complex mappings, why on earth would you want to use them? Of course, it does not exist for fun. There are many cases in which one-to-one mappings are useful.
- Vertical partition: when an entity has many fields, we can split it into multiple tables based on query pattern to improve performance. For example, we have a
Userentity with many fields, we can split it intoUserandUserProfileentities. TheUserProfileentity will have a 1-1 relationship with theUserentity.- The
user_idwill be the mapping key. - These tables may reside in separate physical databases.
- The
- Avoid
NULLsand subtype discrimination: when an entity has multiple subtypes, for example, we have aProductentity with subtypesSoftware_ProductandHardware_Product, we can use 1-1 mapping to store the common fields in theProducttable and the subtype-specific fields in separate tables.
Appendix
Example source code: https://github.com/dntam00/learning-spring/tree/master/one-to-one