Opinionated rule set for implementing, tuning, and observing ACID & distributed database transactions in modern Java back-ends.
You know the drill. Your service is humming along perfectly until it isn't. Deadlocks during peak traffic. Mysterious data inconsistencies that only show up in production. Transaction timeouts bringing down your entire checkout flow. That cascade of alerts at 3 AM because your distributed transaction logic just can't handle real-world complexity.
Modern Java backends demand bulletproof transaction handling, but most teams are flying blind:
These aren't edge cases anymore. They're everyday realities when your backend actually scales.
These Cursor Rules transform how you handle database transactions in Java applications. Built from real production lessons across Spring Boot, JPA/Hibernate, and distributed systems like Axon Framework and Camunda Platform 8.
Core Philosophy: Start with minimal isolation, design for failure, and instrument everything. Your transactions should be fast, observable, and resilient by default.
Before: Debugging transaction issues means trawling through generic stack traces and guessing at isolation problems.
@Transactional // Mystery box - what isolation? What timeout?
public void processOrder(OrderRequest request) {
// Hope nothing goes wrong
}
After: Every transaction boundary is explicit, observable, and tuned for your use case.
@Transactional(
isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRED,
timeout = 5
)
public void processOrder(OrderRequest request) {
// Validated inputs, clear boundaries, predictable behavior
}
Saga Pattern Implementation: Instead of fragile 2-PC, you'll design compensating workflows that actually work:
@Saga
public class OrderProcessingSaga {
@StartSaga
@SagaOrchestrationStart
public void handle(OrderSubmittedEvent event) {
commandGateway.send(new ReserveInventoryCommand(event.getOrderId()));
}
@EndSaga
public void handle(OrderFailedEvent event) {
// Automatic compensation logic
commandGateway.send(new ReleaseInventoryCommand(event.getOrderId()));
}
}
Smart Isolation Choices: Start at READ_COMMITTED and prove you need higher levels through testing, not assumptions.
Optimistic Locking Patterns: Handle contention gracefully with built-in retry logic:
@Retryable(value = OptimisticLockException.class, maxAttempts = 3)
public void updateInventory(Long productId, int quantity) {
// Automatic exponential backoff on version conflicts
}
Every @Transactional annotation includes explicit isolation, propagation, and timeout settings. No more mystery configurations or production surprises.
OpenTelemetry spans for every transaction boundary with structured logging:
{
"span.name": "db.tx",
"db.isolation_level": "READ_COMMITTED",
"duration_ms": 45,
"db.rows_locked": 3,
"trace_id": "abc123"
}
Saga orchestration with Axon Framework and Camunda workflow management. Every distributed operation has a compensation strategy designed from day one.
Transactions complete in under 100ms by design. Batch operations, proper indexing strategies, and contention-aware retry logic built into your workflow.
Old Approach:
// Somewhere in your service layer
@Transactional // Default everything, hope for the best
public void processPayment() {
// Call external payment API inside transaction
paymentService.charge(); // Network I/O holding locks!
updateOrderStatus();
}
Result: 20 seconds troubleshooting, another 40 minutes finding the external API call holding transaction locks.
With These Rules:
// Pre-transaction validation and I/O
PaymentResult result = paymentService.charge(); // Outside transaction boundary
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 5)
public void recordPayment(PaymentResult result) {
updateOrderStatus(result); // Fast, local operations only
}
Result: Pattern prevents the issue entirely. Clear transaction boundaries make problems impossible.
Scenario: Your team discovers optimistic locking exceptions during peak traffic simulation.
Old Response: Manually add try-catch blocks, implement custom retry logic, hope you got the exponential backoff right.
With These Rules: Built-in retry patterns handle contention automatically:
@Entity
public class Inventory {
@Version
private Long version; // Optimistic locking enabled
// Automatic retry with exponential backoff
@Retryable(value = OptimisticLockException.class, maxAttempts = 3)
public void updateStock(int delta) { /* ... */ }
}
Add the Rules to Cursor
# Copy rules to your .cursor-rules file
Configure Your Transaction Manager
# application.yml
spring:
jpa:
properties:
hibernate:
connection:
isolation: 2 # READ_COMMITTED default
open-in-view: false # Prevent lazy loading issues
Enable Observability
@Configuration
public class TransactionObservabilityConfig {
@Bean
public TransactionInterceptor txInterceptor() {
// OpenTelemetry spans for all @Transactional methods
}
}
Saga Implementation with Axon:
@Saga
public class OrderFulfillmentSaga {
// Complete workflow orchestration with compensation
}
Camunda Workflow Integration:
@Component
public class PaymentProcessingDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
// Isolated transaction per workflow step
}
}
@Testcontainers
class TransactionConcurrencyTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@Test
void shouldHandleConcurrentUpdates() {
// Real database, real concurrency scenarios
CompletableFuture.allOf(
updateInventoryAsync(productId, -1),
updateInventoryAsync(productId, -1)
).join();
// Assert no dirty reads or phantom updates
}
}
For Senior Developers: Spend time on business logic instead of debugging mysterious transaction behavior.
For DevOps Teams: Rich observability data makes performance tuning and incident response straightforward.
For Product Teams: Reliable transaction handling means features ship without performance regressions.
These rules aren't theoretical best practices—they're distilled from production systems handling millions of transactions daily. The combination of explicit configuration, built-in observability, and resilient distributed patterns gives you transaction handling that actually scales.
The Bottom Line: Your transactions become a competitive advantage instead of a liability. Fast, reliable, and observable by default. Your 3 AM alert volume drops to zero, and your team can focus on building features that matter.
Ready to never lose sleep over database transactions again? These rules make bulletproof transaction handling your new default.
You are an expert in Java 17+, Spring Boot 3, JPA/Hibernate, Axon Framework, Camunda Platform 8, FoundationDB, PostgreSQL, MySQL 8+, CockroachDB, Google Spanner, OpenTelemetry, and Jaeger.
Key Principles
- Prefer the lowest isolation level that still guarantees correctness; start at **Read Committed** and raise only when tests prove anomalies.
- Keep transactions **short-lived**: fetch ➞ mutate ➞ commit in < 100 ms; never run business logic loops or I/O inside a transaction scope.
- Treat transactions as **boundaries**: authenticate, authorise, and validate *before* entering; log and trace *inside*; release resources *immediately after*.
- For distributed workflows favour **Saga / orchestration** over 2-PC; design every step to be **idempotent** and to expose a compensating action.
- Build in **observability**: emit `transaction.start|commit|rollback` spans and metrics; alert on timeouts > 95th percentile and deadlock counts.
Java (Language-Specific Rules)
- Annotate service methods with `@Transactional` (Spring) and choose properties explicitly:
- `@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, timeout = 5)`
- Keep entity objects **detached** outside the boundary; never leak `Session`/`EntityManager`.
- Avoid implicit flush: call `entityManager.flush()` only when necessary; otherwise rely on commit.
- Use **record** classes or immutable DTOs at the boundary to prevent accidental dirty updates.
- Prefer **optimistic locking** (`@Version`) with retry loops to pessimistic locks unless contention > 10 %.
- Use `try (Connection c = dataSource.getConnection())` only for low-level, batch, or JDBC-native code; otherwise rely on declarative management.
Error Handling & Validation
- Detect problems early: validate inputs & invariants *before* the `@Transactional` method.
- Inside the transaction:
- Throw `DataIntegrityViolationException` for business conflicts; map to 409 HTTP.
- Translate SQLState errors via Spring’s `SQLExceptionTranslator`.
- Catch `OptimisticLockException` and retry **max 3** times using exponential back-off (100 ms → 400 ms).
- Always **log context** (`traceId`, `userId`, `txId`) on error; include `tx.isolation`, `durationMs` in structured JSON.
- For distributed steps, publish a *compensate* command on failure instead of local rollback.
Framework-Specific Rules
Spring Boot / JPA
- Declare datasource‐specific defaults via `spring.jpa.properties.hibernate.connection.isolation`.
- Disable Spring’s default `open-in-view`; never expose lazy proxies beyond REST layer.
- Use `PlatformTransactionManager` bean when mixing JPA & JDBC.
Axon Framework (Saga Pattern)
- Model long-running transactions as **Process Managers** annotated with `@Saga`.
- Store saga state in the Axon store, *never* in domain aggregates.
- Emit **compensating commands** in `@EndSaga` to undo partial effects.
Camunda (Orchestration)
- Model each micro-service step as an external task; set `transactionPropagation` ="REQUIRES_NEW" in BPMN to isolate failures.
- Use `IncidentHandler` to escalate unrecoverable failures and trigger compensations.
FoundationDB / CockroachDB / Spanner
- Leverage **serialisable, globally ordered** transactions by default; but split large logical operations into smaller transactions to avoid contention.
- Use *savepoints* to minimise retries when working with high-latency multi-region clusters.
Additional Sections
Testing
- Use **Testcontainers** to spin up real DBs in CI; never rely on in-memory DBs for concurrency tests.
- Add **isolation-level suites** that concurrently insert/update the same rows and assert absence of anomalies (dirty reads, non-repeatable reads).
- Wrap saga tests with Axon’s `SagaTestFixture`; simulate message ordering and timeouts.
Performance
- Index **foreign keys & frequently filtered columns** referenced inside transactions.
- Batch inserts/updates with `jdbcTemplate.batchUpdate` or JPA `@BatchSize`.
- Log `pg_stat_activity` (Postgres) or `information_schema.innodb_locks` (MySQL) during load tests; fix hot-spot queries.
Security
- Enforce **row-level security** (RLS) where supported; otherwise inject tenant filters via Hibernate `@Filter`.
- Write to **audit tables** with `user_id`, `action`, `old_value`, `new_value`, `timestamp` on every COMMIT.
- Do not expose internal transaction identifiers to external clients; use opaque, random IDs.
Observability
- Instrument libraries with OpenTelemetry `SpanKind.INTERNAL` named `db.tx`; attach attributes: `db.system`, `db.isolation_level`, `db.rows_locked`.
- Export traces to Jaeger; enable 95 th percentile latency SLO alerts.
Common Pitfalls & Guardrails
- ❌ Holding open transactions across remote calls or message queues.
- ❌ Mixing ORM-managed and native JDBC writes in the same transaction without synchronising flush.
- ❌ Using `SERIALIZABLE` everywhere – increases retries and deadlocks.
- ✅ Plan for **graceful degradation**: on persist failure, continue read-only service paths.
Directory & File Structure
- `src/main/java/com/org/order/OrderService.java` // @Transactional façade
- `src/main/java/com/org/order/db` // DB access layer
- `src/main/java/com/org/order/saga` // Axon sagas & commands
- `src/test/java/com/org/order/tx` // isolation & concurrency tests
Glossary
- **MVCC** – Multi-Version Concurrency Control, underlying snapshot isolation.
- **Saga** – sequence of local transactions with compensation.
- **2-PC** – Two-Phase Commit, a distributed atomic commitment protocol; avoid when possible.