Distributed Objects   «Prev  Next»

Lesson 7Jakarta Persistence Entities
ObjectiveExplain how Jakarta Persistence entities represent and manage durable domain data in Jakarta EE 11.

Jakarta Persistence Entities in Jakarta EE 11

Older enterprise Java courses used entity beans to represent persistent business information such as customers, bank accounts, and products. That component model is now historical. Entity beans have been removed from the Jakarta EE 11 Platform, and applications should not design new persistence layers around remote persistent components.

Jakarta Persistence 3.2 is the Jakarta EE 11 standard for persistence and object-relational mapping. Its central programming artifact is the entity: a lightweight Java domain object whose state can be stored in a relational database. An entity is not an Enterprise Bean and is not a remotely shared server object. CDI managed beans, Jakarta REST resources, servlets, and session beans can all call an application service that works with entities.

What Is a Jakarta Persistence Entity?

An entity class models durable application data. A Product entity, for example, can represent a row in a product table while presenting the application with fields and domain methods. The persistence provider maps entity state to database columns and coordinates loading and storing that state.

An entity is declared with @Entity or in mapping XML. Every entity has a primary key. A portable entity class is a top-level class or static inner class, has a public or protected no-argument constructor, and is not final. Its persistent fields or properties are also non-final. The location of mapping annotations determines whether the provider uses field access or property access, so an entity should use one access strategy consistently.

A Product Entity

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Version;

import java.math.BigDecimal;

@Entity
public class Product {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private BigDecimal price;

    @Version
    private long version;

    protected Product() {
    }

    public Product(String name, BigDecimal price) {
        this.name = name;
        this.price = price;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void changePrice(BigDecimal price) {
        if (price.signum() < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }
        this.price = price;
    }
}

The @Id field holds the persistent identity. @GeneratedValue asks the provider to obtain a key using its configured generation strategy. The @Version field supports optimistic locking, which detects conflicting updates. The protected constructor is available to the persistence provider, while the public constructor and changePrice method express application rules.

Entity classes can contain associations and richer behavior, but they should remain focused on the domain. They do not need remote interfaces, network stubs, or container-specific base classes.

Entity Identity and the Persistence Context

The value of an entity primary key identifies persistent data for EntityManager operations. Within one persistence context, a given persistent identity corresponds to one managed entity instance. This identity guarantee is local to that context. Loading product 42 in another request, transaction, or server can produce a different Java object representing the same database row.

A persistence context is a set of managed entity instances associated with an EntityManager. It acts as a unit of work between application objects and the database. The provider tracks changes made to managed entities and synchronizes those changes with the database, usually when the context is flushed as part of transaction completion.

This behavior is called dirty checking. Calling changePrice does not necessarily issue an immediate SQL statement. If the product is managed, the provider detects the changed field and writes it at the appropriate synchronization point. This allows several related changes to participate in one transaction.

Entity Lifecycle

An entity instance moves through lifecycle states according to how the application uses it:

StateMeaningTypical transition
New or transientThe object exists in memory but has no persistent identity managed by the current context.Create it with a constructor, then call persist.
ManagedThe object belongs to a persistence context, and tracked changes can be synchronized with the database.Use find, run a query, or persist a new object.
DetachedThe object still has entity identity, but its changes are no longer tracked by that persistence context.The context closes, is cleared, or the object is detached.
RemovedThe managed object is scheduled for deletion when the transaction is synchronized.Call remove on a managed entity.

EntityManager.persist makes a new entity managed and schedules its insertion. find retrieves an entity by primary key. Queries locate entities by other criteria. remove schedules a managed entity for deletion. merge copies state from a detached object into a managed instance and returns that managed instance; it does not reattach the argument itself. For ordinary request-based work, it is often clearer to load a managed entity and invoke a domain method than to treat merge as a universal update operation.

Transactions Belong in Application Services

Persistence operations that change durable state need a transaction boundary. In a Jakarta EE runtime, an application service can define that boundary and receive a container-managed EntityManager. The following stateless session bean uses container-managed transactions. By default, its business methods execute with a required transaction context.

import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

import java.math.BigDecimal;

@Stateless
public class ProductService {
    @PersistenceContext
    private EntityManager entityManager;

    public Product create(String name, BigDecimal price) {
        Product product = new Product(name, price);
        entityManager.persist(product);
        return product;
    }

    public Product find(long id) {
        return entityManager.find(Product.class, id);
    }

    public void changePrice(long id, BigDecimal price) {
        Product product = entityManager.find(Product.class, id);
        if (product == null) {
            throw new IllegalArgumentException("Unknown product: " + id);
        }
        product.changePrice(price);
    }

