ORM là gì? (Object-Relational Mapping)
Trong quá trình phát triển Backend, ứng dụng thường xuyên cần tương tác với database để lưu trữ, truy vấn, cập nhật và xóa dữ liệu. Một cách phổ biến để thực hiện những thao tác này là viết SQL trực tiếp. Tuy nhiên, khi dự án phát triển, việc viết và quản lý quá nhiều câu SQL có thể làm tăng lượng code cần duy trì.
Để giải quyết vấn đề này, lập trình viên có thể sử dụng ORM (Object-Relational Mapping) — kỹ thuật ánh xạ giữa các đối tượng trong chương trình và dữ liệu trong cơ sở dữ liệu quan hệ.
ORM cung cấp một lớp trừu tượng giúp lập trình viên thao tác với dữ liệu thông qua các đối tượng và phương thức trong code, thay vì phải tự viết SQL cho mọi thao tác. Tuy nhiên, ORM không loại bỏ SQL mà thường chuyển các thao tác trong code thành câu lệnh SQL để database thực thi.
1. ORM là gì?
ORM (Object-Relational Mapping) là kỹ thuật ánh xạ giữa mô hình đối tượng trong chương trình và mô hình dữ liệu quan hệ trong database.
Trong chương trình, dữ liệu thường được biểu diễn bằng các cấu trúc như class, object hoặc struct. Trong database quan hệ, dữ liệu được lưu trữ dưới dạng bảng, hàng và cột.
ORM giúp kết nối hai cách biểu diễn này. Nó hỗ trợ ánh xạ dữ liệu từ bảng database thành đối tượng trong code và ngược lại, đồng thời cung cấp các phương thức để thực hiện những thao tác truy vấn hoặc thay đổi dữ liệu.
Ví dụ, database có bảng users:
| id | name | |
|---|---|---|
| 1 | An | [email protected] |
| 2 | Bình | [email protected] |
Trong Go, dữ liệu tương ứng có thể được biểu diễn bằng một struct:
type User struct {
ID uint
Name string
Email string
}
Struct User mô tả dữ liệu người dùng trong chương trình, còn bảng users lưu trữ dữ liệu tương ứng trong PostgreSQL.
Nếu sử dụng ORM như GORM, lập trình viên có thể truy vấn dữ liệu và nhận kết quả trực tiếp vào các biến kiểu User mà không cần tự viết toàn bộ câu SQL cho thao tác đó.
Một số ORM phổ biến gồm:
- GORM: ORM dành cho Go.
- Entity Framework Core: ORM dành cho hệ sinh thái .NET.
- Hibernate: ORM phổ biến trong Java.
- SQLAlchemy: Thư viện SQL và ORM trong Python.
2. ORM hoạt động như thế nào?
ORM đóng vai trò trung gian giữa code Backend và database. Khi chương trình yêu cầu một thao tác trên dữ liệu, ORM sử dụng thông tin ánh xạ để tạo hoặc thực hiện truy vấn phù hợp, sau đó chuyển kết quả trả về thành các đối tượng trong chương trình.
Quá trình tổng quát có thể mô tả như sau:
Backend code → ORM → SQL → Database → Kết quả → Đối tượng trong code
Bước 1: Định nghĩa model
Lập trình viên định nghĩa model hoặc struct để biểu diễn dữ liệu cần sử dụng.
Ví dụ:
type User struct {
ID uint
Name string
Email string
}
Model giúp chương trình xác định các trường dữ liệu cần làm việc. Cách ánh xạ tên struct, tên bảng và tên cột cụ thể phụ thuộc vào ORM và cấu hình.
Bước 2: Thực hiện thao tác thông qua ORM
Giả sử ứng dụng cần lấy người dùng có ID bằng 1. Với GORM, có thể viết:
var user User
result := db.First(&user, 1)
if result.Error != nil {
// Xử lý lỗi
}
Ở đây, db là đối tượng kết nối database đã được khởi tạo trước đó.
Thay vì tự viết SQL, lập trình viên gọi phương thức First của GORM để tìm bản ghi phù hợp và ánh xạ kết quả vào biến user.
Bước 3: ORM thực hiện truy vấn SQL
Với ví dụ trên, GORM có thể tạo truy vấn tương đương:
SELECT *
FROM users
WHERE id = 1
ORDER BY id
LIMIT 1;
Câu SQL thực tế có thể khác tùy phiên bản GORM, cách định nghĩa model và cấu hình. ORM chịu trách nhiệm tạo truy vấn theo quy tắc ánh xạ và hệ quản trị cơ sở dữ liệu được sử dụng.
Bước 4: Database thực thi truy vấn
PostgreSQL tiếp nhận câu SQL, kiểm tra và thực thi truy vấn, sau đó trả về dữ liệu phù hợp nếu có bản ghi khớp.
Bước 5: ORM ánh xạ kết quả trở lại code
ORM đọc kết quả từ database và gán các giá trị tương ứng vào struct User.
Sau đó, chương trình có thể sử dụng dữ liệu như những trường thông thường:
fmt.Println(user.Name)
fmt.Println(user.Email)
Như vậy, ORM giúp lập trình viên làm việc với dữ liệu theo cách gần với mô hình đối tượng trong code hơn, trong khi database vẫn thực hiện việc lưu trữ và truy vấn dữ liệu.
3. Các thao tác CRUD với ORM
CRUD là viết tắt của bốn thao tác cơ bản khi làm việc với dữ liệu:
- Create: Tạo hoặc thêm dữ liệu.
- Read: Đọc hoặc truy vấn dữ liệu.
- Update: Cập nhật dữ liệu.
- Delete: Xóa dữ liệu.
Với Go và GORM, các thao tác này có thể được thực hiện như sau.
3.1. Create — Thêm dữ liệu
Để tạo một người dùng mới:
user := User{
Name: "An",
Email: "[email protected]",
}
result := db.Create(&user)
if result.Error != nil {
// Xử lý lỗi
}
GORM tạo truy vấn INSERT tương ứng và gửi đến database. Nếu thao tác thành công, giá trị của các trường được database tạo ra, chẳng hạn ID tự tăng, có thể được cập nhật vào biến user tùy cấu hình và cơ chế của database.
SQL tương đương có thể là:
INSERT INTO users (name, email)
VALUES ('An', '[email protected]');
3.2. Read — Truy vấn dữ liệu
Để lấy người dùng có ID bằng 1:
var user User
result := db.First(&user, 1)
if result.Error != nil {
// Xử lý lỗi, bao gồm trường hợp không tìm thấy
}
Để lấy danh sách người dùng:
var users []User
result := db.Find(&users)
if result.Error != nil {
// Xử lý lỗi
}
GORM thực hiện truy vấn và ánh xạ các bản ghi tìm được vào biến user hoặc slice users.
3.3. Update — Cập nhật dữ liệu
Để cập nhật tên người dùng có ID bằng 1:
result := db.Model(&User{}).
Where("id = ?", 1).
Update("name", "An Nguyen")
if result.Error != nil {
// Xử lý lỗi
}
GORM tạo truy vấn UPDATE để thay đổi giá trị cột name của bản ghi thỏa mãn điều kiện.
Ví dụ SQL tương đương:
UPDATE users
SET name = 'An Nguyen'
WHERE id = 1;
Sử dụng tham số như Where("id = ?", 1) giúp thư viện truyền giá trị theo cơ chế tham số hóa phù hợp, thay vì ghép trực tiếp dữ liệu đầu vào vào chuỗi SQL.
3.4. Delete — Xóa dữ liệu
Để xóa người dùng có ID bằng 1:
result := db.Delete(&User{}, 1)
if result.Error != nil {
// Xử lý lỗi
}
Với model thông thường không sử dụng cơ chế soft delete, GORM có thể tạo truy vấn tương đương:
DELETE FROM users
WHERE id = 1;
Nếu model có trường DeletedAt và sử dụng soft delete của GORM, thao tác xóa thường chỉ đánh dấu bản ghi là đã xóa thay vì xóa vật lý khỏi database.
Các ví dụ trên minh họa cách ORM cung cấp phương thức để thực hiện CRUD. Trong ứng dụng thực tế, lập trình viên vẫn cần kiểm tra lỗi, xác nhận dữ liệu đầu vào, xử lý trường hợp không tìm thấy bản ghi và bảo đảm người dùng có quyền thực hiện thao tác.
4. ORM và SQL trực tiếp khác nhau thế nào?
ORM và SQL trực tiếp đều có thể được sử dụng để tương tác với database, nhưng khác nhau về cách lập trình viên mô tả thao tác và mức độ kiểm soát truy vấn.
| Tiêu chí | ORM | SQL trực tiếp |
|---|---|---|
| Cách viết | Sử dụng model, đối tượng và phương thức | Viết câu lệnh SQL |
| Lượng code lặp | Có thể giảm đối với CRUD thông thường | Có thể cần thêm code để thực thi và ánh xạ kết quả |
| Kiểm soát truy vấn | Phụ thuộc ORM và cách sử dụng | Chủ động kiểm soát cấu trúc SQL |
| Bảo trì | Thuận tiện khi thao tác nhất quán theo model | Thuận tiện khi cần thể hiện rõ logic truy vấn |
| Tối ưu hiệu năng | Cần kiểm tra SQL mà ORM tạo ra | Có thể thiết kế và tinh chỉnh SQL trực tiếp |
| Truy vấn phức tạp | Có thể khó biểu diễn bằng API của ORM | Có thể phù hợp hơn với truy vấn SQL đặc thù |
Ví dụ, cùng một yêu cầu lấy người dùng theo ID:
Viết SQL trực tiếp:
SELECT id, name, email
FROM users
WHERE id = $1;
Trong Go, bạn có thể dùng database/sql để thực thi truy vấn và ánh xạ kết quả vào biến:
var user User
err := db.QueryRow(
`SELECT id, name, email FROM users WHERE id = $1`,
1,
).Scan(&user.ID, &user.Name, &user.Email)
if err != nil {
// Xử lý lỗi
}
Sử dụng GORM:
var user User
result := db.First(&user, 1)
if result.Error != nil {
// Xử lý lỗi
}
Ví dụ này cho thấy ORM có thể giảm lượng code cần viết cho một thao tác thông thường. Tuy nhiên, SQL trực tiếp cũng không nhất thiết dài hoặc khó bảo trì nếu được tổ chức tốt.
Trong thực tế, hai cách tiếp cận có thể được kết hợp. Lập trình viên có thể sử dụng ORM cho CRUD thông thường và viết SQL trực tiếp cho những truy vấn cần kiểm soát đặc biệt.
5. Ưu điểm và hạn chế của ORM
5.1. Ưu điểm
Giảm code lặp
ORM cung cấp các phương thức phổ biến để thêm, truy vấn, cập nhật và xóa dữ liệu, giúp giảm việc lặp lại những đoạn code thực thi SQL và ánh xạ kết quả.
Hỗ trợ ánh xạ dữ liệu
ORM giúp chuyển đổi giữa bản ghi trong database và đối tượng trong code. Điều này tạo thuận lợi khi làm việc với model và các quan hệ dữ liệu.
Tăng năng suất phát triển
Đối với những thao tác thông thường, lập trình viên có thể viết ít code hơn và tập trung vào logic nghiệp vụ của ứng dụng.
Hỗ trợ nhiều tính năng liên quan đến database
Tùy thư viện, ORM có thể hỗ trợ quan hệ giữa các model, transaction, điều kiện truy vấn, preload dữ liệu và tích hợp với các công cụ quản lý schema.
5.2. Hạn chế
Có thể tạo truy vấn không tối ưu
ORM không tự động hiểu mọi yêu cầu nghiệp vụ theo cách tối ưu nhất. Cách sử dụng không phù hợp có thể tạo truy vấn dư thừa, lấy quá nhiều dữ liệu hoặc thực hiện nhiều thao tác không cần thiết.
Có thể gặp N+1 Query Problem
Khi lấy một danh sách bản ghi rồi truy cập dữ liệu liên quan riêng lẻ cho từng bản ghi, ORM có thể phát sinh nhiều truy vấn bổ sung.
Ví dụ, lấy 100 đơn hàng rồi truy vấn riêng thông tin khách hàng của từng đơn hàng có thể dẫn đến tổng cộng 101 truy vấn. Tùy ORM và cách tải quan hệ, vấn đề này có thể được hạn chế bằng preload, eager loading, batch loading hoặc truy vấn JOIN phù hợp.
Không thay thế kiến thức SQL
Nếu không hiểu SQL, lập trình viên sẽ khó đánh giá câu truy vấn được sinh ra, xác định nguyên nhân truy vấn chậm hoặc lựa chọn cách tối ưu phù hợp.
Có thể che khuất chi phí thực thi
Một dòng code ORM ngắn không có nghĩa database chỉ thực hiện một thao tác đơn giản. Để hiểu hiệu năng thực tế, cần quan sát SQL được thực thi và đo lường khi cần thiết.
Do đó, ORM là công cụ hỗ trợ lập trình chứ không phải cơ chế tự động tối ưu mọi truy vấn.
6. ORM trong dự án Backend thực tế
Trong một dự án Backend, ORM thường nằm ở lớp truy cập dữ liệu hoặc được sử dụng bên trong lớp chịu trách nhiệm tương tác với database.
Ví dụ, một ứng dụng quản lý sản phẩm có thể được tổ chức theo luồng:
Client → API → Logic nghiệp vụ → Lớp truy cập dữ liệu → ORM/SQL → Database
Mỗi thành phần có nhiệm vụ khác nhau:
- API: Tiếp nhận request và trả response cho client.
- Logic nghiệp vụ: Xử lý các quy tắc của ứng dụng, chẳng hạn kiểm tra số lượng hàng tồn kho.
- Lớp truy cập dữ liệu: Tổ chức các thao tác đọc và ghi dữ liệu.
- ORM: Hỗ trợ ánh xạ model và thực hiện các truy vấn database.
- Database: Lưu trữ dữ liệu, thực thi truy vấn và bảo đảm các ràng buộc được cấu hình.
Đây là một cách tổ chức phổ biến, không phải kiến trúc bắt buộc cho mọi dự án. Với ứng dụng nhỏ, một số thành phần có thể được gộp lại.
ORM phối hợp với Transaction như thế nào?
Giả sử khách hàng đặt hàng, hệ thống cần tạo đơn hàng và giảm số lượng hàng tồn kho. Nếu chỉ tạo đơn hàng thành công nhưng cập nhật tồn kho thất bại, dữ liệu có thể rơi vào trạng thái không nhất quán.
Transaction cho phép nhóm các thao tác database thành một đơn vị công việc. ORM có thể cung cấp API để bắt đầu transaction, thực hiện các truy vấn trong transaction và commit hoặc rollback khi cần.
Tuy nhiên, transaction không tự bảo đảm mọi quy tắc nghiệp vụ đều đúng. Lập trình viên vẫn cần thiết kế các bước xử lý và cơ chế kiểm soát phù hợp.
ORM phối hợp với Migration như thế nào?
ORM và Migration giải quyết hai vấn đề khác nhau:
- ORM: Hỗ trợ ứng dụng thao tác với dữ liệu thông qua model và các phương thức trong code.
- Migration: Quản lý những thay đổi đối với schema hoặc dữ liệu database theo từng phiên bản.
Ví dụ, khi bổ sung trường price cho model Product, bạn có thể cần tạo migration để bổ sung cột price trong bảng products.
Nếu chỉ cập nhật model mà không cập nhật schema tương ứng, ứng dụng có thể gặp lỗi khi truy vấn hoặc ghi dữ liệu. Vì vậy, model trong code và cấu trúc database cần được quản lý nhất quán.
7. Kết luận
ORM (Object-Relational Mapping) là kỹ thuật ánh xạ giữa các đối tượng trong chương trình và dữ liệu trong database quan hệ. ORM giúp lập trình viên thao tác với dữ liệu thông qua các model và phương thức trong code, đồng thời giảm lượng code lặp khi thực hiện những thao tác database thông thường.
Tuy nhiên, ORM không loại bỏ SQL và cũng không tự động bảo đảm hiệu năng tối ưu. Những vấn đề như N+1 Query Problem, truy vấn dư thừa hoặc xử lý quan hệ dữ liệu không phù hợp vẫn có thể xảy ra nếu lập trình viên không hiểu cách ORM hoạt động.
Đối với Backend Developer, cách tiếp cận hiệu quả là hiểu SQL và cấu trúc database trước, sau đó sử dụng ORM để tăng năng suất phát triển. Khi cần tối ưu hoặc xử lý truy vấn phức tạp, lập trình viên phải biết cách kiểm tra SQL thực tế và đánh giá chi phí thực thi của database.
Bản chất cần ghi nhớ: ORM giúp bạn làm việc với database thuận tiện hơn thông qua code, nhưng việc truy xuất dữ liệu đúng đắn và hiệu quả vẫn phụ thuộc vào cách thiết kế và sử dụng hệ thống.








Khoá học lập trình game con rắn cho trẻ em




