JPA - Bidirectional OneToMany/ManyToOne with Join table Example

[Updated: May 31, 2017, Created: Apr 21, 2017]
A quick overview of bidirectional one-to-many/many-to-one join table mapping strategy
  • By default, a bidirectional OneToMany/ManyToOne association uses a foreign key column on the side which has the single-valued reference with @ManyToOne annotation.
  • We can override this default by using an intermediate join table to persist the association.
  • To achieve that we have to use @JoinTable annotation on the both sides.
  • In this case, we don't have to use 'mappedBy' element of the @OneToMany annotation.


public class EntityA {
  private int myIdA;
  private String strA;
  @JoinTable(name = "MY_JOIN_TABLE",
          joinColumns = {@JoinColumn(name = "MY_ENTITY_A_FK", referencedColumnName = "myIdA")},
          inverseJoinColumns = {@JoinColumn(name = "MY_ENTITY_B_FK", referencedColumnName = "myIdB")}
  private List<EntityB> entityBList;
public class EntityB {
  private int myIdB;
  private String strB;
  @JoinTable(name = "MY_JOIN_TABLE",
          joinColumns = {@JoinColumn(name = "MY_ENTITY_B_FK", insertable = false,
                  updatable = false, referencedColumnName = "myIdB")},
          inverseJoinColumns = {@JoinColumn(name = "MY_ENTITY_A_FK", insertable = false,
                  updatable = false, referencedColumnName = "myIdA")}
  private EntityA refEntityA;

Note that we have to use insertable/updatable=false on @ManyToOne side, to avoid persisting same relationship twice from both sides. When insertable/updatable=false, the column is not included in the generated SQL INSERT/UPDATE statements.

Now let's see what table structure our association is mapped to:

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 MY_JOIN_TABLE");
      } finally {

  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));

Here's a quick overview of the relationship:

Persisting and loading entities

public class ExampleMain2 {

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

  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 MY_JOIN_TABLE");

  private static void persistEntity(EntityManagerFactory emf) {
      EntityManager em = emf.createEntityManager();

      EntityB entityB1 = new EntityB();

      EntityB entityB2 = new EntityB();

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


      System.out.println("-- persisting entities --");
      System.out.printf(" %s%n entityA#entityBList: %s%n", entityA, entityA.getEntityBList());
      System.out.printf(" %s%n entityB1#refEntityA: %s%n", entityB1, entityB1.getRefEntityA());
      System.out.printf(" %s%n entityB2#refEntityA: %s%n", entityB2, entityB2.getRefEntityA());



  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(e -> System.out.printf(" %s%n entityA#entityBList: %s%n", e, e.getEntityBList()));

  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(e -> System.out.printf(" %s%n entityB#refEntityA: %s%n", e, e.getRefEntityA()));


-- persisting entities --
EntityA{myIdA=0, strA='testStringA'}
entityA#entityBList: [EntityB{myIdB=0, strB='testStringB'}, EntityB{myIdB=0, strB='testStringB2'}]
EntityB{myIdB=0, strB='testStringB'}
entityB1#refEntityA: EntityA{myIdA=0, strA='testStringA'}
EntityB{myIdB=0, strB='testStringB2'}
entityB2#refEntityA: EntityA{myIdA=0, strA='testStringA'}
-- native queries --
'Select * from EntityA'
[3, testStringA]
'Select * from EntityB'
[1, testStringB]
[2, testStringB2]
'Select * from MY_JOIN_TABLE'
[3, 1]
[3, 2]
-- loading EntityA --
EntityA{myIdA=3, strA='testStringA'}
entityA#entityBList: [EntityB{myIdB=1, strB='testStringB'}, EntityB{myIdB=2, strB='testStringB2'}]
-- loading EntityB --
EntityB{myIdB=1, strB='testStringB'}
entityB#refEntityA: EntityA{myIdA=3, strA='testStringA'}
EntityB{myIdB=2, strB='testStringB2'}
entityB#refEntityA: EntityA{myIdA=3, strA='testStringA'}

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

Bidirectional One To Many With Join Table Example Select All Download
  • jpa-bidirectional-one-to-many-join-table
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF

See Also