Spring Boot v2.6.4
@Transactional
tests are useful to roll back any test data created during tests; however, when a Spring integration test is annotated with @Transactional
the following JPA lifecycle callback methods are not executed:
@PostPersist
@PostLoad
@PreUpdate
@PostUpdate
Example project with tests demonstrating the issue here:
https://github.com/hughwphamill/transaction-test-entity-issue
I understand this is the desired behavior from a JPA perspective, but it's still undesirable from a test perspective.
@Transactional
is incredibly useful in Spring integration tests to roll back test data, while exercising your whole application end to end. If your business logic uses JPA entity listeners then your application can't be properly tested in a @Transactional
test.
Comment From: sbrannen
Hi @hughwphamill,
Thanks for opening your first issue for the Spring Framework. 👍🏻
If your business logic uses JPA entity listeners then your application can't be properly tested in a
@Transactional
test.
You can still test the behavior of your JPA lifecycle callback methods with @Transactional
test methods, but you need to take into account the semantics of the unit of work (first level cache) in your ORM tool (i.e., the "persistence context" in JPA terminology).
This usually means that you need to flush the state of the unit of work to the underlying database.
In the Avoid false positives when testing ORM code note in the Testing chapter of the reference manual, we point out that invoking entityManager.flush()
will help you to achieve this with JPA.
For your particular test class, you can make the @PostPersist
, @PreUpdate
, and @PostUpdate
tests pass by invoking entityManager.flush()
after each invocation of repository.save(entity)
. To make the @PostLoad
test pass, you additionally need to invoke entityManager.clear()
after calling entityManager.flush()
.
In summary, the behavior you have encountered is to be expected since all interactions with the unit of work occur within the same transaction.
To ensure that your JPA lifecycle callback methods are invoked, you have two choices.
- Flush and clear the unit of work as described above within a
@Transactional
test. - Do not use
@Transactional
tests.
I will add a note to the reference manual to point out the need to flush
and clear
when using JPA lifecycle callback methods, in case other developers encounter this issue.
Comment From: hughwphamill
@sbrannen Thanks very much for the detailed explanation and for updating the docs!
Comment From: sbrannen
@hughwphamill, you're welcome!
And thanks for providing feedback.