The Persistence Context

What It Is

Every EntityManager owns a persistence context: a map from (entity type, id) to the one Java object that represents that row. Hibernate also calls it the first-level cache. While an entity is in this map it is managed: Hibernate watches it and saves its changes.

EntityManager
   +-- persistence context
         (Product, 1)  -> Product@A  { name=Boat Rockerz 450, stock=120 }
         (Product, 3)  -> Product@B  { name=Noise ColorFit Pro, stock=40 }
         (User, 7)     -> User@C     { [email protected] }
         + a snapshot of each one's loaded values (for dirty checking)

Two Guarantees

  • Repeatable identity: inside one persistence context, em.find(Product.class, 1L) twice returns the same object (== is true).
  • One SELECT per row: the second find is answered from the map, without SQL.
  • The context lives as long as the EntityManager — in Spring Boot that is normally one transaction. It is not a global cache shared between users.

Identity Map, Simulated

Example

A miniature persistence context: find checks the map first and only "queries the database" on a miss.

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<Long, String> productsTable = new TreeMap<>(Map.of(
            1L, "Boat Rockerz 450", 3L, "Noise ColorFit Pro"));

        MiniEntityManager em = new MiniEntityManager(productsTable);
        Product a = em.find(1L);
        Product b = em.find(1L);           // same id again
        Product c = em.find(3L);
        System.out.println("a == b ? " + (a == b));
        System.out.println("a == c ? " + (a == c));
        System.out.println("SQL queries run: " + em.queries);

        em.clear();                         // new transaction / context
        Product d = em.find(1L);
        System.out.println("after clear, a == d ? " + (a == d));
        System.out.println("SQL queries run: " + em.queries);
    }
}

class Product {
    final Long id; String name;
    Product(Long id, String name) { this.id = id; this.name = name; }
}

class MiniEntityManager {
    private final Map<Long, String> table;
    private final Map<Long, Product> context = new LinkedHashMap<>();
    int queries = 0;
    MiniEntityManager(Map<Long, String> table) { this.table = table; }

    Product find(Long id) {
        Product cached = context.get(id);
        if (cached != null) { System.out.println("find(" + id + "): from persistence context"); return cached; }
        queries++;
        System.out.println("find(" + id + "): select ... from products where id=" + id);
        String name = table.get(id);
        if (name == null) return null;
        Product p = new Product(id, name);
        context.put(id, p);
        return p;
    }
    void clear() { context.clear(); }
}

Output

find(1): select ... from products where id=1
find(1): from persistence context
find(3): select ... from products where id=3
a == b ? true
a == c ? false
SQL queries run: 2
find(1): select ... from products where id=1
after clear, a == d ? false
SQL queries run: 3