skip to content

Transactional Tests & Rollback

@Transactional on a test rolls it back afterwards so tests do not pollute each other, with overrides and hooks for the cases that need a real commit. Interviewers point out that this also hides commit-time failures, and want you to know it.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

6

What happens to the database when you annotate a Spring integration test with @Transactional, and why?

level: juniorimportance: must knowfreq 70%

answer

  1. Rolls back by default, pass or fail
  2. TransactionalTestExecutionListener does it
  3. @DataJpaTest transactional; @SpringBootTest not
  4. Isolation + no teardown
  5. Rollback != exception-driven

basics

~20 s

Spring starts a transaction before the test method and automatically rolls it back afterward, even if the test passes. So any data written during the test is undone, keeping the database clean and each test isolated.

solid answer

~40 s

Putting org.springframework.transaction.annotation.@Transactional on a test class or method makes Spring's TestContext framework wrap each test method in a transaction that is rolled back by default when the method finishes — pass or fail. This is done by the TransactionalTestExecutionListener. The point is isolation and cleanup: writes made during the test never commit, so tests don't leak state into one another and you avoid manual teardown. Note it's the *test's* @Transactional (from spring-tx), not the web @Transactional on your service — though it's the same annotation. Slices like @DataJpaTest are meta-annotated with it, so they roll back automatically; @SpringBootTest is not transactional unless you add it. The rollback is intentional and independent of whether an exception was thrown.

code

java · 25 lines
java
@DataJpaTest // meta-annotated with @Transactional -> rolls back automatically
class UserRepositoryTest {

    @Autowired UserRepository users;

    @Test
    void savesUser() {
        users.save(new User("[email protected]"));
        assertThat(users.count()).isEqualTo(1);
        // No @AfterEach cleanup: the transaction is rolled back after this method,
        // so the next test starts with an empty table.
    }
}

@SpringBootTest
@Transactional // NOT transactional by default -> add this to get rollback
class OrderServiceIntegrationTest {
    @Autowired OrderService service;

    @Test
    void placingOrderWritesRow() {
        service.place(new Order(...));
        // rolled back at end of method
    }
}

go deeper

for a junior

Must know: @Transactional test = automatic rollback, keeps DB clean, no manual cleanup.

for a middle

Should know rollback is default regardless of pass/fail, and which slices are transactional vs @SpringBootTest.

for a senior

Explains the listener, single-transaction-per-method semantics, and the disambiguation of multiple tx managers.

for a principal

Frames rollback tests as an isolation strategy with trade-offs (cache masking, no real commit) and decides when NOT to use them.

## The mechanism When you place `@Transactional` (from `org.springframework.transaction.annotation`) on a test class or test method, the Spring TestContext Framework's **`TransactionalTestExecutionListener`** does the following around each test method: 1. Before the method: starts a new transaction using the configured `PlatformTransactionManager`. 2. Runs the test method inside that transaction. 3. After the method: **rolls the transaction back by default** — regardless of whether the test passed or threw. So every INSERT/UPDATE/DELETE performed during the test is undone. The database is left exactly as it was before the test ran. ## Why this is the default - **Isolation** — tests don't see each other's writes; order-independence. - **No teardown code** — you don't have to write `@AfterEach` cleanup to delete rows. - **Speed** — rollback is generally cheaper than commit + cleanup. ## Key clarifications - The rollback is **not** triggered by an exception. A perfectly green test still rolls back. This surprises beginners who expect a passing test to persist data. - It is the **same annotation** you use on services (`@Transactional`), but here it is interpreted by the test framework, not by the AOP proxy around a bean. - **Test slices**: `@DataJpaTest`, `@DataJdbcTest`, `@JdbcTest` are meta-annotated with `@Transactional`, so they roll back automatically. `@SpringBootTest` is **not** transactional — if you want rollback there, add `@Transactional` yourself. - Only one transaction manager is used. If the context has more than one `PlatformTransactionManager`, you must disambiguate (e.g. `@Transactional("txManagerBeanName")` or a `TransactionManagementConfigurer`). ## Gotcha to be aware of Because the whole test runs in **one** transaction, a `save` followed by a `find` may be served from the JPA first-level cache without a real SQL round-trip, and constraint violations may not surface until a flush. This can mask bugs — covered in more advanced questions on this topic. ## When to override Use `@Commit` / `@Rollback(false)` when you deliberately want the data to persist (rare — e.g. debugging, or a multi-step workflow across transactions).

  • Does the transaction roll back only when the test fails?
    No. The default is to roll back on every transactional test method, whether it passes or throws. Rollback is not tied to test success or to exceptions.
  • Is @SpringBootTest transactional out of the box?
    No. Full-context @SpringBootTest is not transactional; you must add @Transactional to get automatic rollback. Slice tests like @DataJpaTest already include it.

saying these in an interview costs you the question

  • Claiming the transaction only rolls back when the test throws an exception
  • Thinking data written in a @DataJpaTest is permanently persisted
  • Assuming @SpringBootTest rolls back automatically like @DataJpaTest

context

open as a page

How do you make a transactional test commit its changes instead of rolling back, and when would you?

level: middleimportance: should knowfreq 40%

basics

~20 s

Add @Commit (or equivalently @Rollback(false)) on the test method or class. Then Spring commits the transaction instead of rolling it back. Useful rarely — e.g. inspecting persisted data or seeding across tests — but it breaks isolation, so you must clean up yourself.

open as a page

What are @BeforeTransaction and @AfterTransaction for, and how do they order relative to @BeforeEach in a transactional test?

level: seniorimportance: should knowfreq 22%

basics

~20 s

@BeforeTransaction runs before the test's managed transaction starts; @AfterTransaction runs after it ends (commit or rollback). They let you set up or verify data outside the transaction — for example seeding rows that must survive rollback, or asserting the final committed state.

open as a page

How do you programmatically control commit/rollback and cross multiple transaction boundaries within a single transactional test method?

level: seniorimportance: should knowfreq 18%

basics

~20 s

Use the static TestTransaction helper. Inside a @Transactional test you can call TestTransaction.flagForCommit() or flagForRollback(), then TestTransaction.end() to finish the current transaction, and TestTransaction.start() to begin a fresh one — letting one test span several transactions.

open as a page

Why can @Transactional rollback-per-test integration tests give false confidence, and how do you mitigate it?

level: principalimportance: should knowfreq 30%

basics

~20 s

Because the whole test runs in one never-committed transaction, save-then-find can be served from the JPA first-level cache without real SQL, flush/constraint errors may never surface, and lazy loading always works. Mitigate by flushing and clearing the persistence context, and testing commit paths.

open as a page

What is TransactionalTestExecutionListener, and how does it decide which transaction manager to use and whether to commit or roll back?

level: seniorimportance: nice to knowfreq 12%

basics

~20 s

It's one of Spring's built-in TestExecutionListeners that provides transactional test behavior: it starts a transaction before a @Transactional test method and rolls it back (or commits) after. It picks the PlatformTransactionManager from the context and reads @Rollback/@Commit to decide the outcome.

open as a page