In Java Persistence API (JPA), entities represent the core domain objects that are mapped to relational database tables. These entities are managed by the Entity Manager, which serves as the central interface for all persistence operations. By understanding the roles of the Entity Manager and the Persistence Context, developers can efficiently manage object-relational mapping and ensure that data synchronization between the application and database is seamless.
The Entity Manager acts as a gateway for managing entities and their lifecycle within JPA. It provides an abstraction over the database operations, allowing developers to work with Java objects instead of SQL statements. Entities themselves are plain Java objects (POJOs) and are not automatically managed. For an object to become a “managed entity,” the application must invoke specific API methods provided by the Entity Manager.
Once an entity is managed, the Entity Manager ensures that it is synchronized with the database state. This involves performing operations such as persisting new entities, finding entities by their primary keys, removing entities, and updating existing records.
Some of the critical responsibilities of the Entity Manager include:
Managing the relationship between Java classes and the database schema: The Entity Manager maps entity classes to corresponding database tables.
Providing APIs for CRUD operations and queries: Through methods such as persist, find, and remove, it allows direct manipulation of entities and their persistence state.
Synchronizing in-memory objects with the database: The Entity Manager ensures that changes made to managed objects are tracked and reflected in the database.
Persistence Context
The Persistence Context is a fundamental concept in JPA that represents an in-memory repository of entities managed by the Entity Manager. It is sometimes described as a “main memory database” where entities reside in their managed state. Understanding the role and behavior of the Persistence Context is essential for effectively using JPA.
Tracking and Synchronization
When an entity is managed, its state is tracked by the Persistence Context. Any changes made to its properties are automatically monitored and can be synchronized with the database. This synchronization happens during transaction commits or when explicitly triggered.
Lifecycle of Managed Entities
Managed entities have dual existence:
As Java objects (POJOs) within the application.
As corresponding relational tuples in the database.
The link between these two representations exists only within the Persistence Context. When an entity leaves the context, it becomes “detached,” and any further modifications to it are not automatically propagated to the database.
Database Writes and Transactions
By default, updates to the database occur asynchronously. The Persistence Context writes changes to the database only when it is associated with an active transaction. Without a transaction, modifications remain in memory and are not persisted.
Interplay with Entity Manager
The Persistence Context is indirectly controlled by the Entity Manager. While applications interact only with the Entity Manager, the actual management of entity states and synchronization with the database happens within the Persistence Context.
Methods of the EntityManager Interface
The EntityManager interface provides a set of methods to perform various persistence operations.
Method Signature
Description
persist(Object entity)
This method makes an entity instance part of the Persistence Context, marking it as “managed.” Once managed, the entity will be synchronized with the database when required.
find(Class<T> entityClass, Object primaryKey)
Retrieves an entity by its primary key. If found, the entity is loaded into the Persistence Context and becomes managed.
remove(Object entity)
Deletes an entity from the Persistence Context and, consequently, from the database.
refresh(Object entity)
Resets the state of an entity to match its current state in the database. This is useful if the database was updated by another process, and the in-memory state needs to be re-synchronized.
flush()
Forces the Persistence Context to write the current state of managed entities to the database immediately, regardless of transaction boundaries.
graph LR
A[Client] -- Interacts with --> B[Entity Manager]
B -- Manages objects within --> C[Persistence Context]
C -- Contains --> D[Persistent Objects]
D -- Instances of classes belonging to --> E[Persistence Unit]
F[Transaction] -- Is associated with --> C
Managing Entities with the Entity Manager in JPA
In Java Persistence API (JPA), the process of managing entities involves transitioning them between different states (transient, managed, or removed) while interacting with the Entity Manager. This interaction determines how the application and the database stay synchronized.
Creating a New POJO
When a new entity object is instantiated using the new operator, it starts its lifecycle in a transient state. At this stage:
The entity exists purely as a Plain Old Java Object (POJO), disconnected from any database or Persistence Context.
No interaction with the Entity Manager or underlying database occurs.
The entity is unknown to the Persistence Context until explicitly made managed.
Employee emp = new Employee(ID, "John Doe"); // emp is transient, unknown to the Entity Manager
In the transient state, the Entity Manager is unaware of the entity’s existence. As such, it cannot perform operations like persisting, updating, or synchronizing the object with the database.
Transitioning to the Managed State
To make an entity managed, the persist() method of the Entity Manager is called. This method transitions a transient object into the managed state. However, it is critical to understand that managed does not mean immediately saved to the database. Instead, the entity becomes part of the Persistence Context, and its state is tracked for eventual synchronization with the database.
Employee emp = new Employee(ID, "John Doe"); // emp is transientem.persist(emp); // emp is now managed
When the persist() method is invoked:
The entity enters the managed state and is added to the Persistence Context.
Any changes to the entity are tracked and will eventually be reflected in the database when the associated transaction commits.
Database synchronization can be triggered explicitly by calling the flush() method or implicitly when the transaction ends.
Important
A managed entity remains tied to the Persistence Context, ensuring that modifications to its state are tracked and eventually persisted to the database.
The managed state ends when the entity is detached, removed, or the Persistence Context closes.
For example, changes to the emp object after calling persist() will be tracked and saved to the database when the transaction commits:
emp.setName("Jane Doe"); // Change tracked by the Persistence Context
Finding an Entity
To retrieve an existing entity from the database, the find() method of the Entity Manager is used. This method requires two arguments:
The entity class to search for.
The primary key value of the desired entity.
If the entity exists in the database:
It is retrieved and added to the Persistence Context as a managed object.
Subsequent changes to the object will be tracked and synchronized with the database.
If the entity is not found, find() returns null.
Employee emp = em.find(Employee.class, ID); // emp is managed if found
The amount of data retrieved by find() depends on the fetch policy defined for the entity’s attributes and relationships (e.g., eager or lazy fetching).
Removing an Entity
The remove() method of the Entity Manager transitions a managed entity into the removed state. At this point:
The entity is scheduled for deletion from the database.
When the associated transaction commits or flush() is called, the tuple representing the entity in the database is deleted.
The entity object itself still exists in memory but is no longer tracked by the Persistence Context.
em.remove(emp); // emp is removed, no longer managed
When an entity is removed using the remove() method, the Java object itself remains in memory, but it is no longer managed by the Entity Manager. This means that any further changes to the object will not be tracked or synchronized with the database. The removal of the entity from the database becomes permanent only after the associated transaction commits.
Entity Lifecycle in JPA
The lifecycle of an entity in JPA is defined by its state within the application and its relationship with the Persistence Context and database. These states determine how the entity interacts with the Entity Manager, Persistence Context, and the underlying database.
NEW (Transient):
An entity is in the NEW state when it is created but not yet associated with the Entity Manager. At this stage:
The entity has no persistent identity (primary key) in the database.
No corresponding tuple exists in the database.
Changes to the entity are not tracked by the Persistence Context.
Employee emp = new Employee(ID, "John Doe"); // emp is NEW
MANAGED:
When the Entity Manager begins managing an entity, it transitions to the MANAGED state. In this state:
The entity is associated with the Persistence Context.
Changes to the entity are tracked and synchronized with the database when the transaction commits.
Synchronization occurs one-way (from entity to database), not the other way around.
em.persist(emp); // emp is now MANAGED
DETACHED:
When an entity exits the Persistence Context but retains its identity (primary key), it enters the DETACHED state. Key characteristics:
The entity is no longer managed by the Entity Manager.
Changes to the entity are not tracked or synchronized with the database.
The entity can be re-attached using methods like merge().
em.detach(emp); // emp is now DETACHED
REMOVED:
When the Entity Manager schedules an entity for deletion, it enters the REMOVED state. Characteristics include:
The entity is marked for removal from the database.
The deletion occurs when the transaction commits.
em.remove(emp); // emp is REMOVED
DELETED:
Once the transaction commits and the database tuple is erased, the entity transitions to the DELETED state. The Java object may still exist in memory but is no longer associated with the database.
JPA Application Architectures
JPA-based applications are often designed to leverage container services in Java Enterprise Edition (JEE) environments. These architectures promote a clean separation of concerns and simplify the management of transactions and persistence.
Java EE Architecture Overview
In a typical JEE architecture, the client—such as a web servlet or a front-end component—interacts with a business component, often implemented as an Enterprise JavaBean (EJB) or a Contexts and Dependency Injection (CDI) bean. These business components serve as intermediaries between the client and the persistence layer, communicating with the Entity Manager to perform operations such as creating, reading, updating, or deleting entities.
The application server container, such as an EJB container, plays a crucial role by providing essential services to these business components. It manages dependency injection, ensuring that resources like the Entity Manager are automatically made available where needed. The container also handles the lifecycle of business objects and manages transactional boundaries for Entity Manager operations, so developers do not need to write explicit transaction management code.
Additionally, the container is responsible for resource management, including connection pooling and cleanup, which helps optimize performance and reliability. By delegating these infrastructure concerns to the container, the application code remains focused on business logic, resulting in a cleaner and more maintainable architecture.
Example
For example, in a JEE web application:
A servlet can invoke an EJB method, which interacts with the Entity Manager to persist or query entities.
The container ensures the persist() or find() methods are executed within a transaction.
Transaction Management in JPA
graph LR
A[Container Transactions] -- use --> B[Resource-Local Transactions] -- use --> C[DBMS Transactions]
Effective database application development requires a thorough understanding of transactions. Transactions ensure data consistency and integrity, even in the presence of concurrent operations. In JPA, transaction management happens at multiple abstraction levels:
DBMS Transactions:
These are the lowest-level transactions that occur entirely within the database. They are demarcated by SQL commands (BEGIN, COMMIT, ROLLBACK) and are typically invisible to the application unless explicitly used.
Resource-Local Transactions:
Resource-local transactions are managed programmatically by the application. Developers are responsible for explicitly beginning, committing, or rolling back the transaction through the Entity Manager.
Container Transactions:
These transactions are managed by the Java Transaction API (JTA) and are typically used in JEE environments. The container automatically maps JTA transactions to JDBC transactions. The developer can configure whether transactions are application-managed or container-managed.
@Transactionalpublic void saveEmployee(Employee emp) { em.persist(emp); // Automatically part of a container-managed transaction}
In JEE applications, container transactions are the most commonly used approach. These transactions offer:
Automatic management of transaction boundaries by the container.
Declarative configuration through annotations (e.g., @Transactional) or XML descriptors.
Integration with EJB or CDI components for seamless persistence operations.
For example, when a method is annotated with @Transactional, all operations performed by the Entity Manager within that method are executed as part of a single transaction. If an exception is thrown during the execution of the method, the container automatically rolls back the transaction, ensuring data consistency and integrity without requiring explicit transaction management code from the developer.
Container-Managed (CM) Entity Manager
In Java EE, the Container-Managed Entity Manager (CM EM) simplifies persistence management by delegating the creation, lifecycle, and transaction management of the EntityManager to the application server container. This type of Entity Manager is the default and ensures seamless integration with enterprise services like transactions and dependency injection.
Injection into Business Objects:
The container injects the EntityManager instance into EJBs or CDI-managed beans using the @PersistenceContext annotation. @PersistenceContext specifies the persistence unit to use, allowing the container to manage the EntityManager lifecycle automatically.
@Stateless // Indicates that this is a business object (EJB)public class MyEJBService { @PersistenceContext(unitName = "MyPersistenceUnit") private EntityManager em; // Automatically managed by the container}
Automatic Lifecycle Management:
The container creates and destroys instances of the EntityManager as needed.
Developers do not need to explicitly manage the EntityManager lifecycle.
Transaction Integration:
The container automatically associates the EntityManager with a transaction.
When the transaction commits, the container ensures that changes made to entities in the Persistence Context are synchronized with the database.
Example of CM Transaction in Action
Consider a web application where a servlet interacts with a business object (EJB) to close a mission.
Client Code (Web Servlet)
In this example, the servlet uses dependency injection to access the MissionService EJB, which contains business logic for closing a mission.
@EJB(name = "it.polimi.db2.mission.services/MissionService")private MissionService mService; // Injected business objectpublic void closeMission(int missionId, int userId) { Mission mission = mService.closeMission(missionId, userId);}
The @EJB annotation indicates that the MissionService is an EJB (Enterprise JavaBean) that will be injected by the container. The container manages the lifecycle of this EJB, including its EntityManager.
Business Component Code (MissionService EJB)
The MissionService EJB uses the @PersistenceContext annotation to inject the EntityManager. This allows the EJB to perform persistence operations without needing to manage the EntityManager lifecycle explicitly.
@Statelesspublic class MissionService { @PersistenceContext(unitName = "MyPersistenceUnit") private EntityManager em; // Transaction automatically started by the container public void closeMission(int missionId, int reporterId) throws BadMissionReporter, BadMissionForClosing { // Persistence Context created and linked to the transaction Mission mission = em.find(Mission.class, missionId); if (mission.getReporter().getId() != reporterId) { throw new BadMissionReporter("Reporter mismatch."); } if (mission.getStatus() != MissionStatus.REPORTED) { throw new BadMissionForClosing("Mission not in REPORTED status."); } mission.setStatus(MissionStatus.CLOSED); // Transaction committed automatically at method end }}
Key Features:
The EntityManager is created automatically when the transaction starts.
Changes to entities (mission.setStatus) are tracked and synchronized with the database when the transaction commits.
There is no explicit transaction management code, as it is handled by the container.
When a client (e.g., a servlet) invokes a method of a business object, the container handles the transaction lifecycle:
Transaction Creation:
The container creates or reuses a transaction when a method call is made to an EJB or CDI-managed component.
Transaction Propagation:
If the called method invokes another method (within the same or a different business object), the same transaction is reused by default.
This behavior can be customized using annotations to control transaction boundaries and propagation.
Transactional Behavior of Methods
The @TransactionAttribute annotation allows developers to define the transactional behavior of methods within business components. The TransactionAttributeType parameter specifies how the container manages transactions for the annotated method.
Transaction Attribute
Description
TransactionAttributeType.MANDATORY
Requires an existing transaction to be active when the method is called. If no transaction is active, an exception is thrown.
TransactionAttributeType.REQUIRED (default)
If no transaction is active, the container starts one. If a transaction is already active (e.g., initiated by the caller), it is reused.
TransactionAttributeType.REQUIRES_NEW
Always executes the method within a new transaction. Suspends any active transaction.
TransactionAttributeType.SUPPORTS
The method does not require a transaction but will participate in one if it exists.
TransactionAttributeType.NOT_SUPPORTED
Executes the method outside of any transaction. Suspends any active transaction during method execution.
TransactionAttributeType.NEVER
Ensures that the method executes without a transaction. Throws an exception if a transaction is active when the method is called.
Example of Transactional Attributes
In a mission management service, different methods may require different transactional behaviors. For instance, closing a mission might need to be part of an existing transaction, while logging the closure could be done in a separate transaction.
@Statelesspublic class MissionService { @PersistenceContext private EntityManager em; // Default behavior: REQUIRED @TransactionAttribute(TransactionAttributeType.REQUIRED) public void closeMission(int missionId) { Mission mission = em.find(Mission.class, missionId); mission.setStatus(MissionStatus.CLOSED); } // Custom behavior: REQUIRES_NEW @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void logMissionClosure(int missionId) { // Executes in a separate transaction System.out.println("Mission " + missionId + " closed."); }}