JPA EntityManager Interview Questions and Answers

Master JPA EntityManager with production-ready interview questions covering EntityManagerFactory, persist, find, getReference, merge, remove, flush, clear, refresh, detach, thread safety, lifecycle behavior, batch processing, and senior-level best practices.


JPA EntityManager Interview Questions and Answers

Introduction

EntityManager is the primary JPA interface used to interact with the persistence context.

It manages entity lifecycle operations such as:

  • Creating persistent entities
  • Loading entities
  • Updating managed state
  • Removing entities
  • Flushing pending SQL
  • Detaching entities
  • Refreshing entity state
  • Executing JPQL and native queries
  • Applying locks
  • Accessing persistence-context behavior

Many JPA problems are caused by misunderstanding how EntityManager works.

Common issues include:

  • Calling merge() unnecessarily
  • Expecting detached entities to update automatically
  • Confusing flush() with commit
  • Sharing an EntityManager across threads
  • Keeping too many entities managed
  • Calling remove() on detached entities
  • Using getReference() without understanding proxies
  • Assuming find() always executes SQL
  • Clearing the persistence context too early
  • Creating EntityManagerFactory repeatedly

A strong JPA developer must understand that EntityManager is not simply a CRUD utility.

It represents the entry point into a persistence context that:

  • Tracks entity identity
  • Detects changes
  • Queues SQL operations
  • Coordinates transactions
  • Maintains first-level caching
  • Handles entity state transitions

This guide covers the 15 most frequently asked EntityManager interview questions with production examples, Mermaid diagrams, code snippets, common mistakes, and senior-level recommendations.


Q1. What is EntityManager?

Answer

EntityManager is the main JPA interface used to manage persistent entities and interact with the persistence context.

It provides operations such as:

  • persist()
  • find()
  • getReference()
  • merge()
  • remove()
  • flush()
  • clear()
  • detach()
  • refresh()
  • lock()
  • Query creation

Basic Example

@PersistenceContext
private EntityManager entityManager;
Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

EntityManager Architecture

flowchart TD
    A[Application Service] --> B[EntityManager]
    B --> C[Persistence Context]
    C --> D[Managed Entities]
    C --> E[Pending Inserts]
    C --> F[Pending Updates]
    C --> G[Pending Deletes]
    B --> H[JPA Provider]
    H --> I[JDBC]
    I --> J[Database]

Main Responsibilities

Entity Lifecycle

It moves entities between:

  • Transient
  • Managed
  • Detached
  • Removed

Persistence Context Access

It tracks managed entities and their changes.

Query Execution

It creates:

  • JPQL queries
  • Criteria queries
  • Native SQL queries
  • Named queries

Synchronization

It flushes pending entity changes to the database.

Locking

It applies optimistic or pessimistic locks.

Why Interviewers Ask This

Interviewers want to confirm that you understand EntityManager as the central JPA persistence-context interface rather than a repository implementation.

Production Example

An order service uses EntityManager to:

  1. Load an order
  2. Validate its current state
  3. Modify the order
  4. Persist a new audit entry
  5. Commit both changes atomically

Common Mistake

Describing EntityManager as only a database connection.

Senior Follow-up

EntityManager is not a raw connection. It manages entity identity, lifecycle, dirty checking, write-behind, queries, and transaction participation.


Q2. How is EntityManager created?

Answer

An EntityManager can be created in two main ways:

  • Container-managed
  • Application-managed

Container-Managed EntityManager

Common in Jakarta EE.

@PersistenceContext
private EntityManager entityManager;

The container manages:

  • Creation
  • Transaction association
  • Persistence-context lifecycle
  • Cleanup
  • Context propagation

Constructor-Injected Example

@ApplicationScoped
public class CustomerRepository {

    private final EntityManager
        entityManager;

    @Inject
    public CustomerRepository(
        EntityManager entityManager
    ) {
        this.entityManager =
            entityManager;
    }
}

Whether direct constructor injection is supported depends on the runtime and integration model.

Application-Managed EntityManager

EntityManagerFactory factory =
    Persistence.createEntityManagerFactory(
        "applicationPU"
    );

EntityManager entityManager =
    factory.createEntityManager();

The application must close it:

try {
    // Use EntityManager
} finally {
    entityManager.close();
}

Creation Flow

flowchart TD
    A[Persistence Unit] --> B[EntityManagerFactory]
    B --> C[Create EntityManager]
    C --> D[Persistence Context]
    D --> E[Entity Operations]

Container-Managed Flow

sequenceDiagram
    participant Container
    participant Service
    participant EntityManagerProxy
    participant PersistenceContext
    Container->>Service: Create managed service
    Container->>Service: Inject EntityManager proxy
    Service->>EntityManagerProxy: Call find()
    EntityManagerProxy->>PersistenceContext: Resolve transaction context
    PersistenceContext-->>Service: Managed entity

Application-Managed Flow

sequenceDiagram
    participant Application
    participant EntityManagerFactory
    participant EntityManager
    Application->>EntityManagerFactory: createEntityManager()
    EntityManagerFactory-->>Application: EntityManager
    Application->>EntityManager: Use persistence operations
    Application->>EntityManager: close()

Common Mistake

Creating an EntityManager manually inside every repository method.

Senior Follow-up

Prefer container-managed or framework-managed EntityManager instances in enterprise applications so transaction and lifecycle behavior remain consistent.


Q3. What is EntityManagerFactory?

Answer

EntityManagerFactory creates EntityManager instances.

It is initialized from a persistence unit and contains expensive, shared persistence metadata.

It commonly manages or coordinates:

  • Entity mapping metadata
  • Provider configuration
  • Connection integration
  • Query-plan metadata
  • Second-level cache
  • EntityManager creation

Example

EntityManagerFactory factory =
    Persistence.createEntityManagerFactory(
        "applicationPU"
    );
EntityManager entityManager =
    factory.createEntityManager();

Architecture

flowchart TD
    A[persistence.xml] --> B[Persistence Unit]
    B --> C[EntityManagerFactory]
    C --> D[EntityManager 1]
    C --> E[EntityManager 2]
    C --> F[EntityManager N]

    D --> G[Persistence Context 1]
    E --> H[Persistence Context 2]
    F --> I[Persistence Context N]

Lifecycle

EntityManagerFactory factory =
    Persistence.createEntityManagerFactory(
        "applicationPU"
    );

