Question 8 · Section 16

What Are the Transient, Persistent, Detached, Removed States

Four terms describe the status of a Java object's interaction with the ORM mechanism (Hibernate/JPA) at a given point in time. Correct understanding of these states is critical...

Language versions: English Russian Ukrainian

Overview

Four terms describe the status of a Java object’s interaction with the ORM mechanism (Hibernate/JPA) at a given point in time. Correct understanding of these states is critical for preventing memory leaks, incorrect data updates, and runtime errors.


Junior Level

4 Entity States

State In DB? Tracked by Hibernate?
Transient No No
Persistent Yes Yes
Detached Yes No
Removed Yes (marked for deletion) Yes (until flush)

Example of Each State

// 1. Transient - new object, not in DB
User user = new User();
user.setName("John");
// user NOT in DB, Hibernate NOT tracking

// 2. Persistent - saved and managed by Hibernate
entityManager.persist(user);
// user is in DB (INSERT executed or scheduled)
// Hibernate tracks changes (dirty checking)

// 3. Detached - record in DB exists, but Hibernate not tracking
entityManager.close();
// user still exists in memory
// user still exists in DB
// But Hibernate no longer tracks changes

// 4. Removed - marked for deletion
User user2 = entityManager.find(User.class, 1L);
entityManager.remove(user2);
// user2 marked for deletion
// On flush/commit, DELETE will be executed

How to Check State

// Check if entity is managed
boolean isManaged = entityManager.contains(user);

// Check by ID
// This works with GenerationType.AUTO/IDENTITY.
// With Assigned generation, ID may be set manually.
if (user.getId() == null) {
    // likely Transient
} else if (entityManager.contains(user)) {
    // Persistent
} else {
    // Detached (has ID, but not managed)
}

Middle Level

When NOT to Use Dirty Checking

Dirty checking is convenient for single updates. For bulk processing (1000+ entities) overhead grows - use native SQL queries or StatelessSession.

State Transition Operations

persist()  - Transient -> Persistent
merge()    - Detached -> Persistent (returns new managed copy)
remove()   - Persistent -> Removed
refresh()  - reload from DB (updates Persistent)
detach()   - Persistent -> Detached (evict() in Hibernate)
clear()    - all Persistent -> Detached

Detailed Behavior of Each Operation

persist()

User user = new User();
user.setName("John");
entityManager.persist(user);

// user became Persistent
// INSERT will be executed on flush/commit
user.setName("Jane");  // dirty checking - will be UPDATE

merge()

// merge - the most difficult operation to understand
User detached = getDetachedUser();  // obtained detached object somewhere

User managed = entityManager.merge(detached);

// What happens inside:
// 1. Hibernate looks for entity with same ID in current session
// 2. If not found - loads from DB
// 3. Copies state from detached to managed
// 4. Returns managed object
// 5. detached remains detached!

detached.setName("Changed");     // will NOT affect DB
managed.setName("Changed");      // will affect (dirty checking)

SELECT is needed so Hibernate can check: (1) does the record exist in DB, (2) load current state for dirty checking. Without SELECT, Hibernate doesn’t know what to update.

detach() / evict()

User user = entityManager.find(User.class, 1L);  // Persistent
entityManager.detach(user);  // user -> Detached
// user.getId() still = 1
// But changes no longer tracked

// Hibernate-specific analog:
Session session = entityManager.unwrap(Session.class);
session.evict(user);

refresh()

User user = entityManager.find(User.class, 1L);
// Another transaction changed user in DB
entityManager.refresh(user);  // reload from DB
// Now user has actual values

Common Mistakes

// Detached entity passed to persist
User user = new User();
user.setId(1L);  // ID set manually
entityManager.persist(user);  // EntityExistsException

// Correct
User merged = entityManager.merge(user);

// Ignoring merge() return value
entityManager.merge(detached);  // result lost!
detached.setName("Changed");    // NOT saved

