JPA - ManyToMany relationship

[Updated: May 31, 2017, Created: Apr 24, 2017]
A quick overview of many-to-many relationship in JPA.
  • In many-to-many association, a 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 many-to-many association to database tables, @ManyToMany annotation is used on the collection. We don't use any annotation on the target side unless it's a bidirectional relationship.
  • An intermediate 'join' table is mapped along with the 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 no unique key constraint on any of the two foreign key columns so that many-to-many multiplicity can be achieved.
  • Having no unique foreign key in the join table also means that our collection can have duplicates and also same target entity can be shared among different instances of the source entity's collection.
  • To use a customized join table or to map an existing join table with different attributes (names/columns), we can use @JoinTable annotation.

Example

@Entity
public class EntityA {
  @Id
  @GeneratedValue
  private int myIdA;
  @ManyToMany
  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("test");
      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, , NULL]

A quick overview of the relationship:

Persisting and loading entities

public class ExampleMain2 {

  public static void main(String[] args) {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
      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, entityB));

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

      em.getTransaction().begin();
      em.persist(entityA);
      em.persist(entityA2);
      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
2
---------------------------
'Select * from EntityB'
[3, testStringB]
[4, testStringB 2]
---------------------------
'Select * from ENTITYA_ENTITYB'
[1, 3]
[1, 4]
[1, 3]
[2, 3]
[2, 4]
-- Loading EntityA --
EntityA{myIdA=1, entityBList=[EntityB{myIdB=3, str='testStringB'}, EntityB{myIdB=4, str='testStringB 2'}, EntityB{myIdB=3, str='testStringB'}]}
EntityA{myIdA=2, entityBList=[EntityB{myIdB=3, str='testStringB'}, EntityB{myIdB=4, str='testStringB 2'}]}
-- Loading EntityB --
EntityB{myIdB=3, str='testStringB'}
EntityB{myIdB=4, str='testStringB 2'}

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

Many To Many Example Select All Download
  • jpa-many-to-many-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF

See Also