try {
    EntityManager entityManager =
        factory.createEntityManager();

    try {
        // Work
    } finally {
        entityManager.close();
    }
} finally {
    factory.close();
}

Important Characteristics

EntityManagerFactory is typically:

  • Expensive to create
  • Thread-safe
  • Shared across the application
  • Created once per persistence unit
  • Closed during application shutdown

Common Mistake

Creating a new EntityManagerFactory for every request.

Senior Follow-up

EntityManagerFactory should normally be application-scoped because repeated creation is expensive and wastes provider metadata, cache, and resource initialization.


Q4. EntityManager vs EntityManagerFactory?

Answer

EntityManagerFactory creates EntityManager instances.

EntityManager performs persistence operations and manages one persistence-context view.

Comparison

EntityManagerFactory EntityManager
Creates EntityManagers Manages entity lifecycle
Expensive to create Lightweight compared with factory
Usually application-scoped Usually transaction or request scoped
Thread-safe Not thread-safe
Holds mapping metadata Holds persistence-context state
May own second-level cache Owns first-level cache
One per persistence unit Many instances over application lifetime

Relationship

flowchart LR
    A[EntityManagerFactory] --> B[EntityManager A]
    A --> C[EntityManager B]
    A --> D[EntityManager C]

    B --> E[Persistence Context A]
    C --> F[Persistence Context B]
    D --> G[Persistence Context C]

Example

EntityManagerFactory factory =
    Persistence.createEntityManagerFactory(
        "applicationPU"
    );

EntityManager first =
    factory.createEntityManager();

EntityManager second =
    factory.createEntityManager();

first and second have independent persistence contexts.

Identity Example

Customer firstCustomer =
    first.find(
        Customer.class,
        101L
    );

Customer secondCustomer =
    second.find(
        Customer.class,
        101L
    );

They represent the same database row but are usually different Java object instances because they belong to different persistence contexts.

Common Mistake

Assuming entities loaded through different EntityManagers are the same object instance.

Senior Follow-up

Entity identity guarantees apply within one persistence context, not globally across all EntityManager instances.


Q5. Is EntityManager thread-safe?

Answer

No.

EntityManager is not thread-safe and should not be shared manually across concurrent threads.

It holds mutable persistence-context state such as:

  • Managed entities
  • Entity snapshots
  • Pending changes
  • Lock state
  • Query state
  • Transaction association

Unsafe Example

public class SharedRepository {

    private static EntityManager
        entityManager;
}

Multiple threads using the same instance can create:

  • Race conditions
  • Corrupted persistence state
  • Incorrect entity visibility
  • Flush conflicts
  • Transaction mixing
  • Unexpected exceptions

Unsafe Thread Flow

sequenceDiagram
    participant ThreadA
    participant SharedEntityManager
    participant ThreadB
    ThreadA->>SharedEntityManager: Load Order 101
    ThreadB->>SharedEntityManager: Clear context
    ThreadA->>SharedEntityManager: Modify Order 101
    Note over SharedEntityManager: Entity is unexpectedly detached

Safe Enterprise Pattern

@PersistenceContext
private EntityManager entityManager;

In Jakarta EE or Spring, the injected reference is often a proxy that resolves the correct underlying persistence context.

Important Distinction

The injected proxy may be safe to keep in an application-scoped component because the proxy routes calls to the correct context.

The actual EntityManager and underlying persistence context are not thread-safe.

Async Warning

CompletableFuture.runAsync(
    () -> entityManager.persist(entity)
);

This is unsafe because:

  • The transaction may not propagate
  • The persistence context may not propagate
  • The EntityManager may be accessed from another thread
  • The request may already be complete

Common Mistake

Passing managed entities and EntityManager references into asynchronous worker threads.

Senior Follow-up

Use a new transaction and a separately managed persistence context in the asynchronous execution environment.


Q6. What does persist() do?

Answer

persist() makes a transient entity managed and schedules it for insertion.

Example

Customer customer =
    new Customer(
        "Venu",
        "[email protected]"
    );

entityManager.persist(customer);

State Transition

stateDiagram-v2
    [*] --> Transient
    Transient --> Managed: persist()
    Managed --> DatabaseRow: flush / commit

Persist Flow

sequenceDiagram
    participant Service
    participant EntityManager
    participant PersistenceContext
    participant Database
    Service->>EntityManager: persist(customer)
    EntityManager->>PersistenceContext: Register managed entity
    PersistenceContext->>PersistenceContext: Schedule INSERT
    Note over Database: SQL may not execute immediately
    Service->>EntityManager: flush or commit
    EntityManager->>Database: INSERT customer

Key Behavior

After persist():

  • The same object becomes managed.
  • The persistence context tracks it.
  • Cascades may apply.
  • An insert is scheduled.
  • The generated ID may be assigned immediately or later depending on strategy.
  • Changes after persist() are also tracked.

Example

entityManager.persist(customer);

customer.changeName(
    "Venugopal"
);

The inserted row may contain the updated name.

Persist and Cascades

@OneToMany(
    mappedBy = "order",
    cascade = CascadeType.PERSIST
)
private List<OrderItem> items;
entityManager.persist(order);

Owned items may also be persisted.

persist() Return Value

persist() returns void.

The original object becomes managed.

Duplicate Persist

Calling persist() for an entity whose identifier already exists may lead to an entity-exists or database constraint failure.

Common Mistake

Expecting persist() to execute INSERT immediately in every case.

Senior Follow-up

JPA uses transactional write-behind. SQL may execute during flush, before certain queries, or at transaction commit.


Q7. What does find() do?

Answer

find() retrieves an entity by its primary key.

Example

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

Return Behavior

  • Returns the managed entity if found
  • Returns null if no row exists
  • Checks the persistence context first
  • May check second-level cache
  • Queries the database if needed

Lookup Flow

flowchart TD
    A[find Customer 101] --> B[Check Persistence Context]
    B -->|Found| C[Return Managed Entity]
    B -->|Not Found| D[Check Shared Cache if Enabled]
    D -->|Found| E[Create Managed Instance]
    D -->|Not Found| F[Execute SELECT]
    F --> G{Row Exists?}
    G -->|Yes| H[Create and Manage Entity]
    G -->|No| I[Return null]

First-Level Cache Example

