Close

JPA - Persistence-Context vs Persistence-Unit. Proper use of EntityManager and EntityManagerFactory

[Last Updated: Aug 28, 2018]

An instance of the interface javax. persistence. Entity Manager is used to persist, retrieve, update and remove entities to/from the database. Depending on the environment, an EntityManager can be used in following two ways:

  • Container-managed entity manger: In this case, the instance of EntityManager is managed automatically by the container. We typically obtain the instance of EntityManager by injecting it with @PersistenceContext. This is only applicable to Java EE environment
  • Application-managed entity manager: In this case, we have to explicitly obtain and manage the instance(s) of EntityManager. This is applicable to both Java EE or stand-alone/Java SE applications.

In this tutorial, we are going to focus on application-managed entity manager. We will discuss container-managed entity manager in future tutorials. Here we are going to understand the difference between the two terms 'persistence-unit' and 'persistence-context, how they are related to EntityManagerFactory and EntityManager, and what is the correct way to use them in an stand-alone application.

EntityMangerFactory and persistence-unit

An instance of the interface EntityManagerFactory is used to load a persistence-unit. We can get it via bootstrap class Persistence:

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit")

EntityManagerFactory is thread safe so a single instance can be shared by multiple threads.

What is persistence-unit?

A persistence unit is a logical grouping that contains information like configuration of EntityManagerFactory, a set of entity classes, mapping metadata (can be loaded by scanning mapping annotations or from persistence.xml/orm.xml under META-INF directory). Each persistence-unit must have a unique name. An application can have one or more persistence units.

persistence.xml

The root of the persistence-unit is defined by a persistence.xml file under META-INF directory in the classpath. A typical persistence.xml in an stand-alone application looks like this:

<persistence>
 <persistence-unit name="...." transaction-type="...">
  <provider>...</provider>
  <exclude-unlisted-classes>true/false</exclude-unlisted-classes>
  <properties>
   <property name="javax.persistence.schema-generation.database.action" value="..."/>
   <property name="javax.persistence.jdbc.url" value="..."/>
  </properties>
  <mapping-file>...</mapping-file>
  <class>...</class>
  <class>...</class>
 </persistence-unit>
</persistence>

Where:

  • name: A unique name for persistence unit. It is used to create the instance of EntityManagerFactory by using Persistence. create Entity Manager Factory as shown in above code snippet.
  • transaction-type: Possible values are JTA (used in Java EE environment) and RESOURCE_LOCAL. If RESOURCE_LOCAL is specified then the application is responsible to do transaction management manually, typically like this:
        entityManager.getTransaction().begin();
        entityManager.persist(entity);
                ......
        entityManager.getTransaction().commit();
            
  • provider: This specifies the qualified name of the persistence provider's javax.persistence.spi.PersistenceProvider implementation, e.g. if we are using EclipseLink then it would be: org.eclipse.persistence.jpa.PersistenceProvider
  • exclude-unlisted-classes: If this is set to false then entity classes will be discovered by scanning annotations, otherwise, we have to list all entity classes specified explicitly by <class></class> elements.
  • properties: This element can be used to specify both standard and vendor-specific properties. For example, the standard property javax. persistence. schema-generation. database . action specifies the action to be taken by the persistence provider with regard to the database schema automatic generation. The possible values for this property are none/create/drop-and-create/drop. If this property is not specified or is none, no schema generation actions will be taken on the database.
  • mapping-file: This is to specify one or more object/relational mapping XML files, if we do not want to specify all of them in 'this' persistence.xml file.

EntityManager and persistence-context

An instance of the interface EntityManager is used to initiate a persistence-context. We can obtain EntityManager instance by calling Entity Manger Factory# create Entity Manager Factory:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("testPersistenceUnit");
EntityManager entityManager = emf.createEntityManager();

What is persistence-context?

An EntityManager instance is associated with a persistence-context. The persistence-context is a set of managed unique entity instances. EntityManger interacts with this context to manage entity instances and their lifecycle.

EntityManager is not thread-safe, so we should use only one instance per thread.

Each EntityManagerFactory instance provides EntityManager instances that are all configured in the same manner, i.e. all will be using the same persistence-unit. More than one EntityManagerFactory instances can be used per application which would probably be pointing to different set of entities and data-sources etc.

