VieTopik
Tiếng HànTiếng AnhIT
  • Góc học tập
Tải app
  • Thư viện
  • Luyện thi
  • Cẩm nang
  • Góc học tập
Spring Boot Web APIXây API CRUD
Bài 8/19
6 phút

CRUD hoàn chỉnh

Nội dung bài · 5 mục
  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 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.
  • id của sản phẩm cần sửa lấy từ URL, không lấy từ body.
  • find gom 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             → 404

Lầ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.
  • id lấ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âu
Câu 1

PUT /api/products/5 với body hợp lệ, nhưng không có sản phẩm số 5. Nên trả gì?

Câu 2

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ý?

Câu 3

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ì?

ValidationDependency injection trong Spring

Nội dung bài

  1. 1.Khái niệm
  2. 2.Ví dụ
  3. 3.Thử ngay
  4. 4.Lỗi hay gặp
  5. 5.Tóm tắt