2

The em.refresh(person) in the following code does not work. Rather than refreshing the person with a fresh value from database, it resets (undo or discards) the changes made in the cache. I am not able to understand why?

em.getTransaction().begin();
        
Person person = em.find(Person.class, 2L); //Person[id=2L, age=23]
person.setAge(24); 
System.out.println(person.getAge()); //it prints 24
        
//Person with id=2 in database gets modified concurrently somehow,
//its age becomes 25 in PERSON table (by an SQL update for example "UPDATE person SET age=25 WHERE id=2")

em.refresh(person); // attempts to load fresh value from database with a SELECT...
System.out.println(person.getAge()); //it prints 23, rather than 25, why?
        
em.getTransaction().commit();
em.close(); 

Could someone help me understand this behavior by the refresh() method of EntityManager?

skip
  • 12,193
  • 32
  • 113
  • 153
  • 1
    May be use flush before refresh? – Alien Sep 24 '21 at 06:46
  • 2
    You are making these tests inside a transaction and transactions are generally with 'repeatable reads'. Unless the database was updated (e.g. doing a flush ) it will return the same value on the 2nd select – Guillaume Sep 24 '21 at 07:09
  • @Alien and @Guillaume : Issuing a `em.flush()` before `em.refresh(person)` will issue an `UPDATE...` updating the `age=24` in the database, so with `em.refresh(person)` I will not get `age=25`, I'll get it `age=24`. So the `em.flush()` before `em.refresh(person)` is not going to help to get the updated data from database. – skip Sep 24 '21 at 08:35
  • @Guillaume `em.refresh(person)` is not giving a repeatable-read, it is doing an undo. It is discarding the changes made in the cache (`age` changed from 23 to 24) and taking the `person` object back to when it was first loaded in the cache (`age=23`). So `em.refresh(person)` is not giving a repeatable read either, it is doing an reset/undo of `person`. – skip Sep 24 '21 at 08:49
  • 1
    @skip Are you sure that changes `UPDATE person SET age=25 WHERE id=2` were committed? By the way, what database do you use? – SternK Sep 24 '21 at 09:22
  • @SternK Yes, the auto-commit mode was on by default. After the update, checked it in database from a different session as well. The `UPDATE person SET age=25 WHERE id=2` indeed had committed the change and updated the age to 25. – skip Sep 24 '21 at 09:25
  • @SternK I used the example with MySQL 8. I've updated the question with a screenshot. – skip Sep 24 '21 at 10:47

1 Answers1

3

If you want to see changes made from other transactions while inside a transaction you need to change the isolation level to READ_COMMITTED

<property name="hibernate.connection.isolation">TRANSACTION_READ_COMMITTED</property>

A few definitions to clarify the discussion:

  • Repeatable read: essentially means that within a transaction the database will see the same value, unless the data was modified within that transaction
  • Hibernate flush: the modifications made in the session (e.g. person.setAge(24);) are not visible by the database until they are flushed. A flush occurs when calling em.flush, committing or, typically, when executing a query such as select * from Person where name=... but not when calling refresh()
  • Hibernate refresh: reading the data from the database and updating the session/1st level cache with that data.

So basically:

  • You're modifying the age by calling setAge() but that change is not flushed, hence not visible by the database
  • You're updating from another session but that change is not visible either because the transactions are isolated (unless using READ_COMMITTED)
  • When refresh is called the database is not aware that setAge() was called and it isolates the update from another transaction, so it shows 23
Guillaume
  • 14,306
  • 3
  • 43
  • 40
  • I understand that, but `em.refresh(person)` is not even giving a repeatable-read, it is just resetting/undoing the changes made in the cache, setting the `age` back to 23, the `age` when the `person` was first loaded into the cache from database. – skip Sep 24 '21 at 09:55
  • I'm not sure what you expect but that's what repeatable read means: you're first reading 23, then you're reading a 2nd time (when you refresh) and it reads 23 again, because transactions are isolated by default. You keep mentioning the cache, do you have 2nd-level cache enabled? – Guillaume Sep 24 '21 at 10:12
  • Repeatable-Read means that data will be read from the cache, and in the cache the `age` has been updated to 24, so repeatable-read means the `age` should been read as 24 not 23. `age` returning a value 23 means that state of `person` after the `em.refresh(person)` has been reset back to its initial state when it was first loaded from database into cache. So the `em.refresh(person)` has basically undone any changes made in the cache and not given a repeatable-read. – skip Sep 24 '21 at 10:19
  • No, I have not enabled second-level cache. It's just the first-level cache. – skip Sep 24 '21 at 10:28
  • Am I not correct about the repeatable-read? Should repeatable-read not have retuned a 24 for `age`? – skip Sep 24 '21 at 10:49
  • 2
    I have updated my answer, repeatable-read is in the context of databases. Refresh is not reading from the 1st-level cache – Guillaume Sep 24 '21 at 12:25
  • Correct, the reason why the `age` wasn't updating to 25 was because the default transaction isolation level of MySQL is REPEATABLE_READ. I changed the isolation level to READ COMMITTED and the same code worked just fine. Thanks. – skip Sep 25 '21 at 11:50