@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:
@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) |
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 Java | Cột trong Database | Quy tắc chuyển đổi |
|---|---|---|
firstName | first_name | Thêm dấu gạch dưới trước chữ hoa |
createdAt | created_at | Áp dụng cho các trường thời gian |
projectCode | project_code | Chữ hoa liên tiếp được chuyển thành chữ thường |
isActive | is_active | Giữ 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:
public enum ProjectStatus {
PLANNING, ACTIVE, DONE
}
// 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):
- Một ngày đẹp trời, đồng nghiệp chèn thêm giá trị
REVIEWvào giữa:PLANNING (0),REVIEW (1),ACTIVE (2),DONE (3). - 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ànhREVIEW. - 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).

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
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:
@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:
@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 ID | Không có khóa chính riêng | Bắt buộc có @Id riêng |
| Bảng lưu trữ | Lưu inline ngay trong bảng của Entity cha | Lưu trên một bảng database riêng biệt |
| Hiệu năng truy vấn | Nhanh, không cần câu lệnh SQL JOIN | Cần thực hiện JOIN hoặc phát thêm truy vấn |
| Vòng đời | Phụ thuộc hoàn toàn vào Entity cha | Có thể tồn tại độc lập |
Những cạm bẫy phổ biến cần tránh là gì?
- Quên tham số EnumType.STRING: Khi chỉ viết
@Enumerated, Hibernate mặc định áp dụngEnumType.ORDINAL. - 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
@Entityvới quan hệ@ManyToOne. - 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
@AttributeOverridessẽ gây lỗi trùng tên cột khi migrate database.
Tự kiểm tra kiến thức
- Q1Tại sao nên hạn chế tối đa việc sử dụng EnumType.ORDINAL khi ánh xạ Enum trong JPA?
- Q2Cơ chế SpringPhysicalNamingStrategy mặc định của Spring Boot xử lý thuộc tính Java như thế nào?
- 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?
- Q4Khi 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?
- Q5Thuộ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.
@Entity và @Id: POJO Contract và Chiến Lược Khóa Chính
Bài tiếpEntity Lifecycle và equals/hashCode Trong JPA
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