Add library-management module: console app evolved into a Spring Boot 4 REST API

Stage 2 console classes and the shared LoanPolicy, then a Spring Boot 4.1.2 + JPA API
(H2, @Version optimistic locking, problem+json errors). Tests write output/ transcripts
while asserting the same numbers; scripts regenerate everything.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b4DPL6dtpFg4U4LHc2
This commit is contained in:
Claude
2026-11-14 21:21:16 +05:30
parent b9aeb78b24
commit 26ece9cc9b
38 changed files with 1444 additions and 0 deletions
+77
View File
@@ -0,0 +1,77 @@
# library-management
Companion code for the ankurm.com article **Library Management System in Java: Console Version
to Spring Boot REST API**. One module, three stages: the same lending rules, first in plain Java,
then split into classes, then served over HTTP with Spring Boot 4 and JPA.
Every number, status code and SQL line quoted in the article comes from a file in
[`output/`](output/). The tests write those files while asserting the same numbers, so the
transcripts cannot drift from the code.
## Versions
| Component | Version | Verified against |
|---|---|---|
| JDK | **25.0.5** (Temurin, LTS) | `java -version` |
| Spring Boot | **4.1.2** (4.1.0 went GA on 2026-06-10) | `maven-metadata.xml` on Maven Central |
| Spring Framework | 7.0.10 | `mvn dependency:list` |
| Hibernate ORM | 7.4.13.Final | `mvn dependency:list` |
| Spring Data JPA | 4.1.2 | `mvn dependency:list` |
| Tomcat (embedded) | 11.0.27 | `mvn dependency:list` |
| H2 | 2.4.240 | `mvn dependency:list` |
| Maven | 3.9.11 | `mvn -v` |
## The three stages
| Stage | Where | Run it |
|---|---|---|
| 1. One file, no build tool | `LibraryConsole.java` (attached to the article, not stored here) | `java LibraryConsole.java < scripts/demo-input.txt` |
| 2. Split into classes, rules extracted | `src/main/java/com/ankurm/library/console/` + `core/LoanPolicy.java` | `mvn -q compile && java -cp target/classes com.ankurm.library.console.ConsoleApp < scripts/demo-input.txt` |
| 3. Spring Boot REST API with JPA | `src/main/java/com/ankurm/library/api/` + `core/LoanPolicy.java` | `scripts/run.sh` |
`core/LoanPolicy.java` (14-day loans, 3 active loans, Rs 2 a day fine) is shared by stages 2 and 3
and did not change when the app moved to a database.
## Quickstart
```bash
export JAVA_HOME=/path/to/jdk-25 && export PATH="$JAVA_HOME/bin:$PATH"
mvn test # 6 tests; also regenerates output/02 to output/06
scripts/run.sh # API on http://localhost:8080 (in-memory H2, clean library per start)
scripts/api-demo.sh # in another terminal: curl walk-through
```
The first Maven run must be online so the plugins are cached; `-o` works afterwards.
`scripts/run-all.sh` regenerates every file in `output/`.
## Endpoints
| Method and path | Purpose | Refusals |
|---|---|---|
| `POST /api/books` | add a book with N copies | 400 bad fields, 409 duplicate ISBN |
| `GET /api/books` | list books with copies available | |
| `POST /api/members` | register a member | 400 bad fields, 409 duplicate email |
| `POST /api/loans` | borrow: `{"memberId":1,"bookId":2}` | 404 unknown id, 409 no copies, 422 limit or duplicate loan |
| `POST /api/loans/{id}/return` | return, optional `{"returnedOn":"2026-11-20"}` | 409 already returned, 422 before borrow date |
| `GET /api/loans` | all loans, one SQL statement | |
Errors are RFC 9457 `application/problem+json`.
## Captured output
| File | What it shows | Produced by |
|---|---|---|
| `01-console-structured.txt` | stage 2 run of `scripts/demo-input.txt` (identical to stage 1) | `run-all.sh` |
| `02-loan-policy.txt` | the fine table | `LoanPolicyTest` |
| `03-rest-api.txt` | every success and refusal over MockMvc | `LibraryApiTest` |
| `04-query-count.txt` | 11 SQL statements versus 1 for the same list | `QueryCountTest` |
| `05-race-with-version.txt` | 8 threads, last copy, `@Version`: one winner | `BorrowRaceTest` |
| `06-race-without-version.txt` | same race without `@Version`: 8 loans for 1 copy | `BorrowRaceTest` |
| `07-curl-demo.txt` | the same API on a real Tomcat, driven with curl (dates are the day it ran) | `scripts/api-demo.sh` |
| `08-open-in-view-warning.txt` | the Boot default this module overrides | `scripts/open-in-view-warning.sh` |
| `09-borrow-sql.txt` | the exact SQL one borrow sends, including `where id=? and version=?` | `scripts/sql-trace.sh` |
Timing-dependent detail: in `05` the 7 refused borrows split between "optimistic-lock failure"
(a thread read the book before the winner committed) and "any other reason" (a thread started late
and found no copy left). Over repeated runs I saw splits ranging from 7/0 to 4/3, which is why the
transcript counts them separately. The invariants (1 winner, 1 loan, 0 copies left) are asserted.