Read the root and examples/AGENTS.md first. Reference module for JPA, transactions,
raw JDBC, Flyway migrations, and HikariCP.
entity/ Order, OrderItem (JPA entities), OrderStatus (enum)
repository/ OrderRepository — Spring Data JPA (derived, JPQL, native, @Modifying)
service/ OrderService — the transaction-boundary layer
jdbc/ JdbcOrderDao — JdbcTemplate/NamedParameterJdbcTemplate/SimpleJdbcInsert
config/ DataSourceConfig — programmatic HikariCP (@Primary, overrides autoconfig)
resources/db/migration/ V1__create_orders.sql (Flyway)
- Entities:
@Versionoptimistic locking;@PrePersist/@PreUpdatetimestamps; bidirectional@OneToMany(cascade = ALL, orphanRemoval = true)maintained through helper methods (addItem/removeItemkeep both sides in sync and callrecalculateTotal()); no public setters for managed fields (id, version, timestamps, total);@Enumerated(EnumType.STRING)for enums. Entities are mutable (JPA needs it) — that is the only place mutability is the default. - Transactions: class-level
@Transactional(readOnly = true)on the service; write methods override with@Transactional(rollbackFor = Exception.class)and explicit propagation/isolation where it matters. Transactions live in the service layer, never in controllers or repositories. - Queries: derived methods for simple cases;
@QueryJPQL with@Paramfor joins;LEFT JOIN FETCH+Optional<Order>return for eager loading (never returnnullfrom a repository); native SQL only when JPQL can't express it;@Modifyingfor bulk updates. - JDBC:
JdbcOrderDaoshows positional params,MapSqlParameterSource,SimpleJdbcInsert.executeAndReturnKey,batchUpdate, and an immutablerecord OrderSummaryprojection with a staticRowMapperconstant. - Migrations: Flyway,
V{n}__snake_case_description.sql,ddl-auto: validate(Hibernate validates, Flyway owns the schema). Write portable, SQL-standard DDL (GENERATED BY DEFAULT AS IDENTITY, notAUTO_INCREMENT/BIGSERIAL) so the same migration runs on H2 and PostgreSQL. Never edit an applied migration — add a new versioned file.
- Default: H2 in-memory (
jdbc:h2:mem:templatedb), console at/h2-console, Flyway runsV1__create_orders.sqlat startup. No external services needed. productionprofile: PostgreSQL onlocalhost:5432/templatedb; requiresDB_USERNAME/DB_PASSWORDenv vars. Migrations are portable DDL and shared with the H2 path; if a vendor-specific migration ever becomes necessary, add it underdb/migration/postgresqland append that location in the production profile'sspring.flyway.locations(see the comment inapplication.yml).
./mvnw -pl examples/database spring-boot:run # H2, Flyway migrates on boot
./mvnw -pl examples/database test # 39 tests: entities, service, @DataJpaTest, @JdbcTest- Open Session in View is disabled (
open-in-view: false). Touching a lazy association (Order.items,OrderItem.order) outside a transaction throwsLazyInitializationException. UsefindByIdWithItems(LEFT JOIN FETCH, returnsOptional<Order>) or stay inside a@Transactionalmethod. DataSourceConfigis@Primaryand replaces Boot's auto-configured pool; thespring.datasource.hikari.*yml block binds to it via@ConfigurationProperties.- Tests (39) run on embedded H2 with the real Flyway migration — no Docker needed:
OrderTest/OrderItemTest/OrderStatusTest(entities),OrderServiceTest(Mockito),OrderRepositoryTest(@DataJpaTest),JdbcOrderDaoTest(@JdbcTest+@Import). Extend the matching class when you touch a layer; for real-Postgres coverage add a Testcontainers*ITmodeled onexamples/testing/PostgresContainerIT. - Service methods throw plain
IllegalArgumentException/IllegalStateException(no web layer here); wrap in domain exceptions if you expose them over HTTP.