Guidance for AI coding agents working in this repository.
| Concern | Choice |
|---|---|
| Language | Java 17 |
| Framework | Spring Boot 3.2.5 |
| Build | Maven (pom.xml, no Gradle) |
| Persistence | Spring Data JPA + Hibernate |
| Transactions | Atomikos 6.x via com.atomikos:transactions-spring-boot3-starter:6.0.1 |
| Database | PostgreSQL (latest) |
| Validation | Jakarta Bean Validation (spring-boot-starter-validation) |
| Testing | JUnit 5 + Spring Boot Test + Testcontainers (postgres:latest) |
com.example.perftest
├── PerfTestApplication.java entry point
├── entity/ JPA entities
├── repository/ Spring Data JPA repositories
├── service/ @Transactional business logic
├── controller/ @RestController REST endpoints
└── config/ cross-cutting config (@RestControllerAdvice, etc.)
- All mutating service methods carry
@Transactional(JTA, managed by Atomikos). - Read-only queries use
@Transactional(readOnly = true). - Never annotate controllers or repositories with
@Transactional; keep it in the service layer only. - The Atomikos starter is not Spring Boot's built-in
spring-boot-starter-jta-atomikos. Do not replace it or add the built-in one — they conflict.
- The XA data source is configured via
spring.jta.atomikos.datasource.*properties inapplication.properties. - The XA class is
org.postgresql.xa.PGXADataSource. - Do not introduce a manual
DataSourceorEntityManagerFactorybean — Atomikos auto-configuration owns those.
- Use
GenerationType.SEQUENCEwith a dedicated@SequenceGeneratorper entity (notIDENTITY), as XA transactions require sequence-based ID generation. - Audit timestamps (
createdAt,updatedAt) are managed via@PrePersist/@PreUpdate.
- Base path convention:
/api/{resource}(plural noun, lowercase). - Error responses use Spring's
ProblemDetail(RFC 7807). ThrowEntityNotFoundExceptionfor 404,IllegalArgumentExceptionfor 400,IllegalStateExceptionfor 409 —GlobalExceptionHandlermaps them. - No
@Transactionalin controllers; delegate entirely to services.
- Use Jakarta constraint annotations on entity fields.
- Accept
@Valid @RequestBodyin controller POST/PUT methods. - Do not duplicate validation logic in services if the entity already constrains it.
Start the database (Docker required):
docker build -f Dockerfile.postgres -t perftest-postgres .
docker run -d --name perftest-postgres -p 5432:5432 perftest-postgresRun the application:
mvn spring-boot:runBase URL: http://localhost:8080/api/accounts
Tests use Testcontainers — a real postgres:latest container is started automatically. No external database needed for tests.
@DynamicPropertySource overrides the Atomikos XA properties to point at the container:
registry.add("spring.jta.atomikos.datasource.xa-properties.serverName", postgres::getHost);
registry.add("spring.jta.atomikos.datasource.xa-properties.portNumber", postgres::getFirstMappedPort);Run all tests:
mvn testRun only the performance test:
mvn test -Dtest=AccountPerformanceTestThe performance test (AccountPerformanceTest) uses 20 threads, a CountDownLatch starting gun, and reports min/avg/p95/p99/max latency plus throughput (ops/s) for each phase.
- Do not switch to
spring-boot-starter-jta-atomikos— the project deliberately usescom.atomikos:transactions-spring-boot3-starter:6.0.1. - Do not use
GenerationType.IDENTITYon any entity — incompatible with XA transactions. - Do not add
spring.datasource.*properties — Atomikos owns the datasource; mixing the two will cause startup failure. - Do not mock the
AccountServiceorAccountRepositoryin integration tests — use Testcontainers so XA transaction semantics are exercised against a real database. - Do not add
@Transactionalto test methods — it bypasses the JTA transaction manager and produces misleading results.