Add crud-mysql module: Spring Boot 4.1 CRUD API on a real MySQL 8.4.6 with JPA, Flyway, validation and ProblemDetail errors

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HhW8LD8r3EigoBaCGrQZx5

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HhW8LD8r3EigoBaCGrQZx5
This commit is contained in:
Claude
2026-10-21 16:43:45 +00:00
parent ba709737de
commit 2996628ff1
27 changed files with 968 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
# What the application is connected to and what Flyway built
--- the server and the driver ---
database : MySQL 8.4.6
driver : MySQL Connector/J mysql-connector-j-9.7.0 (Revision: 0aade1f13bcc98faf7dda5c02e782481eb291f62)
dialect : MySQLDialect
ddl-auto : validate
--- flyway_schema_history ---
version=1 description=create book success=true
--- SHOW CREATE TABLE book ---
CREATE TABLE `book` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`author` varchar(120) NOT NULL,
`isbn` varchar(13) NOT NULL,
`price` decimal(10,2) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_book_isbn` (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
+57
View File
@@ -0,0 +1,57 @@
# Create, read, list, update, delete against MySQL. Each block is the curl command, then the status line, the Location header if any, and the body. The test uses a random port; it is printed as 8080.
--- create three books ---
$ curl -i -X POST 'localhost:8080/api/books' -H 'Content-Type: application/json' -d '{"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":45.50}'
HTTP 201 Location: http://localhost:8080/api/books/1
{"id":1,"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":45.50}
$ curl -i -X POST 'localhost:8080/api/books' -H 'Content-Type: application/json' -d '{"title":"Java Concurrency in Practice","author":"Brian Goetz","isbn":"9780321349606","price":39.99}'
HTTP 201 Location: http://localhost:8080/api/books/2
{"id":2,"title":"Java Concurrency in Practice","author":"Brian Goetz","isbn":"9780321349606","price":39.99}
$ curl -i -X POST 'localhost:8080/api/books' -H 'Content-Type: application/json' -d '{"title":"Clean Code","author":"Robert Martin","isbn":"9780132350884","price":33.00}'
HTTP 201 Location: http://localhost:8080/api/books/3
{"id":3,"title":"Clean Code","author":"Robert Martin","isbn":"9780132350884","price":33.00}
--- read one ---
$ curl -i -X GET 'localhost:8080/api/books/1'
HTTP 200
{"id":1,"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":45.50}
--- list, two per page ---
$ curl -i -X GET 'localhost:8080/api/books?page=0&size=2'
HTTP 200
{"content":[{"id":1,"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":45.50},{"id":2,"title":"Java Concurrency in Practice","author":"Brian Goetz","isbn":"9780321349606","price":39.99}],"page":0,"size":2,"totalElements":3,"totalPages":2}
$ curl -i -X GET 'localhost:8080/api/books?page=1&size=2'
HTTP 200
{"content":[{"id":3,"title":"Clean Code","author":"Robert Martin","isbn":"9780132350884","price":33.00}],"page":1,"size":2,"totalElements":3,"totalPages":2}
--- filter by author ---
$ curl -i -X GET 'localhost:8080/api/books?author=goetz'
HTTP 200
{"content":[{"id":2,"title":"Java Concurrency in Practice","author":"Brian Goetz","isbn":"9780321349606","price":39.99}],"page":0,"size":5,"totalElements":1,"totalPages":1}
--- update the price ---
$ curl -i -X PUT 'localhost:8080/api/books/1' -H 'Content-Type: application/json' -d '{"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":42.00}'
HTTP 200
{"id":1,"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":42.00}
--- delete, then ask for it again ---
$ curl -i -X DELETE 'localhost:8080/api/books/3'
HTTP 204
$ curl -i -X GET 'localhost:8080/api/books/3'
HTTP 404
{"detail":"Book 3 not found","instance":"/api/books/3","status":404,"title":"Not Found"}
--- what is in the table now (read with SQL, not through the API) ---
id=1 title=Effective Java price=42.00
id=2 title=Java Concurrency in Practice price=39.99
+45
View File
@@ -0,0 +1,45 @@
# What the client sees when something is wrong
--- validation: three fields wrong at once ---
$ curl -i -X POST 'localhost:8080/api/books' -H 'Content-Type: application/json' -d '{"title":"","author":"Someone","isbn":"123","price":-1}'
HTTP 400
{"detail":"Invalid request content.","instance":"/api/books","status":400,"title":"Bad Request","errors":{"isbn":"must be 13 digits starting with 978 or 979","price":"must be greater than or equal to 0.00","title":"must not be blank"}}
--- duplicate ISBN on create ---
$ curl -i -X POST 'localhost:8080/api/books' -H 'Content-Type: application/json' -d '{"title":"Java Concurrency in Practice","author":"Brian Goetz","isbn":"9780321349606","price":39.99}'
HTTP 409
{"detail":"A book with that ISBN already exists","instance":"/api/books","status":409,"title":"Conflict"}
--- duplicate ISBN on update (book 2 takes book 1's ISBN) ---
$ curl -i -X PUT 'localhost:8080/api/books/2' -H 'Content-Type: application/json' -d '{"title":"Java Concurrency in Practice","author":"Brian Goetz","isbn":"9780134685991","price":39.99}'
HTTP 409
{"detail":"A book with that ISBN already exists","instance":"/api/books/2","status":409,"title":"Conflict"}
--- the message MySQL itself produced for the duplicate ---
Duplicate entry '9780134685991' for key 'book.uk_book_isbn'
--- unknown id ---
$ curl -i -X GET 'localhost:8080/api/books/99'
HTTP 404
{"detail":"Book 99 not found","instance":"/api/books/99","status":404,"title":"Not Found"}
$ curl -i -X DELETE 'localhost:8080/api/books/99'
HTTP 404
{"detail":"Book 99 not found","instance":"/api/books/99","status":404,"title":"Not Found"}
--- the id is not a number ---
$ curl -i -X GET 'localhost:8080/api/books/abc'
HTTP 400
{"detail":"Failed to convert 'id' with value: 'abc'","instance":"/api/books/abc","status":400,"title":"Bad Request"}
--- the body is not JSON ---
$ curl -i -X POST 'localhost:8080/api/books' -H 'Content-Type: application/json' -d '{not json'
HTTP 400
{"detail":"Failed to read request","instance":"/api/books","status":400,"title":"Bad Request"}
+23
View File
@@ -0,0 +1,23 @@
# The SQL Hibernate sent for each request (recorded by a StatementInspector)
--- create: POST /api/books -> HTTP 201 ---
insert into book (author,isbn,price,title) values (?,?,?,?)
--- read one: GET /api/books/1 -> HTTP 200 ---
select b1_0.id,b1_0.author,b1_0.isbn,b1_0.price,b1_0.title from book b1_0 where b1_0.id=?
--- list page 0, size 2: GET /api/books?page=0&size=2 -> HTTP 200 ---
select b1_0.id,b1_0.author,b1_0.isbn,b1_0.price,b1_0.title from book b1_0 order by b1_0.id limit ?,?
select count(b1_0.id) from book b1_0
--- list filtered by author: GET /api/books?author=bloch -> HTTP 200 ---
select b1_0.id,b1_0.author,b1_0.isbn,b1_0.price,b1_0.title from book b1_0 where upper(b1_0.author) like upper(?) escape '\\' order by b1_0.id limit ?
--- update: PUT /api/books/1 -> HTTP 200 ---
select b1_0.id,b1_0.author,b1_0.isbn,b1_0.price,b1_0.title from book b1_0 where b1_0.id=?
update book set author=?,isbn=?,price=?,title=? where id=?
--- delete: DELETE /api/books/1 -> HTTP 204 ---
select b1_0.id,b1_0.author,b1_0.isbn,b1_0.price,b1_0.title from book b1_0 where b1_0.id=?
delete from book where id=?
+32
View File
@@ -0,0 +1,32 @@
# Real curl -i output against the packaged jar (java -jar target/crud-mysql-1.0.0.jar), Date header removed
$ curl -i -X POST localhost:8080/api/books -H 'Content-Type: application/json' -d '{"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":45.50}'
HTTP/1.1 201
Location: http://localhost:8080/api/books/1
Content-Type: application/json
Transfer-Encoding: chunked
{"id":1,"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":45.50}
$ curl -i localhost:8080/api/books/1
HTTP/1.1 200
Content-Type: application/json
Content-Length: 94
{"id":1,"title":"Effective Java","author":"Joshua Bloch","isbn":"9780134685991","price":45.50}
$ curl -i -X DELETE localhost:8080/api/books/1
HTTP/1.1 204
$ curl -i localhost:8080/api/books/1
HTTP/1.1 404
Content-Type: application/problem+json
Transfer-Encoding: chunked
{"detail":"Book 1 not found","instance":"/api/books/1","status":404,"title":"Not Found"}