CRUD hoàn chỉnh
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Mỗi bài trong bốn bài trước lo một phần: nhận dữ liệu, trả kết quả, DTO, validation. Bài này ghép tất cả lại thành API sản phẩm đủ bốn thao tác thêm, đọc, sửa, xoá. Dữ liệu vẫn để trong bộ nhớ, chương 4 sẽ chuyển sang database.
Khái niệm
🔄 CRUD: bốn thao tác cơ bản với dữ liệu gồm Create (tạo), Read (đọc), Update (sửa), Delete (xoá).
| Thao tác | Request | Trả về khi thành công |
|---|---|---|
| Create | POST /api/products |
201 + sản phẩm mới |
| Read | GET /api/products |
200 + danh sách |
| Read | GET /api/products/1 |
200 + sản phẩm |
| Update | PUT /api/products/1 |
204 |
| Delete | DELETE /api/products/1 |
204 |
Không tìm thấy sản phẩm thì Read, Update, Delete đều trả 404.
Ví dụ
// ProductsController.java
package com.shop.api;
import jakarta.validation.*;
import java.net.URI;
import java.util.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.*;
@RestController
@RequestMapping("/api/products")
class ProductsController {
private final List<Product> products =
new ArrayList<>();
private int nextId = 1;
@GetMapping
public List<Product> getAll() {
return products;
}
@GetMapping("/{id}")
public ResponseEntity<Product> getById(
@PathVariable int id) {
var product = find(id);
if (product == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(product);
}
@PostMapping
public ResponseEntity<Product> create(
@Valid @RequestBody ProductRequest req) {
var product = new Product();
product.setId(nextId++);
product.setName(req.name());
product.setPrice(req.price());
products.add(product);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(product.getId())
.toUri();
return ResponseEntity.created(location)
.body(product);
}
@PutMapping("/{id}")
public ResponseEntity<Void> update(
@PathVariable int id,
@Valid @RequestBody ProductRequest req) {
var product = find(id);
if (product == null) {
return ResponseEntity.notFound().build();
}
product.setName(req.name());
product.setPrice(req.price());
return ResponseEntity.noContent().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(
@PathVariable int id) {
var product = find(id);
if (product == null) {
return ResponseEntity.notFound().build();
}
products.remove(product);
return ResponseEntity.noContent().build();
}
private Product find(int id) {
return products.stream()
.filter(p -> p.getId() == id)
.findFirst()
.orElse(null);
}
}
// Product.java
package com.shop.api;
class Product {
private int id;
private String name = "";
private long price;
public int getId() { return id; }
public String getName() { return name; }
public long getPrice() { return price; }
public void setId(int id) { this.id = id; }
public void setName(String name) {
this.name = name;
}
public void setPrice(long price) {
this.price = price;
}
}
// ProductRequest.java
package com.shop.api;
import jakarta.validation.constraints.*;
record ProductRequest(
@NotBlank String name,
@Positive long price) {
}- Tạo và sửa dùng chung
ProductRequest, có validation sẵn. ResponseEntity<Void>dùng cho method không trả dữ liệu, chỉ trả status code.idcủa sản phẩm cần sửa lấy từ URL, không lấy từ body.findgom phần tìm kiếm vào một chỗ cho cả ba method dùng.- Để gọn, ví dụ trả thẳng
Product. Dự án thật nên trả qua DTO response như bài DTO.
Thử ngay
Giữ các file cũ, thay ProductsController.java, Product.java và thêm
ProductRequest.java theo phần Ví dụ, rồi chạy lại server. Tạo file
product.json có nội dung { "name": "Pen", "price": 5000 }, rồi gọi lần
lượt:
curl -i -X POST http://localhost:5000/api/products -H "Content-Type: application/json" -d @product.json
curl -i -X DELETE http://localhost:5000/api/products/1
curl -i -X DELETE http://localhost:5000/api/products/1
curl -i http://localhost:5000/api/products/1Đoán trước khi chạy: xoá sản phẩm số 1 hai lần. Lần xoá thứ hai trả về status code nào?
Xem kết quả
POST → 201
DELETE lần 1 → 204
DELETE lần 2 → 404
GET → 404Lần xoá đầu thành công. Tới lần thứ hai thì sản phẩm đã không còn, nên
delete trả 404. Tắt server rồi chạy lại thì mọi dữ liệu cũng mất, vì chúng
chỉ nằm trong bộ nhớ.
Lỗi hay gặp
Lấy id từ body khi sửa. URL nói sửa sản phẩm số 1, body lại ghi số 2.
API không biết tin bên nào.
// SAI — id nằm cả ở URL lẫn trong body
// UpdateRequest.java
package com.shop.api;
record UpdateRequest(int id, String name) {
}Chỉ lấy id từ URL (@PutMapping("/{id}")). DTO dùng để sửa không có id.
// ĐÚNG — id chỉ nằm ở URL
// UpdateRequest.java
package com.shop.api;
record UpdateRequest(String name) {
}Sửa hay xoá mà không kiểm tra tồn tại. Không tìm thấy vẫn trả 204, client tưởng đã thành công.
Tóm tắt
- CRUD: POST tạo, GET đọc, PUT sửa, DELETE xoá.
- Tạo trả 201, sửa và xoá trả 204, không tìm thấy trả 404.
idlấy từ URL, dữ liệu sửa lấy từ body qua DTO.- Dữ liệu trong bộ nhớ mất khi tắt server, chương 4 sẽ lưu vào database.
Tự kiểm tra
0/3 câuPUT /api/products/5 với body hợp lệ, nhưng không có sản phẩm số 5. Nên trả gì?
Method delete chỉ cần báo thành công, không trả dữ liệu. Kiểu trả về nào hợp lý?
Tạo 3 sản phẩm rồi tắt server và chạy lại. Với cách lưu trong bài, GET /api/products/1 trả gì?