Customer first =
    entityManager.find(
        Customer.class,
        101L
    );

Customer second =
    entityManager.find(
        Customer.class,
        101L
    );

Normally:

first == second

is true inside one persistence context.

SQL Behavior

First lookup may execute:

SELECT
    id,
    name,
    email
FROM customers
WHERE id = ?;

The second may not execute SQL because the entity is already managed.

Locking with Find

Account account =
    entityManager.find(
        Account.class,
        accountId,
        LockModeType.PESSIMISTIC_WRITE
    );

Properties with Find

Map<String, Object> properties =
    Map.of(
        "jakarta.persistence.lock.timeout",
        3000
    );

Account account =
    entityManager.find(
        Account.class,
        accountId,
        LockModeType.PESSIMISTIC_WRITE,
        properties
    );

Common Mistake

Calling methods on the result without checking for null.

Senior Follow-up

Use find() for primary-key lookups when the entity data is required. Use getReference() when only a managed identity reference is needed.


Q8. What does getReference() do?

Answer

getReference() returns a managed entity reference for a given primary key.

The provider may return a proxy without immediately querying the database.

Example

Customer customerReference =
    entityManager.getReference(
        Customer.class,
        customerId
    );

Common Use Case

Assigning a relationship without loading the complete referenced entity.

Customer customer =
    entityManager.getReference(
        Customer.class,
        customerId
    );

Order order =
    new Order(customer);

entityManager.persist(order);

This may avoid an unnecessary customer SELECT.

Proxy Flow

flowchart TD
    A[getReference Customer 101] --> B[Create or Return Proxy]
    B --> C{Only ID Needed?}
    C -->|Yes| D[No Immediate SELECT]
    C -->|No, Access Name| E[Initialize Proxy]
    E --> F[Execute SELECT]
    F --> G{Row Exists?}
    G -->|Yes| H[Return Data]
    G -->|No| I[EntityNotFoundException]

Difference from find()

find() getReference()
Usually loads entity data May return uninitialized proxy
Returns null if absent May fail later when initialized
Good when data is required Good when only identity is needed
Immediate existence knowledge Existence may be deferred

Example

Customer reference =
    entityManager.getReference(
        Customer.class,
        999L
    );

This may appear successful initially.

Later:

reference.getName();

may throw EntityNotFoundException if the row does not exist.

Delete Without Full Load

In some providers, a reference can be used to remove an entity:

Customer reference =
    entityManager.getReference(
        Customer.class,
        customerId
    );

entityManager.remove(reference);

Provider behavior and callbacks may still affect whether a select occurs.

Common Mistake

Using getReference() when immediate existence validation is required.

Senior Follow-up

Use getReference() to establish associations efficiently, but use find() when business logic must verify or inspect the referenced entity.


Q9. What does merge() do?

Answer

merge() copies state from a detached or transient object into a managed instance.

It returns the managed instance.

The object passed to merge() does not become managed automatically.

Example

Customer managedCustomer =
    entityManager.merge(
        detachedCustomer
    );

Important Behavior

detachedCustomer != managedCustomer

may be true.

Merge Flow

sequenceDiagram
    participant DetachedEntity
    participant EntityManager
    participant PersistenceContext
    participant Database
    DetachedEntity->>EntityManager: merge(detached)
    EntityManager->>PersistenceContext: Find existing managed entity
    alt Managed instance already exists
        PersistenceContext->>PersistenceContext: Copy detached state
    else No managed instance
        PersistenceContext->>Database: SELECT by ID if required
        Database-->>PersistenceContext: Current row
        PersistenceContext->>PersistenceContext: Create managed instance
        PersistenceContext->>PersistenceContext: Copy detached state
    end
    EntityManager-->>DetachedEntity: Return managed copy

Example

Customer detached =
    loadCustomerInPreviousTransaction();

detached.changeName(
    "Updated Name"
);

Customer managed =
    entityManager.merge(detached);

After merge:

  • managed is tracked.
  • detached is still detached.
  • Future changes to managed are tracked.
  • Future changes to detached are not tracked.

Dangerous Pattern

entityManager.merge(
    requestEntity
);

This can overwrite:

  • Fields not included in the request
  • Security-sensitive fields
  • Relationships
  • Version state
  • Audit metadata

Safer Update Pattern

@Transactional
public void updateCustomer(
    Long customerId,
    UpdateCustomerRequest request
) {

    Customer customer =
        entityManager.find(
            Customer.class,
            customerId
        );

    if (customer == null) {
        throw new CustomerNotFoundException(
            customerId
        );
    }

    customer.changeName(
        request.name()
    );

    customer.changeEmail(
        request.email()
    );
}

Merge Cascade

@OneToMany(
    cascade = CascadeType.MERGE
)
private List<OrderItem> items;

State may be copied through the relationship graph, which can create unintended updates.

Common Mistake

Continuing to use the detached object after calling merge().

Senior Follow-up

Prefer loading a managed aggregate and applying controlled changes. Reserve merge for deliberate detached-state workflows.


Q10. What does remove() do?

Answer

remove() marks a managed entity for deletion.

Example

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

entityManager.remove(customer);

State Transition

stateDiagram-v2
    Managed --> Removed: remove()
    Removed --> Deleted: flush / commit

Remove Flow

sequenceDiagram
    participant Service
    participant EntityManager
    participant PersistenceContext
    participant Database
    Service->>EntityManager: remove(customer)
    EntityManager->>PersistenceContext: Mark entity removed
    Service->>EntityManager: flush or commit
    EntityManager->>Database: DELETE customer

Managed Entity Requirement

The entity passed to remove() should be managed.

Unsafe:

entityManager.remove(
    detachedCustomer
);

This may throw IllegalArgumentException.

Remove Detached Entity

Customer managed =
    entityManager.merge(
        detachedCustomer
    );

entityManager.remove(managed);

However, loading by ID is often safer:

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

if (customer != null) {
    entityManager.remove(customer);
}

Cascade Remove

@OneToMany(
    mappedBy = "order",
    cascade = CascadeType.REMOVE
)
private List<OrderItem> items;

Deleting the order may also delete order items.

Orphan Removal

@OneToMany(
    mappedBy = "order",
    orphanRemoval = true
)
private List<OrderItem> items;

Removing a child from the collection may delete that child row.

Dangerous Cascade