// Correct
User managed = entityManager.merge(detached);
managed.setName("Changed");  // will be saved

// merge() for persistent
User user = entityManager.find(User.class, 1L);  // already persistent
entityManager.merge(user);  // unnecessary SELECT!

Senior Level

Internal Implementation

Persistence Context Structure:

EntityManagerImpl {
    persistenceContext: StatefulPersistenceContext {
        entitiesByKey: Map<EntityKey, Object>,
        entityEntries: Map<Object, EntityEntry>,
    }
}

EntityEntry stores:
- loadedState - snapshot at load time
- status - MANAGED, DELETED, READ_ONLY, etc.
- id - identifier
- version - for optimistic locking
- lazyPropertiesAreUninitialized - lazy loading flag

Identity Guarantee

Within a single session, Hibernate guarantees:
- For one ID in DB, there is exactly one object in memory
- entityManager.find(User.class, 1L) always returns the same object

User user1 = entityManager.find(User.class, 1L);
User user2 = entityManager.find(User.class, 1L);
assert user1 == user2;  // true (identity)

This is important for:
- dirty checking (one object tracked once)
- data consistency within a transaction

Senior Insight: Merge Magic

When you call em.merge(detachedObject):

1. Check: is there an entity with this ID in the persistence context?
   -> Yes: copy state from detached to managed
   -> No: load from DB (SELECT)

2. If not in DB - create new managed (INSERT on flush)

3. Copy all fields from detachedObject to managed

4. Return managed object

5. detachedObject remains detached!
   All further actions - with returned object!

Important: merge() may cause SELECT before INSERT/UPDATE

Optimization for Large Data

@Transactional
public void batchImport(List<User> users) {
    int batchSize = 50;
    for (int i = 0; i < users.size(); i++) {
        entityManager.persist(users.get(i));

        if (i % batchSize == 0 && i > 0) {
            entityManager.flush();    // flush to DB
            entityManager.clear();    // clear persistence context
        }
    }
}

Best Practices

Understand entity states
persist() for new
merge() for detached (use return value!)
Avoid long-term storage of detached objects
Use entityManager.contains() for state check
clear() for large batch operations

persist() with set ID
merge() for persistent (unnecessary SELECT)
Ignoring merge() return value
Accessing lazy fields on detached objects
Passing detached objects between threads

Comparison Table

State In DB? In Persistence Context? Dirty Checking?
Transient No No No
Persistent Yes Yes Yes
Detached Yes No No
Removed Still yes Yes (until flush) No

Interview Cheat Sheet

Must know:

  • Transient: new object, not in DB, Hibernate not tracking
  • Persistent: in DB, in persistence context, dirty checking works
  • Detached: in DB, but NOT in persistence context, changes not tracked
  • Removed: marked for deletion, DELETE on flush
  • persist() - Transient -> Persistent, merge() - Detached -> Persistent (returns a copy!)
  • entityManager.contains() checks if entity is managed
  • Identity guarantee: in one session, one ID = one object in memory

Frequent follow-up questions:

  • How to check state? ID null -> Transient, contains() true -> Persistent, contains() false + ID exists -> Detached
  • Why does merge() return a new object? Original detached remains detached, Hibernate creates managed copy
  • What is Identity Guarantee? Within one session, Hibernate guarantees one ID = one object in memory
  • When is detach() needed? To free memory, during batch operations, for large data processing

Red flags (DO NOT say):

  • “persist() for object with set ID” - EntityExistsException
  • “merge() for persistent - it’s fine” - unnecessary SELECT
  • “Detached objects can be modified directly” - need merge()
  • “I pass detached objects between threads” - not thread-safe

Related topics:

  • [[7. Describe the Entity Lifecycle in Hibernate]]
  • [[14. What is the Difference Between persist() and merge()]]
  • [[12. What is Dirty Checking in Hibernate]]
  • [[15. What Does the refresh() Method Do]]