One-To-One Shared Primary Key Relationship
One to one relationship refers to the relationship between two entities/tables A and B in which one item/row of A may be linked with only one item/row of B, and vice versa.
In this example,
book
and book_detail
tables have a one-to-one relationship. A book has only one book detail, and a book detail belong to only one book.
book_detail.book_id
is a foreign key references to book.id
. book_detail
also uses its foreign key book_id
as primary key so-called shared primary key.@Id
declares the entity identifier.@Column
maps the entity's field with the table's column. If @Column
is omitted, the field name of the entity will be used as column name by default.@OneToOne
defines a one-to-one relationship between 2 entities.@JoinColumn
defines foreign key column and indicates the owner of the relationship.mappedBy
indicates the inverse of the relationship.unique = true
enforces the unique constraint, 1 address belongs to only 1 library.@MapsId
defines embedded primary key, book_detail.book_id
is embedded from book.id
Domain Model
For the following examples, I’m going to use the following
Post
and PostDetails
classes:
The
Post
entity is the parent, while the PostDetails
is the child association because the Foreign Key is located in the post_details
database table.Typical mapping
Most often, this relationship is mapped as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| @Entity (name = "PostDetails" ) @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 (fetch = FetchType.LAZY) @JoinColumn (name = "post_id" ) private Post post; public PostDetails() {} public PostDetails(String createdBy) { createdOn = new Date(); this .createdBy = createdBy; } //Getters and setters omitted for brevity } |
More, even the
Post
entity can have a PostDetails
mapping as well:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| @Entity (name = "Post" ) @Table (name = "post" ) public class Post { @Id @GeneratedValue private Long id; private String title; @OneToOne (mappedBy = "post" , cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false ) private PostDetails details; //Getters and setters omitted for brevity public void setDetails(PostDetails details) { if (details == null ) { if ( this .details != null ) { this .details.setPost( null ); } } else { details.setPost( this ); } this .details = details; } } |
However, this mapping is not the most efficient, as further demonstrated.
The
post_details
table contains a Primary Key (PK) column (e.g. id
) and a Foreign Key (FK) column (e.g. post_id
).
However, there can be only one
post_details
row associated with a post
, so it makes more sense to have the post_details
PK mirroring the post
PK.
This way, the
post_details
Primary Key is also a Foreign Key, and the two tables are sharing their PKs as well.PK and FK columns are most often indexed, so sharing the PK can reduce the index footprint by half, which is desirable since you want to store all your indexes into memory to speed up index scanning.
While the unidirectional
@OneToOne
association can be fetched lazily, the parent-side of a bidirectional @OneToOne
association is not. Even when specifying that the association is not optional
and we have the FetchType.LAZY
, the parent-side association behaves like a FetchType.EAGER
relationship. And EAGER fetching is bad.
This can be easily demonstrated by simply fetching the
Post
entity:
1
| Post post = entityManager.find(Post. class , 1L); |
Hibernate fetches the child entity as well, so, instead of only one query, Hibernate requires two select statements:
1
2
3
4
5
6
7
8
| SELECT p.id AS id1_0_0_, p.title AS title2_0_0_ FROM post p WHERE p.id = 1 SELECT pd.post_id AS post_id3_1_0_, pd.created_by AS created_1_1_0_, pd.created_on AS created_2_1_0_ FROM post_details pd WHERE pd.post_id = 1 |
Even if the FK is
NOT NULL
and the parent-side is aware about its non-nullability through the optional
attribute (e.g. @OneToOne(mappedBy = "post", fetch = FetchType.LAZY, optional = false)
), Hibernate still generates a secondary select statement.
For every managed entity, the Persistence Context requires both the entity type and the identifier,
so the child identifier must be known when loading the parent entity, and the only way to find the associated
so the child identifier must be known when loading the parent entity, and the only way to find the associated
post_details
primary key is to execute a secondary query.
Bytecode enhancement is the only viable workaround. However, it only works if the parent side is annotated with
@LazyToOne(LazyToOneOption.NO_PROXY)
and the child side is not using @MapsId
.The most efficient mapping
The best way to map a
@OneToOne
relationship is to use @MapsId
. This way, you don’t even need a bidirectional association since you can always fetch the PostDetails
entity by using the Post
entity identifier.
The mapping looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| @Entity (name = "PostDetails" ) @Table (name = "post_details" ) public class PostDetails { @Id private Long id; @Column (name = "created_on" ) private Date createdOn; @Column (name = "created_by" ) private String createdBy; @OneToOne (fetch = FetchType.LAZY) @MapsId private Post post; public PostDetails() {} public PostDetails(String createdBy) { createdOn = new Date(); this .createdBy = createdBy; } //Getters and setters omitted for brevity } |
This way, the
id
column serves as both Primary Key and FK. You’ll notice that the @Id
column no longer uses a @GeneratedValue
annotation since the identifier is populated with the identifier of the post
association.
The
PostDetails
entity can be persisted as follows:
1
2
3
4
5
6
| doInJPA(entityManager -> { Post post = entityManager.find(Post. class , 1L); PostDetails details = new PostDetails( "John Doe" ); details.setPost(post); entityManager.persist(details); }); |
And we can even fetch the
PostDetails
using the Post
entity identifier, so there is no need for a bidirectional association:
1
2
3
4
| PostDetails details = entityManager.find( PostDetails. class , post.getId() );
copied from : https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
Note : Special Cases and Tips : Tip 1 : Suppose We want unidirectional Relation (Like Every Employe has address) then here we have to make 2 beans employee bean and address bean.
now we have to annotate this address variable as @OneToOne
// public Address address.
// setter and getter
now employee class holds single ref of address class.
(Keep in mind address class will create its own table so we have to declare @Entity and @Table annotation and we have also declare and id to it by defining a variable like address.id)
Keep in mind while saving your parent data if you want to save child data as well as then you have to use cascade type. if you don't define cascade type then manually we have to save parent entity and child entity seperatly.
by doing this undirection away we are trying to maintain fk key of address entity in employee entity class.
If we don't specify fk name then hibernate will pickup automatically as entity class (address) and its pk id like (address(under-score "_")address_pk_id) as column name. If we want to define our custom cloumn name for this column then we have to use @joinColumn(name = "fk column name that you want for this column") annotation.
Now when you fetch parent entity then it will automatically fetch child record as well as.
Disadvantage. suppose we want fetch the record based on child entity like address id then we cann't
Bidirectional @OnetoOne relationship Tips
suppose we have one to one relation between our entities like employee and address. suppose we are not using cascade type in @OneToOne annotation and while saving parent entity we will get exception from hibernate. so either we have to save both entity separately or we have to use cascade type.
if we have separately then again we have to 2 case
case : 1) Suppose we saved employee entity first (Parent entity that consist of fk id) and then we saved address entity.
case : 2) suppose i saved child entity first and then i saved parent entity
so actually what happend here if we go for 1st case then hibernate will fire 3 sql query 2 for insert and one for update of fk.
but if we go for first case then it will fire only 2 select query because while inserting parent entity hibernate knows the fk id. thats the difference.
Note: you can use cascade type but keep in mind if you use cascade type ALL. then we have to use save method. we can also use cascade type = PERSIST but for that we have to use persist method.
Note: If we want to achieve bidirectional relationship then we have to declare parent entity reference in child entity as well as.
like :
@OneToOne(mappedBy="its ref ownership in parent table like address ref variable name")
// public Employee emp;
if we don't define mappedBy then hibernate will add one more column in address table.
|
This blog gives more useful information, I enjoyed to read this blog.
ReplyDeleteDevOps Training in Bangalore
Best DevOps Training in Bangalore
AWS Training in Bangalore
Data Science Courses in Bangalore
DevOps Course in Bangalore
DevOps Training Bangalore
rpa training in bangalore
Python Training in Bangalore
PHP Training in bangalore
spoken english classes in bangalore