JPA - Persisting a Map of basic type keys and @Entity values

[Updated: Jun 6, 2017, Created: Jun 6, 2017]
JPA - A quick overview of Map with basic keys and entity values
  • To persist Map of basic type keys and entity values (the corresponding class is annotated with @Entity), the annotation @OneToMany or @ManyToMany is used on the Map reference.
  • This is just like other collection mappings with only one difference: we have one extra column for the map keys along with the foreign key column(s).
  • By default, JPA naming conventions are used for the mapping. We can customize that by using; @JoinTable and/or @MapKeyColumn annotations.
  • The difference between @OneToMany and @ManyToMany relationship is:
    • In @OnToMany relationship, the join table foreign-key column for the other side entity has unique constraint. That means the map cannot have duplicate values or the same value entity cannot be shared among different instances of the maps' values.
    • In @ManyToMany relationship there's no unique constrains. That means the map can have duplicate entity values or values can be shared among different maps.
  • The use of @OneToMany is optional as OneToMany relationship is applied by default. But when ManyToMany relationship of the map values is desired, we have to use @ManyToMany.

@OneToMany Example of Map with @Entity values

First, we will understand a very basic case of @OneToMany on java.util.Map, then we will see other examples.

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  @OneToMany
  private Map<Date, Task> tasks;
    .............
}
@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_TASK");
      } finally {
          emf.close();
      }
  }
    .............
}

Output

'SHOW TABLES'
[EMPLOYEE, PUBLIC]
[EMPLOYEE_TASK, 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_TASK'
[EMPLOYEE_ID, BIGINT(19), NO, PRI, NULL]
[TASKS_ID, BIGINT(19), NO, UNI, NULL]
[TASKS_KEY, TIMESTAMP(23), NO, PRI, NULL]

A quick overview of the mapping:

As seen above, by default, OneToMany mapping has an intermediate join table. We can change that by applying foreign-key mapping strategy (see below).

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_Task");
  }

  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', tasks={Thu Mar 01 00:00:00 CST 2018=Task{id=0, name='task 1', description='task 1 desc'}, Wed Aug 01 00:00:00 CDT 2018=Task{id=0, name='task 2', description='task 2 desc'}}}
Employee{id=0, name='employee name 2', tasks={Sun Feb 05 00:00:00 CST 2017=Task{id=0, name='task 3', description='task 3 desc'}}}
-- 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_Task'
[4, 1, 2018-03-01 00:00:00.0]
[4, 2, 2018-08-01 00:00:00.0]
[5, 3, 2017-02-05 00:00:00.0]
-- Loading entities --
Employee{id=4, name='employee name 1', tasks={2018-03-01 00:00:00.0=Task{id=1, name='task 1', description='task 1 desc'}, 2018-08-01 00:00:00.0=Task{id=2, name='task 2', description='task 2 desc'}}}
Employee{id=5, name='employee name 2', tasks={2017-02-05 00:00:00.0=Task{id=3, name='task 3', description='task 3 desc'}}}

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.
  • JDK 1.8
  • Maven 3.3.9

Entity Values Map Example Select All Download
  • map-with-entity-values
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • resources
          • META-INF

Other examples

Customizing mapping with @JoinTable and @MapKeyColumn

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  @OneToMany
  @JoinTable(name = "ASSIGNED_TASKS",
          joinColumns = {@JoinColumn(name = "EMPLOYEE_FK")},
          inverseJoinColumns = {@JoinColumn(name = "TASK_FK")})
  @MapKeyColumn(name = "TASK_DATE")
  private Map<Date, Task> tasks;
    .............
}
@Entity
public class Task {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  private String description;
    .............
}
Complete Example

Mapping java.util.Map with Bidirectional @OneToMany/@ManyToOne entity relationship

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  @OneToMany(mappedBy = "taskEmployee", cascade = CascadeType.ALL)
  @MapKeyColumn(name = "TASK_DATE", nullable = true)
  private Map<Date, Task> tasks;
    .............
}
@Entity
public class Task {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  private String description;
  @ManyToOne
  @JoinColumn(name = "EMP_FK")
  private Employee taskEmployee;
    .............
}
Complete Example

OneToMany foreign-key mapping strategy for java.util.Map entity values

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  @OneToMany
  @JoinColumn(name = "EMPLOYEE_FK")
  @MapKeyColumn(name = "TASK_DATE", nullable = true)
  private Map<Date, Task> tasks;
    .............
}
@Entity
public class Task {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  private String description;
    .............
}
Complete Example

java.util.Map entity values with ManyToMany relationship

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  @ManyToMany
  private Map<Date, Task> tasks;
    .............
}
@Entity
public class Task {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  private String description;
    .............
}
Complete Example

See Also