Spring Boot JDBC Demo
Latest stable Based on Exposed release 1.11.0
Run an Exposed DAO repository behind a Spring MVC API, with transaction ownership kept visible.
What you learn
Section titled “What you learn”This demo connects @ExposedEntity, ExposedJdbcRepository, Spring MVC, H2, and Spring Boot 4. It is especially useful for seeing where Exposed DAO entities must be converted to DTOs: every controller operation enters an Exposed transaction {} block and calls toRecord() before the entity leaves that block.
The demo is not a pure “zero-configuration” example. ExposedConfig explicitly creates SpringTransactionManager; the integration module supplies repository discovery and implementation.
Prerequisites
Section titled “Prerequisites”- JDK 21+
- the repository Gradle wrapper
- no Docker for the default path; both tests and
bootRunuse in-memory H2
Run the tests first:
./gradlew :exposed-spring-boot-jdbc-demo:testThen start the HTTP application:
./gradlew :exposed-spring-boot-jdbc-demo:bootRunThe application listens on port 8080 by default.
Expected result
Section titled “Expected result”At startup, DataInitializer asks MigrationUtils for the statements required by the Products table and inserts three products when the table is empty. These calls should return JSON or the stated status:
curl http://localhost:8080/productscurl 'http://localhost:8080/products/search?name=Kotlin Programming Book'curl -i http://localhost:8080/products/999999The list initially contains three rows, the exact-name search returns the matching product, and the missing ID returns 404. Controller tests also cover create, update, and delete. Repository tests cover CRUD, paging, sorting, Exposed DSL predicates, and the derived methods findByName and findByPriceLessThan.
Failure diagnosis
Section titled “Failure diagnosis”- Context fails to create
springTransactionManager: check that oneDataSourceis available and that an application override does not conflict withExposedConfig. No transaction in context: keep DAO access andtoRecord()insidetransaction {}; do not serialize a live DAO entity after the block closes.- Search returns nothing:
findByNameis an exact derived query, not a substring search. - Data disappears after restart: the default URL is
jdbc:h2:mem:mvcdb; persistence across processes is not part of this demo. - Schema startup fails: compare the
Productstable withMigrationUtilsoutput and the checked-in migration before moving to a production database.
Next route
Section titled “Next route”Read Spring Boot JDBC integration for repository factory and transaction-manager ownership, then JDBC repository patterns for paging, audit, and soft-delete boundaries. The Exposed workshop expands the same JDBC path into schema, transaction, and repository exercises.
When to use it
Section titled “When to use it”Use this example before adopting the Spring integration when your application is blocking Spring MVC and uses Exposed DAO entities behind repository interfaces. It is also a compact regression fixture for repository method parsing. Do not copy it as-is when the application needs R2DBC, durable migration policy, or a service-layer transaction spanning several repository calls.
Coordinates
Section titled “Coordinates”The demo publishes no artifact. A consumer application imports the central BOM and the JDBC integration without an individual version:
dependencies { implementation(platform("io.github.bluetape4k:bluetape4k-dependencies:<version>")) implementation("io.github.bluetape4k.exposed:bluetape4k-exposed-spring-boot-jdbc")}Core concepts
Section titled “Core concepts”ProductEntity is transaction-bound; ProductRecord is safe to return over HTTP. ProductJdbcRepository extends ExposedJdbcRepository<ProductEntity, Long> and adds bounded PartTree methods. The controller currently owns each Exposed transaction, while Spring owns the DataSource and the explicitly declared SpringTransactionManager bean.
This separation is intentional learning material. In a larger application, move a multi-operation transaction to a service instead of nesting or scattering controller transactions.
Quick start
Section titled “Quick start”- Run the test task and inspect
ProductControllerTestplusProductJdbcRepositoryTest. - Start
bootRunand callGET /products. - Put a breakpoint in
ProductController.findByIdand verify thattoRecord()executes insidetransaction {}. - Add one derived query to
ProductJdbcRepositoryand a matching repository test before using it from the controller.
API by task
Section titled “API by task”| Task | Released source to follow |
|---|---|
| Repository contract | ProductJdbcRepository |
| DAO/table mapping | ProductEntity and Products |
| DTO conversion and HTTP status | ProductController |
| DataSource transaction manager | ExposedConfig |
| Schema initialization and seed data | DataInitializer |
| MVC behavior | ProductControllerTest |
| Repository behavior | ProductJdbcRepositoryTest |
Recommended patterns
Section titled “Recommended patterns”- Return DTOs, not Exposed DAO entities, from the HTTP boundary.
- Put business transactions in a Spring-managed service when several operations form one unit of work.
- Treat derived query names as a supported, tested subset rather than assuming every Spring Data keyword works.
- Replace startup-generated schema changes with reviewed migrations in production.
- Keep blocking JDBC work off reactive or event-loop threads.
Integrations
Section titled “Integrations”The demo uses Spring Boot Web and JDBC starters, the bluetape4k Exposed Spring Boot JDBC module, Exposed DAO/JDBC/migration modules, Jackson 3, and H2. It uses neither R2DBC nor an external container.
Configuration
Section titled “Configuration”application.yml configures jdbc:h2:mem:mvcdb, user sa, and debug logging for bluetape4k and Exposed. spring.exposed.generate-ddl: true appears in the demo configuration, while startup schema work is performed explicitly by DataInitializer with MigrationUtils. For production, choose one reviewed migration authority and remove ambiguous ownership.
Operations
Section titled “Operations”Separate context-start failures, schema initialization, transaction failures, derived-query parsing, and HTTP serialization in logs. H2 success proves the application wiring, not PostgreSQL/MySQL compatibility. When changing databases, rerun repository behavior and migration validation against the selected engine.
Testing
Section titled “Testing”ProductControllerTest starts a real random-port Spring application and calls it with RestClient. ProductJdbcRepositoryTest checks the repository against H2 and clears the table between tests. Add a service-level rollback test before moving a multi-step business transaction out of the controller.
Workshops and learning path
Section titled “Workshops and learning path”Follow the code in this order: ExposedConfig → ProductEntity → ProductJdbcRepository → ProductController → the two test classes. The module manual explains what the integration creates; the workshop provides broader practice. This demo remains the runnable reference that ties those two layers together.
Limitations
Section titled “Limitations”The demo uses one in-memory database, startup schema adjustment, controller-owned Exposed transactions, and no authentication. It is not a production migration strategy, connection-pool sizing guide, or proof that every Spring Data derived-query keyword is supported.
Release diagrams
Section titled “Release diagrams”These diagrams are loaded directly from README assets published with the 1.11.0 release and pinned to its immutable commit. They describe this manual’s released structure and runtime flows, not later Snapshot changes. Select a preview to open the SVG at the same release commit.
Spring Boot JDBC demo structure diagram
Section titled “Spring Boot JDBC demo structure diagram”Release README: examples/jdbc-demo/README.md
Spring Boot JDBC demo request transaction flow diagram
Section titled “Spring Boot JDBC demo request transaction flow diagram”Release README: examples/jdbc-demo/README.md

