Close

JPA - Persisting a Map of entity keys and basic values

[Last Updated: Jun 16, 2017]
JPA - A quick overview of Map with entity keys and basic type values
  • To persist Map of entity keys and basic types values, the annotation @ElementCollection is used on the map field/property.
  • The main @Entity class and the key @Entity class are mapped to three separate tables. The first table for the main entity, the second table for the key entity and the third intermediate table (the join table) for linking the main entity's primary key to the key entity's primary key. The join table also has one column for the map's value.
  • In the join table, both main entity id column and key entity id column are primary keys of the join table. That means, the main entity and the Map's key combination will be unique. That makes sense because a Java Map cannot have duplicate keys.
  • The key entity class should implement the hashCode and equals methods (a general requirement for map keys).
  • By default, JPA naming conventions are used for the mapping. We can customize that by using @CollectionTable, @Column or @MapKeyJoinColumn annotations.

Example

This is a simple example with default table/columns mapping. At the end, we will see another example to override default mapping names.

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  @ElementCollection
  private Map<Task, Date> taskMap;
    .............
}
@Entity
public class Task {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  private String description;
    .............
}
public class ExampleMain {

  public static void main(String[] args) {
      EntityManagerFactory emf =
              Persistence.createEntityManagerFactory("example-unit");
      try {
          EntityManager em = emf.createEntityManager();
          nativeQuery(em, "SHOW TABLES");
          nativeQuery(em, "SHOW COLUMNS from EMPLOYEE");
          nativeQuery(em, "SHOW COLUMNS from TASK");
          nativeQuery(em, "SHOW COLUMNS from Employee_TaskMap");
      } finally {
          emf.close();
      }
  }
    .............
}

Output

'SHOW TABLES'
[EMPLOYEE, PUBLIC]
[EMPLOYEE_TASKMAP, PUBLIC]
[TASK, PUBLIC]
'SHOW COLUMNS from EMPLOYEE'
[ID, BIGINT(19), NO, PRI, NULL]
[NAME, VARCHAR(255), YES, , NULL]
'SHOW COLUMNS from TASK'
[ID, BIGINT(19), NO, PRI, NULL]
[DESCRIPTION, VARCHAR(255), YES, , NULL]
[NAME, VARCHAR(255), YES, , NULL]
'SHOW COLUMNS from Employee_TaskMap'
[EMPLOYEE_ID, BIGINT(19), NO, PRI, NULL]
[TASKMAP, TIMESTAMP(23), YES, , NULL]
[TASKMAP_KEY, BIGINT(19), NO, PRI, NULL]

H2 database SHOW statements

A quick overview of the mapping:

Persisting and loading data

public class ExampleMain2 {

  public static void main(String[] args) throws Exception {
      EntityManagerFactory emf =
              Persistence.createEntityManagerFactory("example-unit");
      try {
          persistEntity(emf);
          runNativeQueries(emf);
          loadEntity(emf);
      } finally {
          emf.close();
      }
  }

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

      Employee e1 = new Employee();
      e1.setName("employee name 1");

      Task task1 = new Task();
      task1.setName("task 1");
      task1.setDescription("task 1 desc");

      SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yy");
      Task task2 = new Task();
      task2.setName("task 2");
      task2.setDescription("task 2 desc");

      e1.addTask(sdf.parse("15-01-17"), task1);
      e1.addTask(sdf.parse("20-01-17"), task2);
      System.out.println(e1);

      Employee e2 = new Employee();
      e2.setName("employee name 2");

      Task task3 = new Task();
      task3.setName("task 3");
      task3.setDescription("task 3 desc");

      e2.addTask(sdf.parse("02-05-2017"), task3);
      System.out.println(e2);

      em.getTransaction().begin();
      em.persist(task1);
      em.persist(task2);
      em.persist(task3);
      em.persist(e1);
      em.persist(e2);
      em.getTransaction().commit();
      em.close();
  }

  private static void runNativeQueries(EntityManagerFactory emf) {
      System.out.println("-- Native queries --");
      EntityManager em = emf.createEntityManager();
      ExampleMain.nativeQuery(em, "Select * from Employee");
      ExampleMain.nativeQuery(em, "Select * from Task");
      ExampleMain.nativeQuery(em, "Select * from Employee_TaskMap");
  }

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

Output

-- Persisting entities --
Employee{id=0, name='employee name 1', taskMap={Task{id=0, name='task 1', description='task 1 desc'}=Thu Mar 01 00:00:00 CST 2018, Task{id=0, name='task 2', description='task 2 desc'}=Wed Aug 01 00:00:00 CDT 2018}}
Employee{id=0, name='employee name 2', taskMap={Task{id=0, name='task 3', description='task 3 desc'}=Sun Feb 05 00:00:00 CST 2017}}
-- Native queries --
'Select * from Employee'
[4, employee name 1]
[5, employee name 2]
'Select * from Task'
[1, task 1 desc, task 1]
[2, task 2 desc, task 2]
[3, task 3 desc, task 3]
'Select * from Employee_TaskMap'
[4, 2018-03-01 00:00:00.0, 1]
[4, 2018-08-01 00:00:00.0, 2]
[5, 2017-02-05 00:00:00.0, 3]
-- Loading entities --
Employee{id=4, name='employee name 1', taskMap={Task{id=1, name='task 1', description='task 1 desc'}=2018-03-01 00:00:00.0, Task{id=2, name='task 2', description='task 2 desc'}=2018-08-01 00:00:00.0}}
Employee{id=5, name='employee name 2', taskMap={Task{id=3, name='task 3', description='task 3 desc'}=2017-02-05 00:00:00.0}}

Example Project

Dependencies and Technologies Used:

  • h2 1.4.195: H2 Database Engine.
  • hibernate-core 5.2.10.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

Map Entity Keys Example Select All Download
  • map-with-entity-keys
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • Employee.java
          • resources
            • META-INF

    Overriding defaults with @CollectionTable, @Column and @MapKeyJoinColumn

    • @CollectionTable:

      For the join table.

    • @MapKeyJoinColumn:

      For the map key column (the id ref of key entity).

    • @Column:

      For the map value column

    @Entity
    public class Employee {
      @Id
      @GeneratedValue
      private long id;
      private String name;
      @ElementCollection
      @CollectionTable(name = "EMP_TASKS", joinColumns = @JoinColumn(name = "EMP_ID"))
      @MapKeyJoinColumn(name = "TASK_ID")
      @Column(name = "TASK_DATE")
      private Map<Task, Date> taskMap;
        .............
    }
    @Entity
    public class Task {
      @Id
      @GeneratedValue
      private long id;
      private String name;
      private String description;
        .............
    }
    Complete Example

    See Also