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 6/19
5 phút

DTO

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

Class Product trong hệ thống có cả giá nhập hàng, là thông tin nội bộ. Nếu method trả thẳng Product ra ngoài thì khách hàng thấy luôn giá nhập. Nếu method nhận thẳng Product từ body thì client tự đặt được id. DTO tách phần dữ liệu đi qua API ra khỏi class dùng bên trong.

Khái niệm

📦 DTO (Data Transfer Object): class chỉ chứa dữ liệu đi vào hoặc đi ra qua API, tách riêng khỏi class dùng bên trong hệ thống.

📝 Record: kiểu class chỉ chứa dữ liệu không đổi, Java tự sinh constructor, method đọc từng trường, equals và toString.

record ProductResponse(int id, String name, long price) có sẵn constructor ba tham số và ba method đọc id(), name(), price(). Record hợp với DTO vì DTO chỉ chở dữ liệu. Thường mỗi tài nguyên có hai loại DTO:

DTO Hướng Chứa gì
ProductResponse server → client những gì client được xem
CreateProductRequest client → server những gì client được phép gửi

Ví dụ

// ProductsController.java
package com.shop.api;

import java.net.URI;
import java.util.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/products")
class ProductsController {
    private final List<Product> products =
            new ArrayList<>();

    @PostMapping
    public ResponseEntity<ProductResponse> create(
            @RequestBody
            CreateProductRequest request) {
        var product = new Product();
        product.setId(products.size() + 1);
        product.setName(request.name());
        product.setPrice(request.price());
        product.setCostPrice(
                request.price() * 70 / 100);
        products.add(product);
        URI location = URI.create(
                "/api/products/" + product.getId());
        return ResponseEntity.created(location)
                .body(toResponse(product));
    }

    private ProductResponse toResponse(Product p) {
        return new ProductResponse(
                p.getId(), p.getName(), p.getPrice());
    }
}

// Product.java
package com.shop.api;

class Product {
    private int id;
    private String name = "";
    private long price;
    private long costPrice;

    public int getId() { return id; }
    public String getName() { return name; }
    public long getPrice() { return price; }
    public long getCostPrice() { return costPrice; }

    public void setId(int id) { this.id = id; }

    public void setName(String name) {
        this.name = name;
    }

    public void setPrice(long price) {
        this.price = price;
    }

    public void setCostPrice(long costPrice) {
        this.costPrice = costPrice;
    }
}

// CreateProductRequest.java
package com.shop.api;

record CreateProductRequest(String name, long price) {
}

// ProductResponse.java
package com.shop.api;

record ProductResponse(
        int id, String name, long price) {
}
  • Product là class bên trong, có costPrice là giá nhập.
  • CreateProductRequest không có id hay costPrice, nên client không gửi được hai giá trị đó.
  • ProductResponse không có costPrice, nên giá nhập không bao giờ lộ ra.
  • toResponse chép dữ liệu từ Product sang DTO. Viết tay như vậy là đủ cho hầu hết dự án.
  • URI.create(...) dựng thẳng URL cho header Location, dùng khi chưa có method đọc riêng để trỏ tới.
toResponse JSON client gửi CreateProductRequest: name, price Product: thêm id, costPrice ProductResponse: id, name, price JSON trả về client
Dữ liệu đi qua ba class trong method create

Thử ngay

Giữ các file cũ, thay ProductsController.java, Product.java và thêm CreateProductRequest.java, ProductResponse.java theo phần Ví dụ, rồi chạy lại server. Tạo file product.json:

{ "name": "Pen", "price": 5000, "costPrice": 1, "id": 999 }

Rồi gửi:

curl -i -X POST http://localhost:5000/api/products -H "Content-Type: application/json" -d @product.json

Đoán trước khi chạy: client cố gửi id là 999 và costPrice là 1. Response trả về id bằng bao nhiêu, và có costPrice không?

Xem kết quả
HTTP/1.1 201
Location: /api/products/1
{"id":1,"name":"Pen","price":5000}

id là 1, do server tự cấp. CreateProductRequest không có id và costPrice, nên hai giá trị client gửi bị bỏ qua. Response cũng không có costPrice.

Lỗi hay gặp

Trả thẳng class bên trong ra ngoài. Mọi trường có getter đều lộ ra, kể cả giá nhập.

// SAI — response có luôn costPrice
// ItemsController.java
package com.shop.api;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/items")
class ItemsController {
    @GetMapping("/{id}")
    public Product getById(@PathVariable int id) {
        var product = new Product();
        product.setId(id);
        product.setCostPrice(3500);
        return product;
    }
}
// ĐÚNG — trả qua DTO, không có costPrice
// ItemsController.java
package com.shop.api;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/items")
class ItemsController {
    @GetMapping("/{id}")
    public ProductResponse getById(
            @PathVariable int id) {
        return new ProductResponse(id, "Pen", 5000);
    }
}

Nhận thẳng class bên trong từ body. Client gửi thêm id hay costPrice là gán được luôn qua setter. Luôn nhận qua DTO chỉ chứa những gì client được phép gửi, như CreateProductRequest ở trên.

Tóm tắt

  • DTO là class chỉ chứa dữ liệu đi qua API, tách khỏi class bên trong.
  • DTO response quyết định client được xem gì.
  • DTO request quyết định client được gửi gì, chặn việc tự đặt id hay giá nhập.
  • Record khai báo DTO trên một dòng. Chép dữ liệu sang DTO bằng một method nhỏ.

Tự kiểm tra

0/3 câu
Câu 1

Class User có passwordHash. Method xử lý GET /api/users/1 nên trả về kiểu gì?

Câu 2

Record CreateOrderRequest không có trường status. Client gửi JSON kèm "status": "PAID". Chuyện gì xảy ra?

Câu 3

Vì sao DTO request để tạo sản phẩm không nên có id?

Trả về kết quảValidation

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