Session Lifecycle

SessionFactory and Session

SessionFactory (JPA: EntityManagerFactory) is built once at startup. It is heavy: it parses all mappings, prepares SQL for every entity and holds the L2 cache. It is thread-safe and shared.

Session (JPA: EntityManager) is light and not thread-safe. It holds the persistence context, an action queue of pending SQL, and a JDBC connection while it needs one. In Spring, the injected EntityManager is a proxy that finds the Session bound to the current transaction.

One Request, Step by Step

@Transactional method called
   │  Spring: JpaTransactionManager opens a Session, begins a transaction,
   │          binds Session to the current thread
   ▼
 find / query      → persistence context fills up (entities + snapshots)
 setters           → nothing happens yet (just Java fields)
 persist / remove  → actions added to the ACTION QUEUE
   ▼
 method returns
   │  Spring: commit → Session.flush()
   │            1. dirty checking: compare entities with snapshots → UpdateActions
   │            2. action queue executes in order:
   │               inserts → updates → collection changes → deletes
   │          → JDBC commit → Session closed, entities become DETACHED
   ▼
 response serialised (lazy fields now throw unless OSIV is on)

Action Queue Order

Hibernate does not run SQL in the order you called methods. At flush it runs: orphan removals → inserts → updates → collection element removals → collection inserts → deletes. This order avoids most foreign-key problems, but it surprises people:

@Transactional
public void replaceSku(Long oldId) {
    Product old = em.find(Product.class, oldId);
    em.remove(old);                       // queued as a DELETE
    Product fresh = new Product();
    fresh.setSku(old.getSku());           // same unique SKU
    em.persist(fresh);                    // queued as an INSERT
}   // flush: INSERT runs before DELETE -> unique constraint violation on sku!

// Fix: force the delete out first
em.remove(old);
em.flush();                               // DELETE executed now
em.persist(fresh);