JPA - Bidirectional OneToOne relationship

[Updated: May 31, 2017, Created: Apr 9, 2017]
A quick overview of bidirectional one-to-one relation in JPA
  • Just as with unidirectional one-to-one relationship, bidirectional one-to-one relationship has a single target object reference in the source entity, but additionally target entity has a reference back to the source entity as well.
  • The annotation @OneToOne is used in both source and target classes.
  • We must use 'mappedBy' element with one of the @OneToOne annotations.
  • The value of 'mappedBy' element should be the name of the reference variable used in the other class's back reference.
  • The element 'mappedBy' is needed to specify which side will have the corresponding parent table. Also without 'mappedBy', hibernate will generate the foreign key columns in the both tables. 'mappedBy' must be used to avoid that.
  • The side which has 'mappedBy' specified, will become the target of the relationship and corresponding table will be the parent of the relationship .
  • The side which doesn't have 'mappedBy' element will become the source (owner) and the corresponding table will be the child of the relationship, i.e. it will have the foreign key column.
  • On the owner side, we can also use @JoinColumn, whose one of the purposes is to specify a foreign key column name instead of relying on the default name.
  • From database perspective, there's no difference, between unidirectional and bidirectional one-to-one relationships, because we can always use the queries to get data in the other direction.

Bidirectional @oneToOne example

@Entity
public class EntityA {
  @Id
  @GeneratedValue
  private int myIdA;
  @OneToOne
  private EntityB refEntityB;
  //getters/setters
    .............
  @Override
  public String toString() {
      return "EntityA{" +
              "myIdA=" + myIdA +
              '}';
  }
}
@Entity
public class EntityB {
  @Id
  @GeneratedValue
  private int myIdB;
  @OneToOne(mappedBy = "refEntityB")
  private EntityA refEntityA;
  private String str;
  //getters/setters
    .............
  @Override
  public String toString() {
      return "EntityB{" +
              "myIdB=" + myIdB +
              ", str='" + str + '\'' +
              '}';
  }
}
public class ExampleMain {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
      EntityManager em = emf.createEntityManager();
      try {
          nativeQuery(em, "SHOW TABLES");
          nativeQuery(em, "SHOW COLUMNS from EntityA");
          nativeQuery(em, "SHOW COLUMNS from EntityB");

      } finally {
          em.close();
          emf.close();
      }
  }

  public static void nativeQuery(EntityManager em, String s) {
      System.out.printf("---------------------------%n'%s'%n", s);
      Query query = em.createNativeQuery(s);
      List list = query.getResultList();
      for (Object o : list) {
          if (o instanceof Object[]) {
              System.out.println(Arrays.toString((Object[]) o));
          } else {
              System.out.println(o);
          }
      }
  }
}

Output

---------------------------
'SHOW TABLES'
[ENTITYA, PUBLIC]
[ENTITYB, PUBLIC]
---------------------------
'SHOW COLUMNS from EntityA'
[MYIDA, INTEGER(10), NO, PRI, NULL]
[REFENTITYB_MYIDB, INTEGER(10), YES, , NULL]
---------------------------
'SHOW COLUMNS from EntityB'
[MYIDB, INTEGER(10), NO, PRI, NULL]
[STR, VARCHAR(255), YES, , NULL]

H2 database SHOW statements

Following diagram gives a quick overview of the bidirectional relationship:

Persisting data

public class ExampleMain2 {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
      try {
          persistEntity(emf);
          nativeQueries(emf);
          loadEntityA(emf);
          loadEntityB(emf);
      } finally {
          emf.close();
      }
  }

  private static void nativeQueries(EntityManagerFactory emf) {
      EntityManager em = emf.createEntityManager();
      ExampleMain.nativeQuery(em, "Select * from EntityA");
      ExampleMain.nativeQuery(em, "Select * from EntityB");
  }

  private static void persistEntity(EntityManagerFactory emf) {
      System.out.println("-- Persisting entities --");
      EntityManager em = emf.createEntityManager();
      EntityA entityA = new EntityA();
      EntityB entityB = new EntityB();
      entityB.setStr("testStringB");
      entityB.setRefEntityA(entityA);
      entityA.setRefEntityB(entityB);

      em.getTransaction().begin();
      em.persist(entityA);
      em.persist(entityB);
      em.getTransaction().commit();

      em.close();
  }

  private static void loadEntityA(EntityManagerFactory emf) {
      System.out.println("-- Loading EntityA --");
      EntityManager em = emf.createEntityManager();
      EntityA entityA = em.find(EntityA.class, 1);
      System.out.println(entityA);
      System.out.println(entityA.getEntityB());
      em.close();
  }

  private static void loadEntityB(EntityManagerFactory emf) {
      System.out.println("-- Loading EntityB --");
      EntityManager em = emf.createEntityManager();
      EntityB entityB = em.find(EntityB.class, 2);
      System.out.println(entityB);
      System.out.println(entityB.getRefEntityA());
      em.close();
  }
}

