Spring REST API & Data JPA
26/46
Bài 26 / 46~12 phútJPA FundamentalsLộ trình · chặng 16/21Miễn phí… lượt xem

@Column, @Enumerated và @Embeddable: Ánh Xạ Thuộc Tính

Làm chủ tinh chỉnh @Column, quy tắc đặt tên snake_case, cạm bẫy EnumType.ORDINAL và kỹ thuật gom nhóm value object inline với @Embeddable.

TL;DR: @Column tinh chỉnh metadata cột DB (nullable, unique, length). Cơ chế SpringPhysicalNamingStrategy tự động chuyển đổi camelCase sang snake_case. @Enumerated bắt buộc dùng EnumType.STRING vì ORDINAL dễ gây data corruption khi thay đổi thứ tự enum. @Embeddable gom nhóm các thuộc tính liên quan (như Money hay Address) thành value object trong Java và lưu inline trên cùng một bảng mà không cần quan hệ JOIN.

Annotation @Column tinh chỉnh Database Column như thế nào?

@Column không bắt buộc với mọi thuộc tính vì Hibernate tự suy diễn cấu hình mặc định. Annotation này dùng để tùy biến và ghi đè metadata:

Java
@Column(
    name        = "project_name",   // Ten cot DB (mac dinh: theo naming strategy)
    nullable    = false,            // Rang buoc NOT NULL
    unique      = true,             // Rang buoc UNIQUE
    length      = 100,              // VARCHAR(100) - chi ap dung cho String
    insertable  = true,             // Co mat trong cau lenh INSERT
    updatable   = false             // Khong cho phep UPDATE (vd: created_at)
)
private String name;

Các trường hợp thực tế thường gặp

Yêu cầu thực tếCấu hình @Column
Bắt buộc nhập@Column(nullable = false)
Ràng buộc duy nhất@Column(unique = true)
Giới hạn ký tự ngắn@Column(length = 50)
Nội dung văn bản dài@Column(columnDefinition = "TEXT")
Ngày tạo cố định@Column(updatable = false)
Số tiền / tỷ giá@Column(precision = 19, scale = 4) (BigDecimal)
Quy tắc sạch mã

Không khai báo @Column cho toàn bộ các field. Hãy để SpringPhysicalNamingStrategy tự động làm việc và chỉ can thiệp khi cần ràng buộc cụ thể.

Spring Boot chuyển đổi CamelCase sang Snake_case như thế nào?

Mặc định, Spring Boot kích hoạt SpringPhysicalNamingStrategy để ánh xạ giữa thuộc tính Java và cột cơ sở dữ liệu:

Thuộc tính JavaCột trong DatabaseQuy tắc chuyển đổi
firstNamefirst_nameThêm dấu gạch dưới trước chữ hoa
createdAtcreated_atÁp dụng cho các trường thời gian
projectCodeproject_codeChữ hoa liên tiếp được chuyển thành chữ thường
isActiveis_activeGiữ nguyên tiền tố boolean

Nhờ cơ chế này, bạn không cần viết @Column(name = "created_at") cho thuộc tính createdAt.

Tại sao bắt buộc dùng EnumType.STRING thay vì ORDINAL?

JPA hỗ trợ hai chiến lược lưu trữ Enum vào cơ sở dữ liệu:

Java
public enum ProjectStatus {
    PLANNING, ACTIVE, DONE
}
Java
// KHUYEN CAO: Luon dung STRING
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private ProjectStatus status;

Hiểm họa dữ liệu từ EnumType.ORDINAL

ORDINAL lưu vị trí số của enum trong code (0 cho PLANNING, 1 cho ACTIVE, 2 cho DONE):

  1. Một ngày đẹp trời, đồng nghiệp chèn thêm giá trị REVIEW vào giữa: PLANNING (0), REVIEW (1), ACTIVE (2), DONE (3).
  2. Toàn bộ các dòng cũ trong DB đang lưu số 1 (trước đây là ACTIVE) nay được Java đọc thành REVIEW.
  3. Không hề có exception: Ứng dụng vẫn chạy, database không báo lỗi, nhưng nghiệp vụ bị sai lệch âm thầm (data corruption thầm lặng).

Con số trong DB không đổi nhưng bảng tra thay đổi: số 1 từng là ACTIVE nay tra ra INTERNAL_REVIEW khiến mọi dòng cũ đổi nghĩa âm thầm