@ManyToOne(
    cascade = CascadeType.REMOVE
)
private Department department;

Deleting an employee could attempt to delete a shared department.

Common Mistake

Applying CascadeType.REMOVE to shared entities.

Senior Follow-up

Delete cascades should follow aggregate ownership. Shared reference entities should usually have independent lifecycles.


Q11. What do flush() and clear() do?

Answer

flush() synchronizes pending persistence-context changes with the database.

clear() detaches all managed entities from the persistence context.

flush()

entityManager.flush();

It may execute pending:

  • INSERT
  • UPDATE
  • DELETE

clear()

entityManager.clear();

After clear():

  • Managed entities become detached.
  • First-level cache entries are removed.
  • Dirty checking stops for those objects.
  • Unflushed changes may be lost if not synchronized first.

Flush Flow

flowchart TD
    A[Managed Entity Changes] --> B[Persistence Context]
    B --> C[Dirty Checking]
    C --> D[Generate SQL]
    D --> E[Execute SQL]
    E --> F[Transaction Still Active]

Clear Flow

flowchart TD
    A[Persistence Context] --> B[Managed Customer]
    A --> C[Managed Order]
    A --> D[Managed Product]
    A --> E[clear()]
    E --> F[All Entities Detached]
    E --> G[First-Level Cache Empty]

Flush vs Commit

sequenceDiagram
    participant Service
    participant EntityManager
    participant Database
    Service->>EntityManager: flush()
    EntityManager->>Database: Execute SQL
    Note over Database: Transaction remains open
    alt Later success
        Service->>Database: Commit
    else Later failure
        Service->>Database: Rollback
    end

Key Difference

flush() Commit
Synchronizes SQL Finalizes transaction
Transaction remains active Transaction ends
Changes may still roll back Changes become durable
Can occur multiple times Usually once per transaction

Batch Processing Example

@Transactional
public void importCustomers(
    List<CustomerImportRow> rows
) {

    int batchSize = 500;

    for (
        int index = 0;
        index < rows.size();
        index++
    ) {

        Customer customer =
            mapToCustomer(
                rows.get(index)
            );

        entityManager.persist(customer);

        if (
            (index + 1) % batchSize == 0
        ) {
            entityManager.flush();
            entityManager.clear();
        }
    }

    entityManager.flush();
    entityManager.clear();
}

Why Use Both?

flush() ensures SQL is sent.

clear() releases managed entity references and snapshots.

Batch Flow

flowchart TD
    A[Read Row] --> B[Create Entity]
    B --> C[persist]
    C --> D{Batch Boundary?}
    D -->|No| A
    D -->|Yes| E[flush]
    E --> F[Execute Batched SQL]
    F --> G[clear]
    G --> H[Release Managed State]
    H --> A

Common Mistake

Calling clear() before flush() and losing pending managed changes.

Senior Follow-up

For large jobs, flush() and clear() must be coordinated with transaction boundaries, retry strategy, and checkpointing.


Q12. What does refresh() do?

Answer

refresh() reloads a managed entity's state from the database.

It overwrites in-memory changes with current database values.

Example

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

entityManager.refresh(customer);

Refresh Flow

sequenceDiagram
    participant Service
    participant EntityManager
    participant Database
    Service->>EntityManager: refresh(customer)
    EntityManager->>Database: SELECT current row
    Database-->>EntityManager: Current state
    EntityManager->>Service: Overwrite managed entity fields

Example

customer.changeName(
    "Temporary Name"
);

entityManager.refresh(customer);

After refresh, "Temporary Name" is replaced by the database value.

Use Cases

  • Database triggers changed columns
  • Another process updated the row
  • Stored procedure modified data
  • Need to discard local changes
  • Need a pessimistic lock with fresh state

Refresh with Lock

entityManager.refresh(
    account,
    LockModeType.PESSIMISTIC_WRITE
);

Limitations

  • Entity must be managed.
  • Local unsaved changes can be lost.
  • It causes database access.
  • Cascaded refresh may load large graphs.
  • It does not replace a proper concurrency strategy.

Cascade Refresh

@OneToMany(
    cascade = CascadeType.REFRESH
)
private List<OrderItem> items;

Refreshing the parent may refresh children.

Common Mistake

Calling refresh() after every update.

Senior Follow-up

Use refresh only when database-generated or externally modified state must be reloaded. Avoid using it to mask weak transaction design.


Q13. What does detach() do?

Answer

detach() removes one managed entity from the persistence context.

After detachment:

  • Dirty checking stops.
  • Changes are not automatically synchronized.
  • The entity remains a normal Java object.
  • Lazy associations may no longer initialize.
  • Cascaded detach may apply.

Example

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

entityManager.detach(customer);

State Transition

stateDiagram-v2
    Managed --> Detached: detach()

Example

entityManager.detach(customer);

customer.changeName(
    "Detached Name"
);

The name change is not automatically stored.

Detach Flow

flowchart TD
    A[Managed Entity] --> B[detach()]
    B --> C[Remove from Persistence Context]
    C --> D[Detached Entity]
    D --> E[No Dirty Checking]
    D --> F[No Automatic SQL]

detach() vs clear()

detach(entity) clear()
Detaches one entity Detaches all entities
Targeted operation Clears complete context
Other entities stay managed Context becomes empty
Good for selective control Good for batch memory control

contains()

You can check whether an entity is managed:

boolean managed =
    entityManager.contains(
        customer
    );

Before detach:

true

After detach:

false

Cascade Detach

@OneToMany(
    cascade = CascadeType.DETACH
)
private List<OrderItem> items;

Detaching the parent can detach children.

Common Mistake

Detaching an entity and then expecting later modifications to be committed.

Senior Follow-up

Detachment can reduce persistence-context overhead, but DTO projections are usually a better choice for read-only API responses.


Q14. What are common EntityManager mistakes?

Answer

Common mistakes include:

  • Sharing one EntityManager across threads
  • Creating EntityManagerFactory repeatedly
  • Forgetting to close application-managed EntityManagers
  • Using merge() for every update
  • Continuing to modify the detached object after merge
  • Calling remove() on a detached entity
  • Treating flush as commit
  • Clearing before flushing
  • Keeping millions of entities managed
  • Calling find() repeatedly without understanding first-level cache
  • Using getReference() when existence must be checked
  • Assuming persist() always inserts immediately
  • Passing managed entities to async threads
  • Exposing entities directly to API clients
  • Ignoring transaction boundaries
  • Using refresh() excessively
  • Detaching entities unnecessarily
  • Assuming bulk JPQL updates synchronize managed entities
  • Calling EntityManager methods outside the required transaction
  • Ignoring exceptions that mark the transaction rollback-only

