Spring Boot / Editorial solution guide
Diagnose missing transaction or rollback behavior
Trace the real managed call and transaction owner before changing propagation, swallowing errors, or assuming a repository call committed the intended unit.
Read-only knowledge base ยท No public comments
Recognize the symptoms
An annotated method appears nontransactional, an expected failure leaves partial changes, or completion raises UnexpectedRollbackException. A direct unit test can pass while the managed runtime behaves differently.
SQL can be deferred until flush/commit, so the source line that changed an entity is not necessarily where persistence failure is reported. Preserve the causal chain and completion outcome.
Understand the likely causes
- The call may bypass a proxy through self-invocation or manual construction, use an unintended manager, or participate in an outer transaction with a different policy.
- A checked exception may lack the required rollback rule, a caught failure may leave rollback-only state, or effects may span resources that one local transaction cannot make atomic.
Draw the actual caller and managed boundary
Record the pinned versions, manager, datasource identity, method signature, proxy mode, caller path, propagation, and earliest failure. Do not print credentials or connect to a configured datasource to experiment. A same-instance call is not the external proxy entry in usual proxy mode, and new creates an ordinary object. Verify the object and route used by the failing application rather than only inspecting the annotation in source.
Classify rollback policy and completion state
Under usual defaults, unchecked failures trigger rollback while checked failures need a matching configured policy. A participating inner failure can mark rollback-only even if an outer method catches it. Read-only is not a universal write prohibition, and save/flush/commit are different events. Determine whether the observed changes were in the same transaction and resource before blaming the annotation mechanism.
State a reviewed service boundary and checked-failure rule
The fragment places the operation on a managed collaborator and declares a checked business rejection as rollback-relevant. StockStore and OrderStore are contextual capabilities, not automatically generated repository methods; their implementations must share the intended transactional resource. The code is unexecuted and does not provision storage. It illustrates a boundary to test, not proof that every effect or external notification rolls back.
package training.catalog;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
class OrderWriteBoundary {
private final StockStore stock;
private final OrderStore orders;
OrderWriteBoundary(StockStore stock, OrderStore orders) {
this.stock=stock; this.orders=orders;
}
@Transactional(rollbackFor=ReservationRejected.class)
public void place(TrustedOrderCommand command) throws ReservationRejected {
stock.reserve(command);
orders.store(command);
}
}
The custom checked exception and stores require the application context and an actual shared transactional implementation. No rollback was executed.
Repair ownership and verify the persisted result
Use the managed collaborator or a reviewed programmatic transaction design, configure the intended rollback rule, and avoid normal-success returns after an unrecovered failure. Do not use REQUIRES_NEW merely to suppress rollback-only symptoms: it changes atomicity and can commit earlier work independently. External delivery needs its own outbox/idempotency policy. Keep the original error visible in restricted diagnostics and return a truthful application outcome.
Verify the fix
In an authorized isolated database test, call through the real managed bean, inject a failure after the first related write, and inspect final stored state after completion. Test checked/unchecked failures, participation, commit-time rejection, and cleanup. A mocked repository, plain Java model, or transactional test wrapper alone does not prove the production call path rolled back.
Keep it from recurring
- Document the business atomic unit, supported manager/resource, proxy entry, rollback rules, and replay policy. Keep scoped integration regressions and separate HTTP-server-thread transactions from the test method's transaction. Never use a configured project database as an accidental rollback experiment.