There are several ways to map One To One relationship in JPA and Hibernate by using @OneToOne annotation including

  • Foreign key unidirectional and bidirectional mapping with @JoinColumn

  • Shared primary key unidirectional and bidirectional mapping with @MapsId and @PrimaryKeyJoinColumn

This guide will show you how to map along with the pros and cons of each approach

Consider the relationship between a person and an id card. One person has only one id card, and one id card only belongs to one person

Foreign key bidirectional mapping with @JoinColumn and @OneToOne

@OneToOne would be placed on both entities of the relationship, the mappedBy attribute value points to the relationship owner which has a foreign key column in the underlying table

@Entity
public class Person {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;

    @OneToOne(cascade = CascadeType.ALL, optional = false)
    @JoinColumn(name = "id_card_id")
    private IDCard idCard;

    ...
}
@Entity
public class IDCard {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(unique = true, nullable = false)
    private String code = UUID.randomUUID().toString();

    @OneToOne(mappedBy = "idCard")
    private Person person;

    ...
}

@JoinColumn specifies the foreign key column. It has the default name to the underscore string join of the association field name and its primary key column name

The mappedBy attribute on @OneToOne is required to specify for bidirectional mapping. If mappedBy is absent, JPA and Hibernate will create a redundant foreign key column pointed back to the relationship owner

mysql> describe idcard;  
+-----------+--------------+------+-----+---------+----------------+
| Field     | Type         | Null | Key | Default | Extra          |
+-----------+--------------+------+-----+---------+----------------+
| id        | int(11)      | NO   | PRI | NULL    | auto_increment |
| code      | varchar(255) | NO   | UNI | NULL    |                |
| person_id | int(11)      | NO   | UNI | NULL    |                |
+-----------+--------------+------+-----+---------+----------------+

Pros and cons

  • Foreign key mapping is more native and easy to understand than shared primary key mapping

  • The parent entity can quickly access and cascade CRUD operations to the child association

Hands-on tutorials

Foreign key unidirectional mapping with @JoinColumn and @OneToOne

@OneToOne would be placed on only 1 entity of the one-to-one relationship. We would not need to specify the mappedBy attribute

@Entity
public class Person {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;

    @OneToOne(cascade = CascadeType.ALL, optional = false)
    @JoinColumn(name = "id_card_id")
    private IDCard idCard;

    ...
}
@Entity
public class IDCard {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(unique = true, nullable = false)
    private String code = UUID.randomUUID().toString();

    ...
}

@JoinColumn specifies the foreign key column. It has the default name to the underscore string join of the association field name and its primary key column name

Pros and cons

  • Foreign key mapping is more native and easy to understand than shared primary key mapping

  • Foreign key unidirectional mapping is less verbose than the bidirectional mapping

Shared primary key bidirectional mapping with @MapsId and @OneToOne

The same primary key would be used by both entities of the relationship. In the child entity, the shared primary key is also the foreign key

@Entity
public class Person {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "person")
    private IDCard idCard;

    ...
}

@Entity
public class IDCard implements Serializable {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @OneToOne(cascade = CascadeType.ALL, optional = false)
    @JoinColumn(name = "person_id")
    @MapsId
    private Person person;

    ...
}

@OneToOne is marked on both entities. On the child entity, @MapsId is marked on the same association with @OneToOne

@JoinColumn specifies the foreign key column. It has the default name to the underscore string join of the association field name and its primary key column name

Pros and cons

  • The parent entity can quickly access and cascade CRUD operations to the child association

  • Foreign key mapping would be more native and easy to understand than shared primary key mapping

Hands-on tutorials

Shared primary key unidirectional mapping with @PrimaryKeyJoinColumn and @OneToOne

The same primary key would be used by both entities of the relationship. In the child entity, the shared primary key is also the foreign key

@Getter @Setter
@Entity
public class Person {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String name;

    ...
}

@Entity
public class IDCard {  
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int personId;

    @OneToOne(cascade = CascadeType.ALL, optional = false)
    @PrimaryKeyJoinColumn(name = "person_id", referencedColumnName = "id")
    private Person person;

    @Column(unique = true, nullable = false)
    private String code = UUID.randomUUID().toString();

    ...
}

@OneToOne is only marked on child entity along with @PrimaryKeyJoinColumn

Pros and cons

  • The parent entity cannot access or cascade CRUD operations to the child association

Hands-on tutorials

In this article, we had a quick look at various ways to map One to One relationship in JPA and Hibernate along with the notes on their pros and cons