Output

-- Persisting entities --
---------------------------
'Select * from EntityA'
[1, 2]
---------------------------
'Select * from EntityB'
[2, testStringB]
-- Loading EntityA --
EntityA{myIdA=1}
EntityB{myIdB=2, str='testStringB'}
-- Loading EntityB --
EntityB{myIdB=2, str='testStringB'}
EntityA{myIdA=1}

Note that in above example, we didn't have to 'find' EntityA and it's reference EntityB separately . Loading the 'owner' entity will load the other one as well. The same is true for the reverse association.

Bidirectional @OneToOne without specifying 'mappedBy'

What will happen, if we don't use 'mappedBy' on the reverse side? Let's try that out.

@Entity
public class EntityC {
  @Id
  @GeneratedValue
  private int myIdC;
  @OneToOne
  private EntityD refEntityD;
  //getters/setters
    .............
  @Override
  public String toString() {
      return "EntityC{" +
              "myIdC=" + myIdC +
              '}';
  }
}
@Entity
public class EntityD {
  @Id
  @GeneratedValue
  private int myIdD;
  @OneToOne
  private EntityC refEntityC;
  private String str;
  //getters/setters
    .............
  @Override
  public String toString() {
      return "EntityD{" +
              "myIdD=" + myIdD +
              ", str='" + str + '\'' +
              '}';
  }
}
public class ExampleMain3 {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test2");
      EntityManager em = emf.createEntityManager();
      try {
          ExampleMain.nativeQuery(em, "SHOW TABLES");
          ExampleMain.nativeQuery(em, "SHOW COLUMNS from EntityC");
          ExampleMain.nativeQuery(em, "SHOW COLUMNS from EntityD");

      } finally {
          em.close();
          emf.close();
      }
  }
}

Output

---------------------------
'SHOW TABLES'
[ENTITYC, PUBLIC]
[ENTITYD, PUBLIC]
---------------------------
'SHOW COLUMNS from EntityC'
[MYIDC, INTEGER(10), NO, PRI, NULL]
[REFENTITYD_MYIDD, INTEGER(10), YES, , NULL]
---------------------------
'SHOW COLUMNS from EntityD'
[MYIDD, INTEGER(10), NO, PRI, NULL]
[STR, VARCHAR(255), YES, , NULL]
[REFENTITYC_MYIDC, INTEGER(10), YES, , NULL]

Both sides generated tables which have their own foreign key columns and both are cross referencing each other. This is bad as per good database design principles.

Let's persist and load data:

public class ExampleMain4 {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test2");
      try {
          persistEntity(emf);
          nativeQueries(emf);
          loadEntityC(emf);
          loadEntityD(emf);
      } finally {
          emf.close();
      }
  }

  private static void nativeQueries(EntityManagerFactory emf) {
      EntityManager em = emf.createEntityManager();
      ExampleMain.nativeQuery(em, "Select * from EntityC");
      ExampleMain.nativeQuery(em, "Select * from EntityD");
  }

  private static void persistEntity(EntityManagerFactory emf) {
      System.out.println("-- Persisting entities --");
      EntityManager em = emf.createEntityManager();
      EntityC entityC = new EntityC();
      EntityD entityD = new EntityD();
      entityD.setStr("testStringD");
      entityC.setRefEntityD(entityD);
      entityD.setRefEntityC(entityC);

      em.getTransaction().begin();
      em.persist(entityC);
      em.persist(entityD);
      em.getTransaction().commit();

      em.close();
  }

  private static void loadEntityC(EntityManagerFactory emf) {
      System.out.println("-- Loading EntityC --");
      EntityManager em = emf.createEntityManager();
      EntityC entityC = em.find(EntityC.class, 1);
      System.out.println(entityC);
      System.out.println(entityC.getEntityD());
      em.close();
  }

  private static void loadEntityD(EntityManagerFactory emf) {
      System.out.println("-- Loading EntityD --");
      EntityManager em = emf.createEntityManager();
      EntityD entityD = em.find(EntityD.class, 2);
      System.out.println(entityD);
      System.out.println(entityD.getRefEntityC());
      em.close();
  }
}

Output

-- Persisting entities --
---------------------------
'Select * from EntityC'
[1, 2]
---------------------------
'Select * from EntityD'
[2, testStringD, 1]
-- Loading EntityC --
EntityC{myIdC=1}
EntityD{myIdD=2, str='testStringD'}
-- Loading EntityD --
EntityD{myIdD=2, str='testStringD'}
EntityC{myIdC=1}

Example Project

Dependencies and Technologies Used :

  • h2 1.4.193: H2 Database Engine.
  • hibernate-core 5.2.8.Final: The core O/RM functionality as provided by Hibernate.
  • JDK 1.8
  • Maven 3.3.9

One To One Bidirecional Relation Examples Select All Download
  • jpa-one-to-one-bidirectional-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF

See Also