When the application has finished using the entity manager factory, and/or at application shutdown, the application should close the entity manager factory by calling emf.close(). Once an entity manager factory has been closed, all entity managers created from it are considered to be in the closed state as well.

Example

In the following example we are going to demonstrate that how using same instance of EntityManager in two threads can fail.

Creating a simple entity

@Entity
public class MyEntity {
  @Id
  private int myId;
  private String myStr;
    .............
  @Override
  public String toString() {
      return "MyEntity{" +
              "myId=" + myId +
              ", myStr='" + myStr + '\'' +
              '}';
  }
  //getters/setters
}

src/main/resources/META-INF/persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
 <persistence-unit name="testPersistenceUnit" transaction-type="RESOURCE_LOCAL">
  <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
  <exclude-unlisted-classes>false</exclude-unlisted-classes>
  <properties>
   <property name="javax.persistence.schema-generation.database.action" value="create"/>
   <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"/>
  </properties>
 </persistence-unit>
</persistence>

Sharing the instance of a single EntityManager in two threads

public class NotThreadSafeExample {

  public static void main(String[] args) {
      final EntityManagerFactory emf = Persistence.createEntityManagerFactory("testPersistenceUnit");
      final EntityManager em = emf.createEntityManager();
      ExecutorService es = Executors.newFixedThreadPool(2);

      List<Future<?>> futures = new ArrayList<>();
      Future<?> f1 = es.submit(() -> {
          return runTask(em, 1, "test 1");

      });
      Future<?> f2 = es.submit(() -> {
          return runTask(em, 2, "test 2");
      });
      futures.add(f1);
      futures.add(f2);
      try {//waiting for threads to finish
          for (Future<?> future : futures) {
              future.get();
          }
      } catch (Exception e) {
          e.printStackTrace();
      }

      nativeQuery(em, "Select * from MyEntity");
      es.shutdown();
      em.close();
      emf.close();
  }

  private static String runTask(EntityManager em, int id, String str) {
      System.out.printf("persisting id: %s str:%s%n", id, str);
      em.getTransaction().begin();
      em.persist(createNewMyEntity(id, str));
      em.getTransaction().commit();
      return "done executing id: " + id;
  }

  private static MyEntity createNewMyEntity(int id, String str) {
      MyEntity entity = new MyEntity();
      entity.setMyId(id);
      entity.setMyStr(str);
      return entity;
  }

  private static void nativeQuery(EntityManager entityManager, String s) {
      System.out.println("--------\n" + s);
      Query query = entityManager.createNativeQuery(s);
      List list = query.getResultList();
      for (Object o : list) {
          System.out.println(Arrays.toString((Object[]) o));
      }
  }
}

Output

[EL Info]: 2017-03-06 02:57:59.24--ServerSession(110771485)--EclipseLink, version: Eclipse Persistence Services - 2.6.4.v20160829-44060b6
[EL Info]: connection: 2017-03-06 02:57:59.411--ServerSession(110771485)--/file:....._testPersistenceUnit login
    successful