Blind Merge Anti-Pattern

@Transactional
public Customer update(
    Customer requestEntity
) {
    return entityManager.merge(
        requestEntity
    );
}

Problems

  • Request fields can overwrite protected data.
  • Missing fields may overwrite current values.
  • Relationships can be replaced.
  • Stale state can be copied.
  • Cascade merge can traverse large graphs.
  • API and persistence models become coupled.

Safer Update

@Transactional
public CustomerResponse updateCustomer(
    Long customerId,
    UpdateCustomerRequest request
) {

    Customer customer =
        entityManager.find(
            Customer.class,
            customerId
        );

    if (customer == null) {
        throw new CustomerNotFoundException(
            customerId
        );
    }

    customer.changeName(
        request.name()
    );

    customer.changeEmail(
        request.email()
    );

    return customerMapper.toResponse(
        customer
    );
}

Large Context Anti-Pattern

@Transactional
public void importRows(
    List<Row> rows
) {
    for (Row row : rows) {
        entityManager.persist(
            map(row)
        );
    }
}

With one million rows, the persistence context may retain:

  • One million entities
  • Snapshots
  • Relationship state
  • Pending actions

Memory Risk

flowchart TD
    A[Persist Many Entities] --> B[Persistence Context Grows]
    B --> C[More Snapshots]
    C --> D[Higher Heap Usage]
    D --> E[Longer Dirty Checking]
    E --> F[GC Pressure or OutOfMemory]

Async Anti-Pattern

@Asynchronous
public void process(
    Customer managedCustomer
) {
    managedCustomer.changeName(
        "Async Update"
    );
}

The entity may be detached in the async context.

Pass identifiers or immutable DTOs instead.

Interview Tip

Strong candidates explain both API behavior and persistence-context consequences.


Q15. What are senior-level EntityManager best practices?

Answer

Senior developers should use EntityManager with explicit lifecycle, transaction, and performance boundaries.

Best Practices

  • Prefer container-managed EntityManager.
  • Keep EntityManager scoped to a transaction or request.
  • Never share the actual EntityManager across threads.
  • Keep EntityManagerFactory application-scoped.
  • Use persist() for new entities.
  • Use find() when entity state is required.
  • Use getReference() when only identity is needed.
  • Avoid blind merge().
  • Load managed entities for controlled updates.
  • Call remove() only on managed entities.
  • Understand flush timing.
  • Do not treat flush as commit.
  • Use flush() and clear() for large batches.
  • Keep persistence contexts small.
  • Avoid passing managed entities across async boundaries.
  • Use DTO projections for read-heavy APIs.
  • Clear or refresh after bulk DML.
  • Apply locking intentionally.
  • Review generated SQL.
  • Test failure and rollback behavior.

EntityManager Decision Flow

flowchart TD
    A[Entity Operation] --> B{New Entity?}

    B -->|Yes| C[persist]
    B -->|No| D{Need Current Data?}

    D -->|Yes| E[find]
    D -->|No, Only Identity| F[getReference]

    E --> G{Need Update?}
    G -->|Yes| H[Modify Managed Entity]
    G -->|No| I[Map to DTO]

    F --> J[Assign Relationship or Remove]

    K[Detached Entity] --> L{Can Load Managed Entity?}
    L -->|Yes| M[Load and Apply Controlled Changes]
    L -->|No| N[merge Carefully]

    H --> O[Flush at Transaction Boundary]
    M --> O
    N --> O

Senior Checklist

Correct Entity State
        │
        ▼
Correct EntityManager Operation
        │
        ▼
Clear Transaction Boundary
        │
        ▼
Small Persistence Context
        │
        ▼
Controlled Flush Timing
        │
        ▼
No Cross-Thread Sharing
        │
        ▼
Generated SQL Review
        │
        ▼
Failure and Rollback Testing

Senior Follow-up

Good EntityManager usage makes entity state transitions predictable and keeps SQL, memory usage, and transaction behavior under control.


EntityManager Operation Summary

Operation Main Purpose Entity State Result
persist() Make new entity persistent Original entity becomes managed
find() Load by primary key Returns managed entity
getReference() Obtain identity reference Returns managed reference or proxy
merge() Copy detached state Returns managed copy
remove() Schedule deletion Managed entity becomes removed
flush() Synchronize SQL Entities remain managed
clear() Empty persistence context All entities detached
detach() Remove one entity from context Target entity detached
refresh() Reload database state Entity remains managed
contains() Check managed state Returns boolean

Complete Entity Lifecycle

stateDiagram-v2
    [*] --> Transient

    Transient --> Managed:
        persist()

    Transient --> ManagedCopy:
        merge()

    ManagedCopy --> Managed

    Managed --> Detached:
        detach()

    Managed --> Detached:
        clear()

    Managed --> Detached:
        EntityManager closes

    Detached --> ManagedCopy:
        merge()

    Managed --> Removed:
        remove()

    Removed --> Managed:
        persist before flush

    Removed --> [*]:
        flush and commit

persist() vs merge()

persist() merge()
Used for new entities Used for detached or copied state
Original instance becomes managed Original instance remains detached
Returns void Returns managed instance
Usually schedules INSERT May SELECT then INSERT or UPDATE
Cascade PERSIST applies Cascade MERGE applies
Clear intent for creation Higher risk for graph copying

Flow Comparison

flowchart TD
    A[Transient Entity] --> B[persist]
    B --> C[Same Object Managed]
    C --> D[INSERT]

    E[Detached Entity] --> F[merge]
    F --> G[Copy State]
    G --> H[Managed Copy]
    H --> I[UPDATE or INSERT]

find() vs getReference()

find() getReference()
Loads entity state May return proxy
Returns null if absent May fail during initialization
Use when data is needed Use when only ID is needed
Usually immediate SELECT May avoid SELECT
Existence known immediately Existence may be deferred

Use Case

Use find()

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

customer.verifyActive();

Use getReference()

Customer customer =
    entityManager.getReference(
        Customer.class,
        customerId
    );

