Close

JPA - Using @JoinTable in bidirectional @OneToOne relationship

[Last Updated: Oct 29, 2025]
A quick overview of bidirectional one-to-one join table mapping strategy.
  • By default, a bidirectional one-to-one association, populates the relationship in a foreign key column of the table that corresponds to the source entity.
  • We can instead use an intermediate table to persist the foreign-key/primary-key mapping. To achieve that we have to use @JoinTable annotation on the source (owner) entity side.
  • In the target entity, we still have to use @OneToOne with 'mappedBy' element.

Example

@Entity
public class EntityA {
    @Id
    @GeneratedValue
    private int myIdA;
    private String stringA;
    @OneToOne
    @JoinTable(name = "MY_JOIN_TABLE",
            joinColumns = {
                    @JoinColumn(name = "ENTITYA_FK", referencedColumnName = "myIdA")
            },
            inverseJoinColumns = {
                    @JoinColumn(name = "ENTITYB_FK", referencedColumnName = "myIdB", unique = true)
            }
    )
    private EntityB entityB;
    .............
}
@Entity
public class EntityB {
    @Id
    @GeneratedValue
    private int myIdB;
    private String stringB;

    @OneToOne(mappedBy = "entityB")
    private EntityA entityA;
    .............
}
public class ExampleMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");
        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");
        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]
[MY_JOIN_TABLE, PUBLIC]
---------------------------
'SHOW COLUMNS from EntityA'
[MYIDA, INTEGER(10), NO, PRI, NULL]
[STRINGA, VARCHAR(255), YES, , NULL]
---------------------------
'SHOW COLUMNS from EntityB'
[MYIDB, INTEGER(10), NO, PRI, NULL]
[STRINGB, VARCHAR(255), YES, , NULL]
---------------------------
'SHOW COLUMNS FROM MY_JOIN_TABLE'
[ENTITYB_FK, INTEGER(10), YES, UNI, NULL]
[ENTITYA_FK, INTEGER(10), NO, PRI, 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 MY_JOIN_TABLE");
    }

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

        EntityB entityB = new EntityB();
        entityB.setStringB("test stringB");

        EntityA entityA = new EntityA();
        entityA.setStringA("test StringA");
        entityA.setEntityB(entityB);

        entityB.setEntityA(entityA);

        System.out.println("-- persisting entities --");
        System.out.printf("entityA: %s%n", entityA);
        System.out.printf("entityB: %s%n", 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();
        List<EntityA> entityAList = em.createQuery("Select t from EntityA t").getResultList();
        entityAList.forEach(e -> System.out.printf("entityA: %s%n", e));
        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(e -> {
            System.out.printf("entityB: %s%n", e);
            System.out.printf("entityB#entityA: %s%n", e.getEntityA());
        });
        em.close();
    }
}

Output

-- persisting entities --
entityA: EntityA{myIdA=0, stringA='test StringA', entityB=EntityB{myIdB=0, stringB='test stringB', entityA=0}}
entityB: EntityB{myIdB=0, stringB='test stringB', entityA=0}
-- native queries --
---------------------------
'Select * from EntityA'
[1, test StringA]
---------------------------
'Select * from EntityB'
[2, test stringB]
---------------------------
'Select * from MY_JOIN_TABLE'
[2, 1]
-- loading EntityA --
entityA: EntityA{myIdA=1, stringA='test StringA', entityB=EntityB{myIdB=2, stringB='test stringB', entityA=1}}
-- loading EntityB --
entityB: EntityB{myIdB=2, stringB='test stringB', entityA=1}
entityB#entityA: EntityA{myIdA=1, stringA='test StringA', entityB=EntityB{myIdB=2, stringB='test stringB', entityA=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.
    Implements javax.persistence:javax.persistence-api version 2.1
  • JDK 1.8
  • Maven 3.3.9

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

    See Also