SpringJTA-JPA-Hibernate事务传播对数据库更新的影响及疑问
Great question! Let's break down exactly why you're seeing these two distinct behaviors—database updates when your test has no @Transactional, but no database changes when it does—by looking at how Spring manages transactions, EntityManager lifecycle, and test framework defaults.
Why Database Updates Happen When the Test Method Has No @Transactional
Your categoryService.update() method is annotated with @Transactional (default propagation level: REQUIRED). Here's what plays out when your test method doesn't have its own transaction:
- When you call
categoryService.find()first: Since this method has no@Transactionalannotation, Spring creates a temporaryEntityManager, runs the query, then closes the EntityManager immediately. TherootAChild1object you get back is detached (no longer linked to an active EntityManager/transaction). - After modifying the detached object's name, you call
categoryService.update(): Spring detects there's no active transaction, so it creates a new one and binds a fresh EntityManager to it. Theem.merge(category)call takes your detached object, copies its state into a new managed instance in the current EntityManager's context, and marks it for persistence. - When
update()finishes executing, Spring automatically commits this new transaction. Hibernate flushes the managed object's changes to the database, so you see the update in your database. - The subsequent
categoryService.find()call creates another temporary EntityManager, pulls the fresh updated data from the database, and your assertions pass.
Why No Database Updates Happen When the Test Method Has @Transactional
This boils down to Spring Test's default transaction rollback behavior:
Any test method annotated with @Transactional will automatically have its transaction rolled back when the test finishes. This is a built-in feature to prevent test data from polluting your database across test runs.
Let's break down each propagation level you tested:
- REQUIRED: Your
categoryService.update()joins the existing test method transaction. All changes (including themergecall) are tracked in the test's EntityManager and its first-level cache. But when the test ends, the entire transaction is rolled back—so none of those changes make it to the database. The first-level cache still has the modified object, which is why yourfind()call returns the updated name. - REQUIRES_NEW: Even though this propagation level creates a separate transaction context, if you've set it on the test method itself, it's still the test's transaction that gets rolled back. Only if you applied
REQUIRES_NEWto theupdate()method would that sub-transaction commit independently. Since your test method owns the transaction, all operations within it are rolled back. - NESTED: Nested transactions are tightly tied to the parent test transaction. When the parent rolls back, all nested transactions roll back too—so no changes persist to the database. The first-level cache retains the modified object during the test execution, hence your local assertions pass.
If you want to see database updates when using @Transactional in your test, add the @Rollback(false) annotation to your test method. This disables the automatic rollback and lets the transaction commit normally.
内容的提问来源于stack exchange,提问作者Geek