EnumType.STRING lưu trực tiếp chuỗi ký tự 'ACTIVE'. Cho dù bạn có đổi thứ tự trong enum Java, dữ liệu trong database vẫn hoàn toàn an toàn và nhất quán.

Nhóm thuộc tính thành Value Object với @Embeddable ra sao?

Trong thiết kế hướng đối tượng và Domain-Driven Design (DDD), một số thuộc tính luôn đi liền với nhau thành một Value Object nhưng không cần định danh ID riêng (ví dụ: Money gồm số tiền và đơn vị tiền tệ, hoặc Address gồm đường, phường, thành phố).

@Embeddable giải quyết bài toán này: gom nhóm dữ liệu trong Java nhưng lưu inline trên cùng một bảng database, không sinh bảng mới và không tốn chi phí câu lệnh JOIN.

Embeddable mở phẳng thành cột trong bảng chủ giúp đọc một Order chỉ với 1 SELECT không cần JOIN, khác với Entity riêng cần bảng phụ và khóa ngoại

Java
@Embeddable
public class Money {
    @Column(name = "amount", precision = 19, scale = 4)
    private BigDecimal amount;

    @Column(name = "currency", length = 3)
    private String currency;

    protected Money() {} // JPA require
    public Money(BigDecimal amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }
    // Getters...
}

Nhúng vào Entity cha bằng @Embedded:

Java
@Entity
@Table(name = "invoices")
public class Invoice {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Embedded
    private Money total; // Map thang vao cot amount va currency tren bang invoices
}

Tái sử dụng với @AttributeOverrides

Nếu một Entity cần nhúng nhiều đối tượng Money (ví dụ subtotal và tax), tên cột sẽ bị xung đột. Ta dùng @AttributeOverrides để định danh lại:

Java
@Embedded
@AttributeOverrides({
    @AttributeOverride(name = "amount", column = @Column(name = "tax_amount")),
    @AttributeOverride(name = "currency", column = @Column(name = "tax_currency"))
})
private Money tax;

Bảng so sánh @Embeddable Value Object và @Entity có quan hệ

Đặc điểm@Embeddable (Value Object)@Entity (Associated Entity)
Định danh IDKhông có khóa chính riêngBắt buộc có @Id riêng
Bảng lưu trữLưu inline ngay trong bảng của Entity chaLưu trên một bảng database riêng biệt
Hiệu năng truy vấnNhanh, không cần câu lệnh SQL JOINCần thực hiện JOIN hoặc phát thêm truy vấn
Vòng đờiPhụ thuộc hoàn toàn vào Entity chaCó thể tồn tại độc lập

Những cạm bẫy phổ biến cần tránh là gì?

  1. Quên tham số EnumType.STRING: Khi chỉ viết @Enumerated, Hibernate mặc định áp dụng EnumType.ORDINAL.
  2. Dùng @Embeddable cho đối tượng có Lifecycle riêng: Nếu một đối tượng cần quản lý ID, cần CRUD độc lập hoặc chia sẻ qua lại giữa nhiều Entity, hãy dùng @Entity với quan hệ @ManyToOne.
  3. Quên override tên cột khi nhúng trùng lặp: Nhúng 2 value object cùng loại vào 1 entity mà không dùng @AttributeOverrides sẽ gây lỗi trùng tên cột khi migrate database.

Tự kiểm tra kiến thức

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tại sao nên hạn chế tối đa việc sử dụng EnumType.ORDINAL khi ánh xạ Enum trong JPA?
  2. Q2
    Cơ chế SpringPhysicalNamingStrategy mặc định của Spring Boot xử lý thuộc tính Java như thế nào?
  3. Q3
    Đối tượng được đánh dấu bằng @Embeddable mang đặc tính nào sau đây trong cơ sở dữ liệu?
  4. Q4
    Khi nhúng hai đối tượng cùng kiểu @Embeddable vào một Entity cha, làm cách nào để tránh lỗi trùng tên cột?
  5. Q5
    Thuộc tính length trong annotation @Column(length = 50) có tác dụng đối với những kiểu dữ liệu nào?

Bài tiếp theo: Lifecycle Callbacks & Hợp Đồng equals/hashCode

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Lộ trình Junior Java Backend Developer

Chặng 16/21 · Spring Data JPA — bài 5/20 của chặng

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Entity Lifecycle và equals/hashCode Trong JPA