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
78 lines
4.1 KiB
Markdown
78 lines
4.1 KiB
Markdown
# 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.
|