Hibernate / JPA One to One Mappings

Learn to create and manage one-to-one relationships between entities in a hibernate/JPA-based application using @OneToOne annotation. We will be learning to create the association in 4 different ways.

1. Overview

We are taking the example of an employee and his account having one to one relationship. We are assuming that

  • an employee can have only one Account
  • an account will be associated with one employee only
  • EmployeeEntity is the owner of the relationship in a bi-directional relationship.

In hibernate, there are primarily 3 ways to create one-to-one relationships between two entities. Either way, we have to use @OneToOne annotation.

  1. The first technique is widely used and uses a foreign key column in one of the tables.
  2. The second technique uses a rather known solution of having a join table to store the mapping between the first two tables.
  3. The third technique is something new that uses a common primary key in both tables.

2. Using a Foreign Key Association

In this kind of association, a foreign key column is created in the owner entity. For example, we have made EmployeeEntity owner, then an extra column "ACCOUNT_ID" will be created in Employee table. This column will store the foreign key for Account table.

The table structure will be like this:

foreign-key-association-one-to-one-2460824

To make such an association, refer the Account entity in EmployeeEntity class as follow:

@Entity
@Table(name = "EMPLOYEE")
public class EmployeeEntity implements Serializable { 

      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      @Column(name = "ID")
      private Integer employeeId;

      @OneToOne
      @JoinColumn(name="ACCOUNT_ID")
      private AccountEntity account;

      //Other fields, getters, setters are hidden for brevity
}

The join column is declared with the @JoinColumn annotation that looks like the @Column annotation. It has one more parameter named referencedColumnName. This parameter declares the column name in the targeted entity that will be used to join.

If no @JoinColumn is declared on the owner side, the defaults apply. A join column(s) will be created in the owner table and its name will be the concatenation of the name of the relationship in the owner side, _ (underscore), and the name of the primary key column(s) in the owned side.

In a bidirectional relationship, one of the sides (and only one) has to be the owner. The owner is responsible for the association column(s) update. To declare any side as not responsible for the relationship, the attribute mappedBy is used. The ‘mappedBy‘ refers to the property name of the association on the owner’s side.

@Entity
@Table(name = "ACCOUNT")
public class AccountEntity implements Serializable {

      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      @Column(name = "ID")
      private Integer accountId;

      @OneToOne(mappedBy = "account")
      private EmployeeEntity employee;

      //Other fields, getters, setters are hidden for brevity
}

Above "mappedBy” attribute declares that it is dependent on the owner entity for mapping.

Let’s test the above mappings:

AccountEntity account = new AccountEntity();
account.setAccountNumber("123-345-65454");

// Add new Employee object
EmployeeEntity emp = new EmployeeEntity();
emp.setEmail("demo-user@mail.com");
emp.setFirstName("demo");
emp.setLastName("user");

// Save Account
session.persist(account);

Assertions.assertNotNull(account.getAccountId());

// Save Employee
emp.setAccount(account);
session.persist(emp);
Assertions.assertNotNull(emp.getEmployeeId());

Assertions.assertNotNull(emp.getAccount().getAccountId());

Running the above code creates the desired schema in the database and runs these SQL queries.

Hibernate: insert into ACCOUNT (ACC_NUMBER) values (?)
Hibernate: insert into Employee (ACCOUNT_ID, EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?, ?)

We can verify the data and mappings in both tables when we run the above program.

3. Using a Join Table

This approach is not new to all of us. Here, hibernate will create a new table that will store the primary key values from both entities. Let us start with the targeted DB structure in this technique.

join-table-one-to-one-mapping-7664579

In this technique, the main annotation to be used is @JoinTable. This annotation is used to define the new table name (mandatory) and foreign keys from both of the tables. Let’s see how it is used:

@Entity
@Table(name = "EMPLOYEE")
public class EmployeeEntity implements Serializable { 

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Integer employeeId;

  @OneToOne(cascade = CascadeType.ALL)
  @JoinTable(name = "EMPLOYEE_ACCCOUNT",
      joinColumns = @JoinColumn(name = "EMPLOYEE_ID"),
      inverseJoinColumns = @JoinColumn(name = "ACCOUNT_ID"))
  private AccountEntity account;

  //Other fields, getters, setters are hidden for brevity
}

@JoinTable annotation is used in EmployeeEntity class. It declares that a new table EMPLOYEE_ACCOUNT will be created with two columns EMPLOYEE_ID (primary key of EMPLOYEE table) and ACCOUNT_ID (primary key of ACCOUNT table).

Testing the above entities generates the following SQL queries in log files:

Hibernate: insert into ACCOUNT (ACC_NUMBER) values (?)
Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?)
Hibernate: insert into EMPLOYEE_ACCCOUNT (ACCOUNT_ID, EMPLOYEE_ID) values (?, ?)

4. Using a Shared Primary Key

In this technique, hibernate will ensure that it will use a common primary key value in both tables. This way primary key of EmployeeEntity can safely be assumed the primary key of AccountEntity also.

The table structure will be like this:

shared-primary-key-one-to-one-8711420

In this approach, @PrimaryKeyJoinColumn is the main annotation to be used. Let us see how to use it.

@Entity
@Table(name = "EMPLOYEE")
public class EmployeeEntity implements Serializable { 

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Integer employeeId;

  @OneToOne(cascade = CascadeType.ALL)
  @PrimaryKeyJoinColumn
  private AccountEntity account;

  //Other fields, getters, setters are hidden for brevity
}

In AccountEntity side, it will remain dependent on the owner entity for the mapping.

@Entity
@Table(name = "ACCOUNT")
public class AccountEntity implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Integer accountId;

  @OneToOne(mappedBy="account", cascade=CascadeType.ALL)
  private EmployeeEntity employee; 

  //Other fields, getters, setters are hidden for brevity
}

Testing the above entities generates the following SQL queries in log files:

Hibernate: insert into ACCOUNT (ACC_NUMBER) values (?)
Hibernate: insert into Employee (ACCOUNT_ID, EMAIL, FIRST_NAME, LAST_NAME) values (?, ?, ?, ?)

5. Using a Shared Primary Key with @MapsId

In this technique, hibernate assumes both the source and target share the same primary key values. When using @MapsId, the parent-side association becomes redundant since the child-entity can be easily fetched using the parent entity identifier.

In this approach, @MapsId is the main annotation to be used. Let’s see how to use it.

@Entity
@Table(name = "EMPLOYEE")
public class EmployeeEntity implements Serializable { 

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Integer employeeId;

  @OneToOne
  @MapsId
  private AccountEntity account;

  //Other fields, getters, setters are hidden for brevity
}

In AccountEntity side, it will remain dependent on the owner entity for the mapping. So no changes are required on AccountEntity side.

@Entity
@Table(name = "ACCOUNT")
public class AccountEntity implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Integer accountId;

  //Other fields, getters, setters are hidden for brevity
}

Testing the above entities generates the following SQL queries in log files:

Hibernate: insert into ACCOUNT (ID, ACC_NUMBER) values (?, ?)
Hibernate: insert into Employee (EMAIL, FIRST_NAME, LAST_NAME, account_ID) values (?, ?, ?, ?)

So, we have seen all 4 different ways to create one-to-one mapping supported in hibernate. I will suggest you download the source code and play with it.

Happy Learning !!

Sourcecode on Github

Leave a Reply

31 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions, and frequently asked interview questions.