Order order =
    new Order(customer);

flush() vs Commit

flush() Commit
Synchronizes changes Ends successful transaction
SQL may execute Changes become durable
Rollback still possible Rollback no longer possible afterward
Can happen automatically Explicit or container-managed
May occur multiple times Normally once

Timeline

sequenceDiagram
    participant Service
    participant PersistenceContext
    participant Database
    Service->>PersistenceContext: Modify managed entity
    Service->>PersistenceContext: flush
    PersistenceContext->>Database: UPDATE
    Note over Database: Transaction not committed
    Service->>Service: More business logic
    alt Success
        Service->>Database: COMMIT
    else Failure
        Service->>Database: ROLLBACK
    end

Automatic Flush Triggers

Flush may occur:

  • At transaction commit
  • Before certain JPQL queries
  • When flush() is called
  • According to flush mode
  • Before provider-required database synchronization

Flush Mode

entityManager.setFlushMode(
    FlushModeType.COMMIT
);

Common values:

  • AUTO
  • COMMIT

AUTO

The provider may flush before a query when pending changes could affect the result.

COMMIT

The provider generally delays flushing until commit, although specification and provider rules still apply.

Example

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

customer.deactivate();

List<Customer> activeCustomers =
    entityManager.createQuery(
        """
        select c
        from Customer c
        where c.active = true
        """,
        Customer.class
    )
    .getResultList();

With automatic flush, the update may execute before the query so results are consistent with pending changes.


Persistence Context Identity

Customer first =
    entityManager.find(
        Customer.class,
        101L
    );

Customer second =
    entityManager.find(
        Customer.class,
        101L
    );

assert first == second;

Identity Map Flow

flowchart TD
    A[Request Entity by Type and ID] --> B[Persistence Context Key]
    B --> C[Customer.class + 101]
    C --> D{Instance Present?}
    D -->|Yes| E[Return Same Instance]
    D -->|No| F[Load and Register Instance]

Managed Entity Update

@Transactional
public void activateCustomer(
    Long customerId
) {

    Customer customer =
        entityManager.find(
            Customer.class,
            customerId
        );

    customer.activate();
}

No explicit call to:

entityManager.merge(customer);

is required because the entity is already managed.

Flow

flowchart TD
    A[find] --> B[Managed Entity]
    B --> C[Modify Entity]
    C --> D[Dirty Checking]
    D --> E[UPDATE at Flush]

Merge Identity Risk

Customer detached =
    new Customer();

detached.setId(101L);
detached.setName("New Name");

Customer managed =
    entityManager.merge(detached);

The detached object may contain incomplete state.

Possible overwrite:

email = null
active = false
roles = empty

depending on the object and mapping.

Safer Flow

flowchart TD
    A[Update Request DTO] --> B[Validate Allowed Fields]
    B --> C[find Managed Entity]
    C --> D[Apply Explicit Changes]
    D --> E[Dirty Checking]

Bulk DML and EntityManager

Bulk JPQL bypasses normal managed-entity state synchronization.

Example

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

entityManager.createQuery(
    """
    update Customer c
    set c.active = false
    where c.id = :customerId
    """
)
.setParameter(
    "customerId",
    customerId
)
.executeUpdate();

The database row changes, but customer may still show:

customer.isActive() == true

Stale Context Flow

flowchart TD
    A[Managed Entity active=true] --> B[Bulk JPQL Update]
    B --> C[Database active=false]
    A --> D[Persistence Context Still active=true]
    C --> E[State Mismatch]
    D --> E

Solution

entityManager.clear();

or:

entityManager.refresh(customer);

depending on the use case.


Exception Handling and EntityManager

A persistence exception can mark the transaction rollback-only.

Example

try {
    entityManager.flush();
} catch (
    PersistenceException exception
) {
    // Transaction may now be rollback-only.
    throw exception;
}

Unsafe Pattern

try {
    entityManager.persist(customer);
    entityManager.flush();
} catch (
    PersistenceException exception
) {
    // Ignore and continue using transaction
}

Continuing may produce additional failures.

Failure Flow

flowchart TD
    A[Persistence Operation] --> B[Constraint Violation]
    B --> C[PersistenceException]
    C --> D[Transaction Marked Rollback-Only]
    D --> E{Continue Processing?}
    E -->|Yes| F[Commit Fails]
    E -->|No| G[Rollback]

Batch Processing with Transaction Chunks

For very large imports, one transaction for the entire file may be unsafe.

Chunk Flow

flowchart TD
    A[Read 1000 Rows] --> B[Begin Batch Transaction]
    B --> C[Persist Rows]
    C --> D[flush]
    D --> E[clear]
    E --> F[Commit Batch]
    F --> G[Record Checkpoint]
    G --> H{More Rows?}
    H -->|Yes| A
    H -->|No| I[Complete Job]

Benefits

  • Lower memory usage
  • Shorter transactions
  • Better restartability
  • Reduced lock duration
  • Smaller rollback scope
  • Better operational visibility

Trade-Off

Previously committed chunks cannot be rolled back automatically if a later chunk fails.

For all-or-nothing requirements, consider:

  • Staging tables
  • Job status columns
  • Promotion transaction
  • Compensating cleanup
  • Database partition swap
  • Temporary schema

Transaction-Scoped EntityManager

A transaction-scoped persistence context is commonly used in stateless services.

Flow

sequenceDiagram
    participant Request
    participant Service
    participant EntityManager
    participant Transaction
    Request->>Service: Invoke operation
    Service->>Transaction: Begin
    Transaction->>EntityManager: Create or associate persistence context
    Service->>EntityManager: Perform operations
    Service->>Transaction: Commit
    Transaction->>EntityManager: End context

Entities become detached after the context ends.


Extended Persistence Context

An extended persistence context may remain active across multiple transactions.

@PersistenceContext(
    type =
        PersistenceContextType.EXTENDED
)
private EntityManager entityManager;

Use Cases

  • Stateful conversational workflows
  • Multi-step desktop or server-side UI interactions
  • Long-running editing sessions

Risks

  • Large memory retention
  • Stale data
  • Long-lived managed entities
  • Complex concurrency
  • Unexpected flush behavior
  • Difficult failure recovery

Comparison

