JPA - OneToOne relationship

[Updated: Apr 26, 2017, Created: Apr 7, 2017]
A quick overview of one-to-one relationship in JPA
  • In Java, one-to-one relationship (normally called association) is where an object has a reference (instance variable) of the other object. In JPA world, these objects are nothing but user defined entities.
  • The object which has the reference is known as source object (owner of the relationship) , whereas, the object which is being referenced is known as target object.
  • In this relationship, same target object instance is not shared (repeated) with different instances of the source object, otherwise it will become many-to-one relationship.
  • In database world, one-to-one relationship is where a table A has a special column, known as foreign-key column, referencing to the primary key column of another table B. Table A is known as child-table, whereas, the table B is known as parent-table.
  • In JPA, we map one-to-one association of objects to parent-child tables by using @OneToOne. This annotation is used in the source class and placed on the field/property of the target reference.
  • The source Java entity corresponds to the child table (the one which has the foreign key column) and the target entity corresponds to the parent table.

Mapping one-to-one association

Following example demonstrates how a one-to-one Java object association is mapped to database one-to-one entity relationship.

@Entity
public class EntityA {
  @Id
  @GeneratedValue
  private int myIdA;
  @OneToOne
  private EntityB entityB;

  public EntityB getEntityB() {
      return entityB;
  }

  public void setEntityB(EntityB entityB) {
      this.entityB = entityB;
  }
}
@Entity
public class EntityB {
  @Id
  @GeneratedValue
  private int myIdB;
  private String stringB;

  public String getStringB() {
      return stringB;
  }

  public void setStringB(String stringB) {
      this.stringB = stringB;
  }
}
public class ExampleMain {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
      EntityManager em = emf.createEntityManager();
      nativeQuery(em, "SHOW TABLES");
      nativeQuery(em, "SHOW COLUMNS from EntityA");
      nativeQuery(em, "SHOW COLUMNS from EntityB");
      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) {
          System.out.println(Arrays.toString((Object[]) o));
      }
  }
}

Output

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

H2 database SHOW statements

Here's a quick review of above example.

The foreign key column name

In above example, the table EntityA contains a foreign key to the table EntityB. By default, the foreign key column name is generated as the concatenation of the following:
- the name of the relationship field i.e EntityA#entityB,
- then "_" and
- then the name of the primary key column in table EntityB.

Using @JoinColumn

If we don't want to use the default name generation of the foreign key column (as stated above) or the table already exits with different foreign-key column name then we should use @JoinColumn annotation on the field/property:

@Entity
public class EntityA {
  @Id
  @GeneratedValue
  private int myIdA;
  @OneToOne
  @JoinColumn(name = "MY_JOIN_COLUMN")
  private EntityB entityB;

  public EntityB getEntityB() {
      return entityB;
  }

  public void setEntityB(EntityB entityB) {
      this.entityB = entityB;
  }
}
Complete Example

Persisting data

In above example, we focused on Java object to database table mapping. In this example we are going to populate and persist our entity objects.

public class ExampleMain2 {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
      EntityManager em = emf.createEntityManager();

      EntityB entityB = new EntityB();
      entityB.setStringB("testString");

      EntityA entityA = new EntityA();
      entityA.setEntityB(entityB);

      em.getTransaction().begin();
      em.persist(entityA);
      em.persist(entityB);
      em.getTransaction().commit();
      ExampleMain.nativeQuery(em, "select * from EntityA");
      ExampleMain.nativeQuery(em, "select * from EntityB");
      emf.close();
  }
}

Output

---------------------------
'select * from EntityA'
[1, 2]
---------------------------
'select * from EntityB'
[2, testString]

Use of 'optional' element of @OneToOne

What will happen, if we persist entityA with entityB being null in above example? Let's try:

public class ExampleMain3 {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
      EntityManager em = emf.createEntityManager();

      EntityA entityA = new EntityA();

      em.getTransaction().begin();
      em.persist(entityA);
      em.getTransaction().commit();
      ExampleMain.nativeQuery(em, "select * from EntityA");
      ExampleMain.nativeQuery(em, "select * from EntityB");
      emf.close();
  }
}

Output

