Skip to content
Exposed docs1.11

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.

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.

  • JDK 21+
  • the repository Gradle wrapper
  • no Docker for the default path; both tests and bootRun use in-memory H2

Run the tests first:

Terminal window
./gradlew :exposed-spring-boot-jdbc-demo:test

Then start the HTTP application:

Terminal window
./gradlew :exposed-spring-boot-jdbc-demo:bootRun

The application listens on port 8080 by default.

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:

Terminal window
curl http://localhost:8080/products
curl 'http://localhost:8080/products/search?name=Kotlin Programming Book'
curl -i http://localhost:8080/products/999999

The 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.

  • Context fails to create springTransactionManager: check that one DataSource is available and that an application override does not conflict with ExposedConfig.
  • No transaction in context: keep DAO access and toRecord() inside transaction {}; do not serialize a live DAO entity after the block closes.
  • Search returns nothing: findByName is 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 Products table with MigrationUtils output and the checked-in migration before moving to a production database.

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.

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.

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")
}

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.

  1. Run the test task and inspect ProductControllerTest plus ProductJdbcRepositoryTest.
  2. Start bootRun and call GET /products.
  3. Put a breakpoint in ProductController.findById and verify that toRecord() executes inside transaction {}.
  4. Add one derived query to ProductJdbcRepository and a matching repository test before using it from the controller.
TaskReleased source to follow
Repository contractProductJdbcRepository
DAO/table mappingProductEntity and Products
DTO conversion and HTTP statusProductController
DataSource transaction managerExposedConfig
Schema initialization and seed dataDataInitializer
MVC behaviorProductControllerTest
Repository behaviorProductJdbcRepositoryTest
  • 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.

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.

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.

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.

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.

Follow the code in this order: ExposedConfigProductEntityProductJdbcRepositoryProductController → 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.

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.

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

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”

Spring Boot JDBC demo request transaction flow diagram

Release README: examples/jdbc-demo/README.md