persisting id: 2 str:test 2
persisting id: 1 str:test 1
[EL Warning]: 2017-03-06 02:57:59.536--UnitOfWork(874596964)--Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [myId] of class [com.logicbig.example.MyEntity] is mapped to a primary key column in the database. Updates are not allowed.
[EL Warning]: 2017-03-06 02:57:59.552--UnitOfWork(874596964)--Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [myId] of class [com.logicbig.example.MyEntity] is mapped to a primary key column in the database. Updates are not allowed.
java.util.concurrent.ExecutionException: javax.persistence.RollbackException: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [myId] of class [com.logicbig.example.MyEntity] is mapped to a primary key column in the database. Updates are not allowed.
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at com.logicbig.example.NotThreadSafeExample.main(NotThreadSafeExample.java:34)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: javax.persistence.RollbackException: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [myId] of class [com.logicbig.example.MyEntity] is mapped to a primary key column in the database. Updates are not allowed.
	at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:159)
	at com.logicbig.example.NotThreadSafeExample.runTask(NotThreadSafeExample.java:50)
	at com.logicbig.example.NotThreadSafeExample.lambda$main$0(NotThreadSafeExample.java:23)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.6.4.v20160829-44060b6): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The attribute [myId] of class [com.logicbig.example.MyEntity] is mapped to a primary key column in the database. Updates are not allowed.
	at org.eclipse.persistence.exceptions.ValidationException.primaryKeyUpdateDisallowed(ValidationException.java:2551)
	at org.eclipse.persistence.mappings.foundation.AbstractDirectMapping.writeFromObjectIntoRowWithChangeRecord(AbstractDirectMapping.java:1265)
	at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildRowForUpdateWithChangeSet(ObjectBuilder.java:1769)
	at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateObjectForWriteWithChangeSet(DatabaseQueryMechanism.java:1030)
	at org.eclipse.persistence.queries.UpdateObjectQuery.executeCommitWithChangeSet(UpdateObjectQuery.java:84)
	at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:301)
	at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
	at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
	at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:803)
	at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
	at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1790)
	at org.eclipse.persistence.internal.sessions.CommitManager.commitChangedObjectsForClassWithChangeSet(CommitManager.java:273)
	at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsForClassWithChangeSet(CommitManager.java:193)
	at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:139)
	at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4264)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1531)
	at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:278)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1169)
	at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:134)
--------
	... 6 more
Select * from MyEntity
[EL Info]: connection: 2017-03-06 02:57:59.568--ServerSession(110771485)--...target/classes/_testPersistenceUnit logout
    successful

    Process finished with exit code 0

From above exception, it's obvious that two 'commit' calls by two threads are not isolated because they are on same instance of the EntityManager. If we put a delay in one thread's 'submit' method then the exception will be avoided but we can still have the exception occasionally. In the following example we are going to see the correct way of using EntityManger in multi-threaded environment.

Using separate instances of EntityManager for the two threads

public class ThreadSafeExample {

  public static void main(String[] args) {

      final EntityManagerFactory emf = Persistence.createEntityManagerFactory("testPersistenceUnit");

      ExecutorService es = Executors.newFixedThreadPool(2);
      List<Future<?>> futures = new ArrayList<>();
      Future<?> f1 = es.submit(() -> {
          return runTask(emf, 1, "test 1");
      });
      Future<?> f2 = es.submit(() -> {
          return runTask(emf, 2, "test 2");
      });
      futures.add(f1);
      futures.add(f2);
      try {
          for (Future<?> future : futures) {
              future.get();
          }
      } catch (Exception e) {
          e.printStackTrace();
      }

      nativeQuery(emf, "Select * from MyEntity");
      es.shutdown();
      emf.close();
  }

  private static String runTask(EntityManagerFactory emf, int id, String str) {
      System.out.printf("persisting id: %s str:%s%n", id, str);
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
      em.persist(createNewMyEntity(id, str));
      em.getTransaction().commit();
      em.close();

      return "done executing id: " + id;
  }

  private static MyEntity createNewMyEntity(int id, String str) {
      MyEntity entity = new MyEntity();
      entity.setMyId(id);
      entity.setMyStr(str);
      return entity;
  }

  private static void nativeQuery(EntityManagerFactory emf, String s) {
      System.out.println("--------\n" + s);
      EntityManager em = emf.createEntityManager();
      Query query = em.createNativeQuery(s);
      List list = query.getResultList();
      for (Object o : list) {
          System.out.println(Arrays.toString((Object[]) o));
      }
  }
}

Output

persisting id: 1 str:test 1
persisting id: 2 str:test 2
--------
Select * from MyEntity
[1, test 1]
[2, test 2]

Example Project

Dependencies and Technologies Used:

  • h2 1.4.193: H2 Database Engine.
  • eclipselink 2.6.4: EclipseLink build based upon Git transaction 44060b6.
    Related JPA version: org.eclipse.persistence:javax.persistence version 2.1.1
  • javax.persistence 2.1.1: javax.persistence build based upon git transaction 40384f5.
  • JDK 1.8
  • Maven 3.3.9

Jpa Entity Manager Example Select All Download
  • entity-context-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • ThreadSafeExample.java
          • resources
            • META-INF

    See Also