---------------------------
'select * from EntityA'
[1, null]
---------------------------
'select * from EntityB'

Nothing persisted in EntityB table.

@OneToOne has an element; 'optional'. By default it's value is true. If we set it to false then a non-null relationship must always exist.

Following example demonstrates that setting 'optional=false' and persisting EntityA with null reference of EntityB, will throw an exception.

@Entity
public class EntityA {
  @Id
  @GeneratedValue
  private int myIdA;
  @OneToOne(optional = false)
  private EntityB entityB;

  public EntityB getEntityB() {
      return entityB;
  }

  public void setEntityB(EntityB entityB) {
      this.entityB = entityB;
  }
}

Output

Caused by: javax.persistence.PersistenceException: org.hibernate.PropertyValueException: not-null property references a null or transient value : com.logicbig.example.EntityA.entityB
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:147)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:162)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:780)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:758)
at com.logicbig.example.ExampleMain.main(ExampleMain.java:20)
... 6 more
Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value : com.logicbig.example.EntityA.entityB
at org.hibernate.engine.internal.Nullability.checkNullability(Nullability.java:92)
at org.hibernate.action.internal.AbstractEntityInsertAction.nullifyTransientReferencesIfNotAlready(AbstractEntityInsertAction.java:115)
at org.hibernate.action.internal.AbstractEntityInsertAction.makeEntityManaged(AbstractEntityInsertAction.java:124)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:283)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:258)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:245)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:326)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:275)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:773)
... 8 more
Complete Example

Use of 'cascade' element of @OneToOne

In our second last example (ExampleMain2), we persisted both entities by calling em.persist(entityB) and em.persist(entityA). What will happen if we persist entityA only, will entityB be persisted automatically. Let's try:

public class ExampleMain4 {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
      try {
          EntityManager em = emf.createEntityManager();

          EntityB entityB = new EntityB();
          entityB.setStringB("testString");

          EntityA entityA = new EntityA();
          entityA.setEntityB(entityB);

          em.getTransaction().begin();
          em.persist(entityA);
          em.getTransaction().commit();
          ExampleMain.nativeQuery(em, "select * from EntityA");
          ExampleMain.nativeQuery(em, "select * from EntityB");
      }
      finally {
          emf.close();
      }
  }
}

Output

Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance beforeQuery flushing : com.logicbig.example.EntityA.entityB -> com.logicbig.example.EntityB
	at org.hibernate.engine.spi.CascadingActions$8.noCascade(CascadingActions.java:380)
	at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:119)
	at org.hibernate.event.internal.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:150)
	at org.hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:141)

If we want to propagate operations performed by EntityManger from the entity having @OneToOne to the associated entity, then we can specify a suitable value for cascade of @OneToOne. By default no operations are cascaded, that's why we had above exception.

In following example, we want to propagate persist() operation, so we will specify cascade= Cascade Type. PERSIST

@Entity
public class EntityC {
  @Id
  @GeneratedValue
  private int myIdC;
  @OneToOne(cascade = CascadeType.PERSIST)
  private EntityD entityD;

  public EntityD getEntityD() {
      return entityD;
  }

  public void setEntityD(EntityD entityD) {
      this.entityD = entityD;
  }
}
@Entity
public class EntityD {
  @Id
  @GeneratedValue
  private int myIdD;
  private String stringD;

  public String getStringD() {
      return stringD;
  }

  public void setStringD(String stringD) {
      this.stringD = stringD;
  }
}

In following code, we have to persist EntityC only, that will persist EntityD as well.

public class ExampleMain5 {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test2");
      EntityManager em = emf.createEntityManager();

      EntityC entityC = new EntityC();
      EntityD entityD = new EntityD();
      entityD.setStringD("testString");
      entityC.setEntityD(entityD);

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

      ExampleMain.nativeQuery(em, "select * from EntityC");
      ExampleMain.nativeQuery(em, "select * from EntityD");
      emf.close();
  }
}

Output

---------------------------
'select * from EntityC'
[1, 2]
---------------------------
'select * from EntityD'
[2, testString]

We will be discussing other elements of @OneToOne annotation in upcoming tutorials.

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 Relation Examples Select All Download
  • one-to-one-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF

See Also