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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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"}
|
||||
|
||||
@@ -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=?
|
||||
@@ -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"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user