How would you reason about ordering a custom auditing or security aspect relative to Spring's own transaction management on the same service method?
answer
- outside vs inside the transaction
- tx advice default = LOWEST_PRECEDENCE / MAX_VALUE
- @EnableTransactionManagement(order=...)
- REQUIRES_NEW to survive rollback
- self-invocation defeats proxies
basics
~20 sDecide which concern must bracket the others and give it the lowest order value so it's outermost. Spring's transaction advice has its own configurable order (default lowest precedence / Integer.MAX_VALUE), so an aspect with a lower value runs outside the transaction; a higher value runs inside it.
solid answer
~50 sOrdering is a design decision about bracketing. Ask: does my aspect need to run outside or inside the transaction? Spring's transaction interceptor is itself an ordered aspect; by default @EnableTransactionManagement / @Transactional infrastructure sits at Ordered.LOWEST_PRECEDENCE (Integer.MAX_VALUE), and you can change it via the 'order' attribute of @EnableTransactionManagement or on the advisor. To run security/auth checks before a transaction opens, give the security aspect a lower value than the transaction's. To have an audit record participate in the same transaction (rolled back on failure), place it inside — a higher value than the transaction advice, so it runs after begin and before commit. I make these values explicit and centralised, document the intended nesting, and avoid Integer boundaries that collide with framework defaults. I also prefer distinct, spaced values (10, 20, 30) to leave room.
code
java · 15 lines@Configuration
@EnableTransactionManagement(order = 100) // tx advice sits at order 100
class TxConfig {}
// Security OUTSIDE the transaction (rejects before begin):
@Aspect @Component @Order(10)
class SecurityAspect {
@Before("execution(* svc.*(..))") void authorize() { /* throws before tx opens */ }
}
// Audit INSIDE the transaction (rolled back with the business change):
@Aspect @Component @Order(200) // higher than 100 -> inner -> within tx
class AuditAspect {
@AfterReturning("execution(* svc.*(..))") void record() { /* same tx */ }
}go deeper
Out of depth; can state lower value = outermost.
Can place security outside vs audit inside conceptually.
Knows the transaction advisor is ordered and configurable, and inside/outside implications for rollback.
Designs the whole onion as a documented contract, accounts for REQUIRES_NEW, self-invocation, and framework defaults.
## Ordering as a bracketing contract For cross-cutting concerns, the real question is **nesting**: which concern must fully enclose which? Because lowest `@Order` value = outermost, you choose values to express the containment you need. ## Spring's transaction advice is itself ordered Spring's declarative transactions (`@Transactional` via `@EnableTransactionManagement` or `<tx:annotation-driven>`) are implemented as an ordered AOP advisor. **By default it runs at `Ordered.LOWEST_PRECEDENCE` (`Integer.MAX_VALUE`)** — i.e. innermost, closest to the target — so most custom aspects naturally sit *outside* the transaction unless you say otherwise. You can move it with the **`order` attribute**: `@EnableTransactionManagement(order = ...)` (and the equivalent on `<tx:annotation-driven order=...>`). The same applies to caching (`@EnableCaching(order=...)`) and async infrastructure. ## Deciding inside vs outside the transaction - **Security / authentication / authorization**: usually **outside** — you want to reject unauthorized calls before opening a transaction. Give it a **lower** value than the transaction advice. - **Audit that must be atomic with the business change**: **inside** the transaction so a rollback also discards the audit row. Give it a **higher** value than the transaction advice (but still runs after begin, before commit). - **Audit that must persist even on failure** (e.g. security incident log): **outside**, and typically in its own transaction (`REQUIRES_NEW`) so the outer rollback doesn't erase it. - **Metrics / timing / logging**: often outermost so they capture the full wall-clock including transaction commit time. ## Concrete nesting example Security `@Order(10)` → Metrics `@Order(20)` → Transaction (default `MAX_VALUE`) → target. On the way out, transaction commits first, then metrics record duration (including commit), then security audits the outcome. ## Practical principles I apply 1. **Make values explicit and centralised** (constants, not magic numbers scattered around). 2. **Space them** (10/20/30) so future concerns slot in without renumbering. 3. **Avoid `Integer.MIN/MAX_VALUE`** unless you deliberately want the extreme, since framework defaults live there. 4. **Document the intended onion** so the interaction is a stated contract, not reverse-engineered. 5. **Prefer `REQUIRES_NEW`** over ordering tricks when a side effect must survive rollback — ordering alone doesn't give you a separate transaction. 6. **Beware self-invocation**: proxy-based AOP (including transactions) doesn't intercept internal `this.method()` calls, which can silently defeat any ordering you designed. ## Interview signal A principal answer connects `@Order` values to transactional semantics (commit/rollback boundaries), knows the transaction advisor's default and its configurable `order`, and treats the nesting as an explicit, documented architectural contract rather than trial-and-error.
- What is the default order of Spring's declarative transaction advice, and how do you change it?By default it is Ordered.LOWEST_PRECEDENCE (Integer.MAX_VALUE), i.e. innermost. You change it via the order attribute of @EnableTransactionManagement (or <tx:annotation-driven order=...>).
- You need an audit entry to survive even when the business transaction rolls back. Is ordering enough?No. Ordering only controls nesting within the same transaction. To persist independently you need a separate transaction, e.g. @Transactional(propagation = REQUIRES_NEW) on the audit path, so the outer rollback doesn't remove it.
- How can proxy-based AOP silently break your carefully ordered aspects?Self-invocation: an internal this.method() call bypasses the proxy, so neither transaction nor custom advice fires, regardless of @Order. Route through the proxy or restructure the call to fix it.
saying these in an interview costs you the question
- Assuming the transaction always runs outermost by default (it defaults to innermost / LOWEST_PRECEDENCE)
- Thinking @Order alone can make an audit survive rollback
- Ignoring self-invocation, which defeats any ordering
- Using Integer.MIN/MAX_VALUE casually and colliding with framework defaults