Entity = object map với bảng DB, do JPA quản lý. DTO (Data Transfer Object) = object thuần chở data qua boundary (API response/request), không dính persistence.
Vì sao không trả entity thẳng ra controller:
1. Lộ cấu trúc DB — đổi schema là vỡ API contract; field nhạy cảm (passwordHash, cột nội bộ) dễ lộ ra JSON.
2. LazyInitializationException / N+1 — Jackson serialize chạm vào lazy field khi transaction đã đóng.
3. Vòng lặp quan hệ — quan hệ 2 chiều (Order ↔ Customer) làm JSON đệ quy vô hạn (phải vá bằng @JsonIgnore — dấu hiệu sai tầng).
4. Over-posting khi nhận request — bind body thẳng vào entity cho phép client set cả field không được phép (role, id).
Mapping: thủ công qua record + factory method (UserDto.from(user)) cho case đơn giản; MapStruct (sinh code lúc compile, type-safe — khuyến nghị) khi nhiều; interface projection của Spring Data cho read-only. ModelMapper dùng reflection, chậm — hạn chế.
An Entity = an object mapped to a DB table, managed by JPA. A DTO (Data Transfer Object) = a plain object carrying data across a boundary (API response/request), free of persistence concerns.
Why not return entities straight from controllers:
1. Leaks the DB structure — schema changes break the API contract; sensitive fields (passwordHash, internal columns) slip into JSON.
2. LazyInitializationException / N+1 — Jackson touches lazy fields during serialisation after the transaction has closed.
3. Relationship cycles — bidirectional relations (Order ↔ Customer) make JSON recurse infinitely (patched with @JsonIgnore — a layer-violation smell).
4. Over-posting on input — binding the request body straight to an entity lets clients set fields they should not (role, id).
Mapping: manual via records + factory methods (UserDto.from(user)) for simple cases; MapStruct (compile-time generated, type-safe — recommended) at scale; Spring Data interface projections for read-only. ModelMapper is reflection-based and slow — avoid.