Example: Flashcard
The example app builds the same features as ch07-jdbc-plus from spring-jdbc-book without Spring Boot: decks, cards, tags, spaced-repetition study, smart decks, CSV import and export, and statistics.
- DB
-
Repositories use
NamedParameterJdbcTemplatedirectly. The executed SQL is visible in the code. Inserts are the exception:SimpleJdbcInsertreads the table metadata and returns the generated key, withSimplePropertySqlParameterSourcetaking the values straight off the domain record.SmartDeckRepositorystill spells its parameters out, because itsSmartConditionenum has to reach the driver asname()rather than as an enum. Row mapping goes throughDataClassRowMapper. The reflection thatDataClassRowMapperandSimplePropertySqlParameterSourceneed under a native image comes from a config the build generates from the domain package.FlashcardDatabasecreates the H2DataSourceand appliesschema.sqlto it before handing it out. Itsfile()serves the running app, and itsinMemory()gives each test a database of its own. - DI
-
There is no container.
FlashcardContextwires everything by calling constructors directly, playing the role of Spring’sApplicationContextby hand. - Transactions
-
Instead of AOP, services call
Transactions.write()/read(), a thin wrapper aroundTransactionTemplate. The wrapped block is exactly the transaction scope. - JSON API
-
Beyond the features of ch07-jdbc-plus, a JSON API (
/api/decks,/api/decks/{id}/cards) sits on the same service layer to show the framework’s REST support. Its wire format is inflashcard.web.Codecs, as hand-writtenJsonWriter/JsonReaderlambdas and as two generated codecs: theNewDeckbody record carries@JsonBound, and the domain recordDeckSummaryis bound through a mixin in the same class. - Introspection
-
/_routeslists every route, and/openapi.jsonis the same list as an OpenAPI 3.1 document. Both are built fromapp.routes(), which exposes the routing table as data. The document goes throughspider-silk-openapi, and the app decides what it covers: the/apiroutes, since everything else here answers with HTML. Those are also the only routes registered with a description, which/_routesshows in a column of its own and/openapi.jsoncarries as each operation’ssummary. - Every response
-
FlashcardContext.createAppconfigures all three of the response-wide concerns, as a real application would.securityHeaders(…)takes the defaults plus a policy this app can run under:default-src 'self'covers everything it loads, andstyle-srcalso allows inline becausestats.jtesizes its chart bars with a style attribute, which no stylesheet can do. HSTS is deliberately absent, sincemainserves the app overhttp://localhost.gzip()covers the pages, the stylesheet, the JSON, and the CSV export alike.cors(…)is scoped to/openapi.jsonalone, and not to/api/decks, which creates decks. That document is the only resource here meant for a client other than this application, such as a Swagger UI served elsewhere. - Logging
-
The application logs through the slf4j API, with Logback as the backend and
src/main/resources/logback.xmlas its configuration.FlashcardContext.createAppregisters a request logger that writes one line per request to the loggerflashcard.access. Jetty’s lines and the framework’s servlet log follow the same configuration, and so do spring-jdbc’s, since its commons-logging finds slf4j on the classpath. The tests run underlogback-test.xml, which keeps the console to warnings and errors. Logging shows the same setup on Log4j and on JDK logging. - Routing
-
There is no
Controllerinterface.FlashcardContext.registerRoutesis the whole table, and handlers arrive as anActionclass, a public method reference, or a lambda.
Run
gradle :example-flashcard:run # precompiled templates
gradle :example-flashcard:run --args=--dev # hot-reloads edited .jte files
# http://localhost:8080
The two commands are jte’s production and development modes: the first renders the template classes the build compiled, the second recompiles a template whose file changed on its next render.
FlashcardApp turns --dev into the devMode flag of FlashcardContext, and FlashcardContext picks the TemplateRenderer from that flag.
The database is the H2 file FlashcardDatabase.file() opens (~/db/spider-silk/flashcard), so data survives restarts.
gradle :example-flashcard:nativeCompile builds the same app as a GraalVM native image.