Transaction-Scoped Extended
Common default Specialized
Short-lived context Long-lived context
Entities detach after transaction Entities remain managed
Lower memory risk Higher memory risk
Good for request-service model Good for conversational state

Resource-Local EntityManager Example

EntityManager entityManager =
    factory.createEntityManager();

EntityTransaction transaction =
    entityManager.getTransaction();

try {
    transaction.begin();

    Customer customer =
        new Customer(
            "Venu",
            "[email protected]"
        );

    entityManager.persist(customer);

    transaction.commit();

} catch (RuntimeException exception) {

    if (transaction.isActive()) {
        transaction.rollback();
    }

    throw exception;

} finally {
    entityManager.close();
}

JTA EntityManager Example

@ApplicationScoped
public class CustomerService {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public Long createCustomer(
        CreateCustomerRequest request
    ) {

        Customer customer =
            new Customer(
                request.name(),
                request.email()
            );

        entityManager.persist(customer);

        return customer.getId();
    }
}

The container or framework handles transaction lifecycle.


EntityManager Query Creation

Typed JPQL Query

TypedQuery<Customer> query =
    entityManager.createQuery(
        """
        select c
        from Customer c
        where c.active = true
        order by c.name
        """,
        Customer.class
    );

List<Customer> customers =
    query.getResultList();

Named Query

TypedQuery<Customer> query =
    entityManager.createNamedQuery(
        "Customer.findActive",
        Customer.class
    );

Native Query

List<Customer> customers =
    entityManager.createNativeQuery(
        """
        SELECT *
        FROM customers
        WHERE active = true
        """,
        Customer.class
    )
    .getResultList();

Criteria Query

CriteriaBuilder builder =
    entityManager
        .getCriteriaBuilder();

CriteriaQuery<Customer> criteria =
    builder.createQuery(
        Customer.class
    );

EntityManager Locking

Optimistic Lock

Customer customer =
    entityManager.find(
        Customer.class,
        customerId,
        LockModeType.OPTIMISTIC
    );

Pessimistic Lock

Account account =
    entityManager.find(
        Account.class,
        accountId,
        LockModeType.PESSIMISTIC_WRITE
    );

Explicit Lock

entityManager.lock(
    account,
    LockModeType.PESSIMISTIC_WRITE
);

The entity must normally be managed.


EntityManager Utility Methods

Check Open State

boolean open =
    entityManager.isOpen();

Check Managed State

boolean managed =
    entityManager.contains(
        customer
    );

Get Flush Mode

FlushModeType flushMode =
    entityManager.getFlushMode();

Access Factory

EntityManagerFactory factory =
    entityManager
        .getEntityManagerFactory();

Unwrap Provider API

Session session =
    entityManager.unwrap(
        Session.class
    );

Use provider-specific APIs only when justified.


close() Behavior

For application-managed EntityManagers:

entityManager.close();

After closing:

  • Persistence context ends
  • Managed entities become detached
  • Most operations are invalid
  • Resources are released

Warning

Do not manually close a container-managed EntityManager.

The container owns it.


EntityManager Performance Considerations

Persistence Context Size

A large context increases:

  • Heap usage
  • Snapshot memory
  • Dirty-checking cost
  • Flush time
  • Garbage-collection pressure

Query Volume

Repeated find() calls within one context may reuse managed instances.

Across different contexts, database or second-level cache access may occur again.

Write Batching

Batch effectiveness depends on:

  • ID generation strategy
  • JDBC batch size
  • Insert ordering
  • Update ordering
  • Flush size
  • Database driver

Read Models

Use DTO projections when managed entities are unnecessary.

List<CustomerSummary> summaries =
    entityManager.createQuery(
        """
        select new com.codewithvenu.customer.CustomerSummary(
            c.id,
            c.name,
            c.email
        )
        from Customer c
        where c.active = true
        """,
        CustomerSummary.class
    )
    .getResultList();

EntityManager Testing Strategy

Test:

  • persist() assigns identity
  • find() returns managed entity
  • Repeated find() uses same instance
  • getReference() works for relationship assignment
  • merge() returns managed copy
  • Detached changes are not persisted
  • remove() deletes entity
  • flush() exposes database constraint failures
  • clear() detaches entities
  • refresh() restores database state
  • Bulk updates create stale context
  • Optimistic locking failures occur correctly
  • Rollback discards flushed changes

Test Flow

flowchart TD
    A[Arrange Entity] --> B[persist and flush]
    B --> C[clear Context]
    C --> D[Execute Operation]
    D --> E[flush]
    E --> F[Assert Database State]
    F --> G[Assert Entity State]

Example Integration Test

@Test
@Transactional
void shouldDetachCustomerAfterClear() {

    Customer customer =
        new Customer(
            "Venu",
            "[email protected]"
        );

    entityManager.persist(customer);
    entityManager.flush();

    assertThat(
        entityManager.contains(customer)
    ).isTrue();

    entityManager.clear();

    assertThat(
        entityManager.contains(customer)
    ).isFalse();
}

Merge Test

@Test
@Transactional
void mergeShouldReturnManagedCopy() {

    Customer customer =
        new Customer(
            "Venu",
            "[email protected]"
        );

    entityManager.persist(customer);
    entityManager.flush();
    entityManager.detach(customer);

    customer.changeName(
        "Venugopal"
    );

    Customer managed =
        entityManager.merge(customer);

    assertThat(
        entityManager.contains(customer)
    ).isFalse();

    assertThat(
        entityManager.contains(managed)
    ).isTrue();

    assertThat(managed)
        .isNotSameAs(customer);
}

Flush Rollback Example

@Transactional
public void createThenFail() {

    Customer customer =
        new Customer(
            "Venu",
            "[email protected]"
        );

    entityManager.persist(customer);
    entityManager.flush();

    throw new IllegalStateException(
        "Force rollback"
    );
}

Even though the insert was sent during flush, rollback should remove its transactional effect.

Flow

sequenceDiagram
    participant Service
    participant EntityManager
    participant Database
    Service->>EntityManager: persist
    Service->>EntityManager: flush
    EntityManager->>Database: INSERT
    Service->>Service: Throw exception
    Service->>Database: ROLLBACK
    Note over Database: Insert is not committed

Import Service Example

