JPA - OneToMany relationship

[Updated: Apr 26, 2017, Created: Apr 12, 2017]
A quick overview of one-to-many relationship in JPA
  • In one-to-many association, a Java object (JPA entity) has a collection reference of another entity.
  • The entity having the collection reference is called 'source' entity', whereas the entity which is being referenced is called 'target entity'.
  • To map one-to-many association to database tables, @OneToMany annotation is used on the collection. We don't use any annotation on the target side unless it's a bidirectional relationship.
  • A middle 'join' table is mapped along with two tables, each for the source and the target entities.
  • The join table has two foreign key columns. One foreign key column refers to the source table. The other foreign key column refers to the target table.
  • In the join table, there is a unique key constraint on the target foreign key column (the one pointing to the target entity), that's because exactly one instance of the target entity should be populated in the source collection, otherwise it will become many-to-many relationship.
  • Having a unique foreign key for the target table also means, our collection (e.g. List) cannot have duplicates.

@OneToMany example

@Entity
public class EntityA {
  @Id
  @GeneratedValue
  private int myIdA;
  @OneToMany
  private List<EntityB> entityBList;
    .............
}
@Entity
public class EntityB {
  @Id
  @GeneratedValue
  private int myIdB;
  private String str;
    .............
}
public class ExampleMain {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
      try {
          EntityManager em = emf.createEntityManager();
          nativeQuery(em, "SHOW TABLES");
          nativeQuery(em, "SHOW COLUMNS from EntityA");
          nativeQuery(em, "SHOW COLUMNS from EntityB");
          nativeQuery(em, "SHOW COLUMNS from ENTITYA_ENTITYB");
          emf.close();
      } finally {
          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]
[ENTITYA_ENTITYB, PUBLIC]
[ENTITYB, PUBLIC]
---------------------------
'SHOW COLUMNS from EntityA'
[MYIDA, INTEGER(10), NO, PRI, NULL]
---------------------------
'SHOW COLUMNS from EntityB'
[MYIDB, INTEGER(10), NO, PRI, NULL]
[STR, VARCHAR(255), YES, , NULL]
---------------------------
'SHOW COLUMNS from ENTITYA_ENTITYB'
[ENTITYA_MYIDA, INTEGER(10), NO, , NULL]
[ENTITYBLIST_MYIDB, INTEGER(10), NO, UNI, NULL]

H2 database SHOW statements

Note that, in above output, there's a UNI (unique key) with ENTITYBLIST_MYIDB.

In this example, we are entirely relying on the mapping defaults. The join table, by default, has a name which is generated by combining the two entity names, appended with an underscore between them. If we want to change that we can use @JoinTable annotation. We will explore that in the next tutorial.

The following diagram summarizes what we have learnt in this example:

Persisting data

In this example, we are going to populate EntityA#entityBList with unique elements.

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) {
      System.out.println(" --- native queries");
      EntityManager em = emf.createEntityManager();
      ExampleMain.nativeQuery(em, "Select * from EntityA");
      ExampleMain.nativeQuery(em, "Select * from EntityB");
      ExampleMain.nativeQuery(em, "Select * from ENTITYA_ENTITYB");
  }

  private static void persistEntity(EntityManagerFactory emf) {
      System.out.println("-- Persisting entities --");
      EntityManager em = emf.createEntityManager();

      EntityB entityB = new EntityB();
      entityB.setStr("testStringB");

      EntityB entityB2 = new EntityB();
      entityB2.setStr("testStringB 2");

      EntityA entityA = new EntityA();
      entityA.setEntityBList(Arrays.asList(entityB, entityB2));

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

      em.close();
  }

  private static void loadEntityA(EntityManagerFactory emf) {
      System.out.println("-- Loading EntityA --");
      EntityManager em = emf.createEntityManager();
      List<EntityA> entityAList = em.createQuery("Select t from EntityA t").getResultList();
      entityAList.forEach(System.out::println);
      em.close();
  }

  private static void loadEntityB(EntityManagerFactory emf) {
      System.out.println("-- Loading EntityB --");
      EntityManager em = emf.createEntityManager();
      List<EntityB> entityBList = em.createQuery("Select t from EntityB t").getResultList();
      entityBList.forEach(System.out::println);
      em.close();
  }
}

Output

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

Populating duplicates

What will happen, if we attempt to persist duplicates in EntityA#entityBList, let's check that out.

public class ExampleMain3 {

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

  private static void persistEntity(EntityManagerFactory emf) {
      System.out.println("-- Persisting entities --");
      EntityManager em = emf.createEntityManager();

      EntityB entityB = new EntityB();
      entityB.setStr("testStringB");

      EntityA entityA = new EntityA();
      entityA.setEntityBList(Arrays.asList(entityB, entityB));

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

      em.close();
  }
}

Output

Caused by: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "UK_RMSTG6BCFUHSX5JHABMEQGGWA_INDEX_4 ON PUBLIC.ENTITYA_ENTITYB(ENTITYBLIST_MYIDB) VALUES (2, 1)"; SQL statement:
insert into EntityA_EntityB (EntityA_myIdA, entityBList_myIdB) values (?, ?) [23505-193]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.index.BaseIndex.getDuplicateKeyException(BaseIndex.java:103)
at org.h2.mvstore.db.MVSecondaryIndex.checkUnique(MVSecondaryIndex.java:231)
at org.h2.mvstore.db.MVSecondaryIndex.add(MVSecondaryIndex.java:190)
at org.h2.mvstore.db.MVTable.addRow(MVTable.java:704)
at org.h2.command.dml.Insert.insertRows(Insert.java:156)
at org.h2.command.dml.Insert.update(Insert.java:114)
at org.h2.command.CommandContainer.update(CommandContainer.java:98)
at org.h2.command.Command.executeUpdate(Command.java:258)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:160)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:146)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:205)
... 24 more

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

See Also