Skip to main content

The best way to map a @OneToOne relationship with JPA and Hibernate

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.idbook_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:
OneToOne
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).
one-to-one
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.
one-to-one-shared-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 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 in employee bean we have to declare ref of address bean class in employee bean class. 
like 
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.

Comments

Post a Comment

Popular posts from this blog

How do I change the time zone of my Amazon RDS database instance?

As we know bydefault time in the format of UTC in mysql.We can set local time zone to our AWS RDS Instance for our application. or any other time zone prefared Cloud Based Website Hosting Service Provider Steps 1: Go to Services and Select RDS Now to change time zone we have to change "Parameter Group" in left side that is associated with DB instance first we can check default Parameter Group for our instance is Parameter group default.mysql5.7  ( in-sync ) like this. So we have to change the time zone in this Parameter Group.  now open that parameter group (default.mysql5.7)  and click on edit parameter. then search for time_zone (because we want to change it.) then we have to change time_zone only by default it is engine-default (that is utc)  we have to select Asia/Calcutta.  More information we can ref.  https://aws.amazon.com/premiumsupport/knowledge-center/rds-change-time-zone/

Changing the Time Zone on Amazon Linux Ec2 Instance

Amazon Linux instances are set to the UTC (Coordinated Universal Time) time zone by default, but you may wish to change the time on an instance to the local time or to another time zone in your network. Important These procedures are intended for use with Amazon Linux. For more information about other distributions, see their specific documentation. To change the time zone on an instance Identify the time zone to use on the instance. The  /usr/share/zoneinfo  directory contains a hierarchy of time zone data files. Browse the directory structure at that location to find a file for your time zone. [ec2-user ~]$ ls /usr/share/zoneinfo Africa Chile GB Indian Mideast posixrules US America CST6CDT GB-Eire Iran MST PRC UTC Antarctica Cuba GMT iso3166.tab MST7MDT PST8PDT WET Arctic EET GMT0 Israel Navajo right W-SU ... Some of the entries at this location are directo

Digital Marketing

What actually is Digital Marketing? This post will help you understand the insights of Digital Marketing What is Digital Marketing? Digital Marketing is an integral part of the overall marketing strategies of any business. It basically covers the advertisement of products/services/business/brand via digital channels. The digital channels could be of any type like websites, search engines, social media, emails, SMS, and MMS. In case if you're using all these digital channels for the marketing, make sure to have all the statistics & workflow of your campaigns via marketing automation. What are the types of digital marketing? Well, there are 6 core digital marketing types: Search Engine Optimization (SEO) : Search Engine Optimization is nothing but a long-term process of improving your website rankings on search engine results pages (SERPs), which in turn has a wide range of tactics & strategies to implement. Although there is no specific method or a  spec