How do you test Spring @Transactional without just hitting hibernate level 1 cache or doing manual session flush? How do you test Spring @Transactional without just hitting hibernate level 1 cache or doing manual session flush? spring spring

How do you test Spring @Transactional without just hitting hibernate level 1 cache or doing manual session flush?


Here's what I was running into. Consider this code in a test method:

    String testDisplayNameChange = "ThisIsATest";    User user = userService.readUser(new Long(77));    user.setDisplayName(testDisplayNameChange);    user = userService.readUser(new Long(77));    assertNotEquals(user.getDisplayName(), testDisplayNameChange);

Note that the method userService.readUser is marked @Transactional in the service class.

If that test method is marked @Transactional the test fails. If it is NOT, it succeeds. Now I'm not sure if/when the Hibernate cache is actually getting involved. If the test method is transactional then each read happens in one transaction, and I believe they only hit the Hibernate level 1 cache (and don't actually read from the database). However, if the test method is NOT transactional, then each read happens in it's own transaction, and each does hit the database. Thus, the hibernate level 1 cache is tied to the session / transaction management.

Take aways:

  1. Even if a test method is calling multiple transactional methods in another class, if that test method itself is transactional, all of those calls happen in one transaction. The test method is the "unit of work". However, if the test method is NOT transactional, then each call to a transactional method within that test executes within it's own transaction.

  2. My test class was marked @Transactional, therefore every method will be transactional unless marked with an overriding annotation such as @AfterTransaction. I could just as easily NOT mark the class @Transactional and mark each method @Transactional

  3. Hibernate level 1 cache seems tied to the transaction when using Spring @Transactional. I.e. subsequent reads of an object within the same transaction will hit the hibernate level 1 cache and not the database. Note there is a level 2 cache and other mechanisms that you can tweak.

I was going to have a @Transactional test method then use @AfterTransaction on another method in the test class and submit raw SQL to evaluate values in the database. This would circumvent the ORM and hibernate level 1 cache altogether, insuring you were comparing actual values in the DB.

The simple answer was to just take @Transactional off of my test class. Yay.


Q

Can anyone confirm if this is expected behavior? Shouldn't @Transaction have forced commit and flush on session? If not, then how would one test a method marked @Transactional and that the methods actually work with transactions?

A

It is the expected behavior. The spring aware transactional unit test support by design rollsback the transaction after the test is finished. This is by design.

An implicit transaction boundary is created per test (each method with @Test) and once the test is finished a rollback is done.

The consequence of this is after all tests are finished there is no data that is actually changed. That is the goal is to be more "unit" like and less "integration" like. You should read the spring documentation as to why this is advantageous.

If you really want to test data being persisted and to view that data outside of the transaction boundary I recommend a more end-to-end test like a functional/integration test such as selenium or hitting your external WS/REST API if you have one.

Q

I.e., how should I rewrite my Unit test here?

A

Your unit test does not work because you session.evict and not flush. Evicting will cause the changes not to be synchronized even though you already called session.update your in a transaction and hibernate batches operations. This is more clear with raw SQL as hibernate defers persisting to batch up all the operations so it waits until the session is flushed or closed to communicate with the database for performance. If you session.flush though the SQL will be executed right away (ie update will really happen) and then you can evict if you like to force a reread but your rereading within the transaction. I'm actually pretty sure flush will cause eviction so there is no need to call evict to force a reread but I might be wrong.