@ApplicationScoped
public class CustomerImportService {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public void importBatch(
        List<CustomerImportRow> rows
    ) {

        int flushSize = 500;

        for (
            int index = 0;
            index < rows.size();
            index++
        ) {

            CustomerImportRow row =
                rows.get(index);

            Customer customer =
                new Customer(
                    row.name(),
                    row.email()
                );

            entityManager.persist(customer);

            if (
                (index + 1)
                    % flushSize == 0
            ) {
                entityManager.flush();
                entityManager.clear();
            }
        }

        entityManager.flush();
        entityManager.clear();
    }
}

Safe Relationship Assignment

@Transactional
public Long createOrder(
    Long customerId,
    CreateOrderRequest request
) {

    Customer customer =
        entityManager.getReference(
            Customer.class,
            customerId
        );

    Order order =
        new Order(customer);

    for (
        CreateOrderItemRequest itemRequest
            : request.items()
    ) {
        order.addItem(
            new OrderItem(
                itemRequest.productCode(),
                itemRequest.quantity()
            )
        );
    }

    entityManager.persist(order);

    return order.getId();
}

Use find() instead if the business operation must verify customer status.


EntityManager Method Decision Matrix

Requirement Recommended Method
Create new entity persist()
Load by ID and inspect data find()
Assign foreign-key reference getReference()
Copy detached state deliberately merge()
Delete managed entity remove()
Force SQL synchronization flush()
Release all managed state clear()
Release one managed entity detach()
Discard local state and reload refresh()
Check managed state contains()
Apply lock lock() or locked find()

Common EntityManager Anti-Patterns

New Factory Per Request

EntityManagerFactory factory =
    Persistence.createEntityManagerFactory(
        "applicationPU"
    );

inside every request is expensive.

Shared Static EntityManager

private static EntityManager
    entityManager;

is unsafe.

Merge Every Managed Entity

Customer customer =
    entityManager.find(
        Customer.class,
        customerId
    );

customer.changeName(
    "New Name"
);

entityManager.merge(customer);

The merge is unnecessary because customer is already managed.

Remove Detached Entity

entityManager.remove(
    detachedCustomer
);

may fail.

Clear Before Flush

entityManager.persist(customer);
entityManager.clear();

The pending entity may no longer be synchronized as intended.

Long Transaction with Huge Context

for (
    int index = 0;
    index < 1_000_000;
    index++
) {
    entityManager.persist(
        createEntity(index)
    );
}

without flush and clear can exhaust memory.


Production EntityManager Architecture

flowchart TD
    A[REST Resource] --> B[Transactional Service]
    B --> C[Repository]
    C --> D[EntityManager Proxy]
    D --> E[Transaction-Scoped Persistence Context]
    E --> F[JPA Provider]
    F --> G[JDBC Connection]
    G --> H[Connection Pool]
    H --> I[Database]

    E --> J[Managed Entities]
    E --> K[Dirty Checking]
    E --> L[Pending SQL Actions]

EntityManager Request Lifecycle

sequenceDiagram
    participant Client
    participant Resource
    participant Service
    participant EntityManager
    participant PersistenceContext
    participant Database
    Client->>Resource: HTTP request
    Resource->>Service: Invoke use case
    Service->>Service: Begin transaction
    Service->>EntityManager: find entity
    EntityManager->>PersistenceContext: Check identity map
    PersistenceContext->>Database: SELECT if needed
    Database-->>PersistenceContext: Row
    PersistenceContext-->>Service: Managed entity
    Service->>Service: Modify entity
    Service->>EntityManager: flush at commit
    EntityManager->>Database: UPDATE
    Service->>Database: COMMIT
    Service-->>Resource: Response DTO
    Resource-->>Client: HTTP response

Senior EntityManager Checklist

Factory

  • One EntityManagerFactory per persistence unit
  • Application-scoped lifecycle
  • Close only during shutdown

EntityManager

  • Container-managed where possible
  • Never manually shared across threads
  • Short-lived persistence context
  • Clear transaction ownership

Entity Operations

  • persist() for creation
  • find() when state is required
  • getReference() for identity relationships
  • Avoid blind merge
  • Remove only managed entities
  • Refresh only when justified

Performance

  • Small persistence context
  • Batch flush and clear
  • Projection queries for reads
  • Generated SQL monitoring
  • Query-count testing

Reliability

  • Flush to detect constraints when needed
  • Understand rollback-only state
  • Test optimistic locking
  • Clear stale state after bulk DML
  • Do not pass managed entities across async boundaries

Interview Quick Revision

Concept Meaning
EntityManager Main JPA persistence-context interface
EntityManagerFactory Creates EntityManagers
persist() Makes new entity managed
find() Loads entity by primary key
getReference() Returns managed reference or proxy
merge() Copies state into managed instance
remove() Marks managed entity for deletion
flush() Synchronizes SQL
clear() Detaches all entities
detach() Detaches one entity
refresh() Reloads database state
contains() Checks managed state
First-Level Cache Persistence-context identity map
Dirty Checking Detects managed changes
Write-Behind Delays SQL until flush

Key Takeaways

  • EntityManager is the primary JPA interface for managing entities, persistence contexts, queries, flushing, and locking.
  • EntityManagerFactory creates EntityManager instances and is expensive to initialize.
  • EntityManagerFactory is usually application-scoped and thread-safe.
  • EntityManager is not thread-safe and should not be manually shared across threads.
  • Container-managed EntityManagers simplify lifecycle and transaction management.
  • persist() makes the original transient entity managed and schedules an insert.
  • find() loads an entity by primary key and checks the persistence context before querying the database.
  • getReference() may return a proxy and is useful when only entity identity is needed.
  • merge() returns a managed copy while the original object remains detached.
  • Blind merge can overwrite protected, stale, or missing state.
  • remove() requires a managed entity and schedules deletion.
  • Delete cascades should follow true aggregate ownership.
  • flush() synchronizes SQL but does not commit the transaction.
  • Flushed changes can still be rolled back.
  • clear() detaches all managed entities and empties the first-level cache.
  • detach() removes one entity from the persistence context.
  • refresh() discards local managed state and reloads current database values.
  • Large batch jobs should periodically flush and clear to control memory.
  • Bulk JPQL updates can leave managed entities stale.
  • Managed entities should not be passed across asynchronous thread boundaries.
  • DTO projections are often better than detached entities for read-only API responses.
  • Senior developers choose EntityManager operations according to entity state, transaction scope, memory usage, and generated SQL behavior.