    public void remove(long id) {
        Product product = entityManager.find(Product.class, id);
        if (product != null) {
            entityManager.remove(product);
        }
    }
}

The service does not call an explicit update method after changePrice. The product returned by find is managed in the current persistence context, so dirty checking detects the new price. If the transaction commits successfully, the change is synchronized with the database. If the transaction rolls back, its database changes are not committed.

A Jakarta REST resource or UI controller can call this service, but the service should own the business operation and transaction boundary. Keeping persistence work behind an application service makes validation, authorization, error handling, and multi-entity updates easier to reason about.

Concurrent Updates and Optimistic Locking

Several users can work with the same product data at the same time, but they normally do so through different persistence contexts and different Java objects. They do not share one entity instance across client connections. The database transaction isolation level and the persistence provider's locking behavior determine how concurrent operations interact.

The @Version field enables optimistic locking. When two transactions read the same entity version and both attempt an update, the first commit advances the version. The second update can no longer match the expected version, so the provider reports an optimistic-lock conflict instead of silently overwriting the first change. The application can then ask the user to retry, reload current data, or resolve the conflict according to business rules.

Pessimistic locking is also available when an operation genuinely needs a database lock before making a decision. It can reduce concurrency and increase deadlock risk, so it should be chosen deliberately. Transactions and locking tools support consistency, but they do not automatically solve every business race. Rules such as inventory reservation still require a carefully designed operation and database constraints.

Legacy Entity Beans and Modern Entities

ConcernLegacy entity beanJakarta Persistence entity
RolePersistent server component in an older distributed-component modelLightweight persistent domain object mapped to durable data
Component typeEnterprise component managed through the old entity-bean contractJava class managed by a persistence provider when associated with a persistence context
IdentityComponent identity tied to the old container modelPrimary key declared with @Id or @EmbeddedId
Lifecycle managerEnterprise component containerEntityManager and its persistence context
Access patternCould be exposed as a distributed persistent objectUsed locally behind an application service; external APIs normally exchange DTOs
Persistence operationsCreation, finder, and removal contracts on component interfacespersist, find, queries, remove, and merge
Transactions and concurrencyContainer-centered component rulesJakarta Transactions, database isolation, and optimistic or pessimistic locking
Jakarta EE 11 statusRemoved from the platformJakarta Persistence 3.2 is the current platform persistence specification

The modern model separates concerns. An entity represents data and domain behavior. An application service coordinates a use case and its transaction. A REST resource, servlet, or UI adapter handles the external interaction. This separation avoids making the persistent object itself a network endpoint.

Persistence Is Not Passivation or Failover

Persistence means that committed business data is stored durably and can be loaded in a later transaction, including after an application restart. It does not mean that the same Java object remains alive. A process crash destroys the in-memory object and its persistence context; a later operation creates or loads another entity instance for the same database identity.

This differs from stateful-session-bean passivation, which temporarily preserves conversational component state, and from HTTP session replication or a distributed cache, which addresses web-session state. Service failover is an availability capability of the deployment architecture. None of these should be described as entity persistence.

Keep Entities Behind the Application Boundary

A practical request path is: HTTP or UI layer, application service, EntityManager and persistence context, then database. The service loads and changes entities within a transaction. The external layer sends command data inward and returns an explicit response model or DTO.

Jakarta EE 11 clients access an application service that manages Product and Customer entities through EntityManager and a persistence context connected to a relational database
Jakarta Persistence entities are managed inside a persistence context behind an application service.

Returning entity graphs directly from a remote API creates avoidable coupling between database mappings and the public contract. It can also trigger problems with lazy associations after the persistence context ends, expose fields unintentionally, and make serialization cycles difficult to control. DTOs let the API choose exactly what data crosses the boundary while entities remain focused on persistence and domain rules.

Running Persistence with Tomcat 11

Jakarta Persistence 3.2 is part of the Jakarta EE 11 Platform, and a compatible full-platform runtime supplies the required integration with persistence and transaction services. Tomcat 11 is a Servlet container rather than a full Jakarta EE Platform implementation. A plain Tomcat deployment therefore needs an added persistence provider, a database driver, a configured data source, and suitable transaction support. An alternative is to deploy the application to a Jakarta EE 11 compatible runtime that supplies those platform services.

Summary

Jakarta Persistence entities are the modern way to model durable relational data in Jakarta EE 11. Each entity has persistent identity, and an EntityManager manages entity instances inside a persistence context. Application services define transaction boundaries, managed changes are synchronized through dirty checking, and version fields can detect conflicting updates. Entities are local domain objects, not shared remote components. Keeping entities behind service and DTO boundaries produces a clearer persistence design and a more stable external API. This architecture also keeps persistence decisions separate from client communication protocols.


JPA Entities - Quiz

Click the Quiz link below to make sure you understand the different types of beans.
JPA Entities - Quiz
In the next lesson, the deployment descriptor and the jar file used to package a bean will be discussed.

[1] Entity bean: An EJB that is an object representation of a piece of persistent data.

SEMrush Software