Training, Workshops, Softwareentwicklung

Hibernate Tutorial

First Level Cache

Hibernate verwaltet für jeden EntityManager einen separaten First-Level-Cache. Sehen wir uns das einmal genauer an.

FindCustomerFirstLevelCacheTest
Customer customer = em.find(Customer.class, 1L); (1)
assertNotNull(customer);
assertEquals("Buck", customer.getFirstname());
assertEquals("Rogers", customer.getLastname());

customer.setFirstname("Alice"); (2)

LOG.info("Before 2nd find");
customer = em.find(Customer.class, 1L); (3)
LOG.info("Behind 2nd find");
assertNotNull(customer);
assertEquals("Alice", customer.getFirstname()); (4)
// change is not persisted!
1 Wir laden das Objekt von der Datenbank.
2 Wir ändern das Objekt.
3 Das Objekt wird "anscheinend" neu geladen.
4 Trotzdem sehen wir noch den geänderten Wert, obwohl in der Datenbank tatsächlich "Buck" eingetragen ist.

Um das zu verstehen, hilft ein Blick auf die Ausgaben im Log:

2018-01-27 17:29:14 [main] INFO  n.t.d.l.l.SLF4JQueryLoggingListener - Name:proxy, Time:1, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, Query:["select customer0_.id as id1_0_0_, customer0_.firstname as firstnam2_0_0_, customer0_.lastname as lastname3_0_0_ from Customer customer0_ where customer0_.id=?"], Params:[(1)]
2018-01-27 17:29:14 [main] INFO  d.e.t.h.d.FindCustomerFirstLevelCacheTest - Before 2nd find (1)
2018-01-27 17:29:14 [main] INFO  d.e.t.h.d.FindCustomerFirstLevelCacheTest - Behind 2nd find
1 Der zweite em.find() Aufruf in unserem Test führt keinen Datenbank-Zugriff aus!

Hier seht Ihr das nochmal im Screencast:

Jeder EntityManager merkt sich alle Objekte, die er einmal im Zugriff hatte im sog. First-Level-Cache. Jedes weitere find() wird aus dem Cache bedient.

Starten wir jedoch mit einem neuen EntityManager wie in folgendem Beispiel, so wird das Objekt erneut geladen.

testFindCustomerWithTwoEntityManagers()
doInHibernate(em -> {
    Customer customer = em.find(Customer.class, 1L);
    assertNotNull(customer);
    assertEquals("Buck", customer.getFirstname());
    assertEquals("Rogers", customer.getLastname());
    customer.setFirstname("Alice");
});

doInHibernate(em -> {
    LOG.info("Before 2nd find");
    // new entitymanager will cause a select
    Customer customer = em.find(Customer.class, 1L);
    assertNotNull(customer);
    // alice is not persisted
    assertEquals("Buck", customer.getFirstname());
    assertEquals("Rogers", customer.getLastname());
});
2018-01-27 17:40:43 [main] INFO  n.t.d.l.l.SLF4JQueryLoggingListener - Name:proxy, Time:2, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, Query:["select customer0_.id as id1_0_0_, customer0_.firstname as firstnam2_0_0_, customer0_.lastname as lastname3_0_0_ from Customer customer0_ where customer0_.id=?"], Params:[(1)]
2018-01-27 17:40:43 [main] INFO  d.e.t.h.d.FindCustomerFirstLevelCacheTest - Before 2nd find
2018-01-27 17:40:43 [main] INFO  n.t.d.l.l.SLF4JQueryLoggingListener - Name:proxy, Time:0, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, Query:["select customer0_.id as id1_0_0_, customer0_.firstname as firstnam2_0_0_, customer0_.lastname as lastname3_0_0_ from Customer customer0_ where customer0_.id=?"], Params:[(1)]

Seltsame Effekte sind möglich

Auch wenn ein Objekt bei einem Zugriff aus der Datenbank gelesen wird, kann im First-Level-Cache ein geänderter Zustand für Überraschungen sorgen.
testFindCustomerFirstLevelCacheEffectOnQueries()
doInHibernate(em -> {


    TypedQuery<Customer> query =
            em.createQuery("SELECT c FROM Customer c", Customer.class); (1)
    Customer customer = query.getSingleResult();

    assertNotNull(customer);
    assertEquals("Buck", customer.getFirstname());
    assertEquals("Rogers", customer.getLastname());

    customer.setFirstname("Alice");

    LOG.info("Before 2nd find");
    customer = em.find(Customer.class, 1L); (2)
    LOG.info("Behind 2nd find");
    assertNotNull(customer);
    assertEquals("Alice", customer.getFirstname());

    query = em.createQuery("SELECT c FROM Customer c", Customer.class); (3)
    LOG.info("Before 2nd query");
    customer = query.getSingleResult();
    assertNotNull(customer);

    // although we do a select which reads Buck, the value is still Alice!!
    assertEquals("Alice", customer.getFirstname()); (4)

});

doInHibernate(em -> {

    // check that nothing got persisted with a new entitymanager
    TypedQuery<Customer> query =
            em.createQuery("SELECT c FROM Customer c", Customer.class);
    Customer customer = query.getSingleResult();

    assertNotNull(customer);
    assertEquals("Buck", customer.getFirstname()); (5)
    assertEquals("Rogers", customer.getLastname());
});
1 Wir lesen "alle" Customer (ist ja nur einer).
2 find() wird aus dem First-Level-Cache bedient.
3 Wir lesen erneut "alle" Customer. Hier wird tatsächlich ein Select ausgeführt.
4 Obwohl wir neu gelesen haben, sehen wir noch den geänderten Wert im Customer
5 Erst mit einem neuen EntityManager erhalten wir wieder den Wert, der in der Datenbank steht.