ORM Là Gì? Ưu Nhược Điểm và Khi Nào Nên Sử Dụng Trong Dự Án Thực Tế

Trong thế giới phát triển ứng dụng web hiện đại, ORM (Object Relational Mapping) đã trở thành một khái niệm quen thuộc. Đặc biệt, với sự trỗi dậy mạnh mẽ của các framework ứng với nhiều ngôn ngữ lập trình, ORM gần như là một lựa chọn không thể thiếu của các nhà phát triển.

ORM giúp đơn giản hóa việc tương tác với cơ sở dữ liệu, giúp code dễ đọc, dễ bảo trì và phát triển hơn. Bài viết này sẽ cung cấp một cái nhìn tổng quan về ORM, phân tích ưu nhược điểm và đưa ra những gợi ý về thời điểm áp dụng ORM vào các dự án thực tế.

Theo định nghĩa trên Wikipedia:

Ánh xạ đối tượng-quan hệ (ORM, O/RM, và công cụ ánh xạ O/R) trong khoa học máy tính là một kỹ thuật lập trình để chuyển đổi dữ liệu giữa các hệ thống kiểu không tương thích bằng cách sử dụng ngôn ngữ lập trình hướng đối tượng. Điều này tạo ra một “cơ sở dữ liệu đối tượng ảo” có thể được sử dụng từ bên trong ngôn ngữ lập trình. Có cả các gói miễn phí và thương mại có sẵn để thực hiện ánh xạ đối tượng-quan hệ, mặc dù một số lập trình viên chọn xây dựng các công cụ ORM của riêng họ.

Hiểu một cách đơn giản, ORM là một kỹ thuật lập trình giúp ánh xạ các bản ghi dữ liệu trong hệ quản trị cơ sở dữ liệu sang dạng đối tượng (object) được định nghĩa trong các class. Đây là một khái niệm phổ biến trong các ngôn ngữ lập trình hiện đại như Java, PHP, Ruby. Bạn có thể áp dụng kỹ thuật này vào bất kỳ dự án nào.

Martin Fowler đề xuất hai patterns chính cho các cách thiết kế ORM khác nhau:

  1. Active Record: Kỹ thuật này sử dụng một đối tượng (object) bao bọc một hàng trong bảng cơ sở dữ liệu hoặc view, đóng gói quyền truy cập cơ sở dữ liệu và cả logic trên dữ liệu đó. Đối tượng này bao gồm cả dữ liệu và hành vi. Dữ liệu thường mang tính liên tục và được lưu trữ trong cơ sở dữ liệu. Active Record tiếp cận một cách rõ ràng, minh bạch, đưa logic thao tác dữ liệu vào object domain. Nhờ đó, các nhà phát triển dễ dàng đọc hiểu logic của pattern này.

    Một số ORM nổi tiếng sử dụng Active Record: Eloquent (Laravel), CakePHP, JOOQ, TOPLink.

  2. Data Mapper: Theo định nghĩa chính thức, Data Mapper là một layer mapper đưa dữ liệu qua lại giữa object và cơ sở dữ liệu, đồng thời vẫn giữ được tính độc lập giữa chúng và giữa chính nó (lớp mapper). Như vậy, có một layer đặc biệt tách biệt với bộ nhớ của cơ sở dữ liệu, có nhiệm vụ chính là chuyển dữ liệu qua lại giữa hai lớp.

    Một số ORM nổi tiếng sử dụng Data Mapper: Doctrine, Hibernate, SQLAlchemy.

Đặc trưng cơ bản của ORM là gói gọn cơ sở dữ liệu trong một object. Một phần của object sẽ chứa data, phần còn lại lo việc xử lý data và biến nó thành cơ sở dữ liệu quan hệ.

ORM giải quyết vấn đề đồng bộ giữa các kiểu dữ liệu trả về khác nhau. Một bên là cơ sở dữ liệu, nơi dữ liệu được thể hiện dưới dạng tập hợp các bản ghi. Một bên là các đối tượng, nơi dữ liệu được hiển thị dưới dạng object.

Ví dụ, đoạn code sau đây minh họa việc tìm kiếm danh sách bản ghi với điều kiện sử dụng SQL thuần:

book_list = new List();
sql = "SELECT * FROM library WHERE author = 'Linus'";
data = query(sql);
while (row = sentayho.com.vn()) {
    book = new Book();
    sentayho.com.vnuthor(row.get('author');
    sentayho.com.vn(book);
}

Với ORM, đoạn code trên có thể được viết đơn giản như sau:

book_list = sentayho.com.vny(author=”Linus”);

Ví dụ này cho thấy những ưu điểm rõ ràng khi sử dụng ORM.

Ưu điểm của ORM

  • Tuân thủ nguyên tắc DRY (Don’t Repeat Yourself): ORM tập trung hóa code của bạn tại một chỗ, giúp việc chỉnh sửa, bảo trì và tái sử dụng code dễ dàng hơn.
  • Tự động hóa nhiều tác vụ: ORM tự động xử lý nhiều tác vụ liên quan đến việc xử lý dữ liệu. Trong ví dụ trên, chúng ta không cần quan tâm đến việc sử dụng vòng lặp để chuyển đổi dữ liệu từ MySQL. Bạn chỉ cần biết đầu vào là từ khóa, đầu ra là danh sách các quyển sách có tác giả là “Linus”.
  • Bắt buộc tuân theo mô hình MVC: Việc sử dụng ORM khuyến khích bạn viết code theo mô hình MVC, giúp code dễ sửa đổi và bảo trì hơn.
  • Dễ tiếp cận: Bạn không cần phải có kiến thức chuyên sâu về MySQL để có thể tiếp cận và sử dụng ORM.
  • Tính linh hoạt: Model không bị ràng buộc chặt chẽ với ứng dụng. Bạn có thể thay đổi hoặc sử dụng Model ở bất kỳ đâu.
  • Tận dụng ưu điểm của OOP: ORM cho phép bạn tận dụng các ưu điểm của lập trình hướng đối tượng (OOP) như kế thừa dữ liệu mà không gặp nhiều khó khăn.

Nhược điểm của ORM

  • Thời gian học: Mỗi framework thường đi kèm với một thư viện ORM riêng. Bạn sẽ cần thời gian để học và làm quen với nó. Hơn nữa, các thư viện ORM thường khá “nặng”.
  • Hiệu năng (Performance): Hiệu năng của ORM thường ổn đối với các truy vấn thông thường. Tuy nhiên, Raw SQL luôn có hiệu năng tốt hơn trong các dự án lớn.
  • Tính trừu tượng: ORM được coi là một lớp trừu tượng của cơ sở dữ liệu. Nếu bạn không quan tâm đến những gì thực sự xảy ra khi sử dụng ORM, bạn có thể rơi vào cái bẫy N+1 query.

Như đã đề cập ở phần nhược điểm, ORM thường có hiệu năng chậm hơn so với việc sử dụng Raw SQL. Dưới đây là thống kê so sánh hiệu năng giữa Eloquent ORM (Laravel) và Raw MySQL trong các thao tác INSERT, UPDATE và SELECT.

Môi trường thử nghiệm:

  • CPU: Quad core Intel Xeon E31220
  • Network: Broadcom NetXtreme BCM5722 Gigabit Ethernet PCI Express
  • Memory: 8.0 GB
  • HDD: 2000.4 GB
  • Operating System: CentOS

4.1 Insert

Thử nghiệm insert số lượng bài viết khác nhau (từ 1000 đến 10000) cho cả hai kỹ thuật (Eloquent ORM và Raw SQL) trong mỗi vòng lặp.

Eloquent ORM

Số lượt Số bài viết Lượt 1 (ms) Lượt 2 (ms) Lượt 3 (ms) Trung bình cộng (ms) Sai lệch (ms)
1 1000 665,25 619,5 585,6 623,5 39,97
2 2000 1150 1140 1110 1133,3 20,81
3 3000 1490 1490 1420 1466,7 40,41
4 4000 1770 1790 1670 1743,3 64,29
5 5000 2080 2090 2220 2130,0 78,10
6 6000 2540 2510 2560 2536,7 25,16
7 7000 2930 3010 3080 3006,7 75,05
8 8000 3360 3520 3380 3420,0 87,17
9 9000 3800 3900 3880 3860,0 52,91
10 10000 4270 4360 4390 4340,0 62,44

Raw SQL

Số lượt Số bài viết Lượt 1 (ms) Lượt 2 (ms) Lượt 3 (ms) Trung bình cộng (ms) Sai lệch (ms)
1 1000 195,81 189,62 180,1 188,5 7,91
2 2000 322,78 335,64 307,01 321,8 14,33
3 3000 413,4 437,62 458,24 436,4 22,44
4 4000 598,87 567,57 559,01 575,2 20,98
5 5000 725,5 740,94 780,15 748,9 28,17
6 6000 867,93 869,12 891,27 876,1 13,14
7 7000 1000 996,14 949,05 981,7 28,36
8 8000 1150 1120 1130 1133,3 15,27
9 9000 1190 1270 1220 1226,7 40,41
10 10000 1430 1380 1450 1420,0 36,05

(Hình ảnh minh họa biểu đồ, cần thay thế bằng biểu đồ thực tế)

4.2 Update

Thử nghiệm update số lượng bài viết khác nhau (từ 1000 đến 10000) cho cả hai kỹ thuật trong mỗi vòng lặp.

Eloquent ORM

Số lượt Số bài viết Lượt 1 (ms) Lượt 2 (ms) Lượt 3 (ms) Trung bình cộng (ms) Sai lệch (ms)
1 1000 305,59 316,06 297,93 306,5 9,10
2 2000 522,06 565,12 543,43 543,5 21,53
3 3000 778,4 772,33 790,52 780,4 9,26
4 4000 1040 1010 1080 1043,3 35,11
5 5000 1290 1240 1260 1263,3 25,16
6 6000 2540 2510 2560 2536,7 25,16
7 7000 2930 3010 3080 3006,7 75,05
8 8000 3360 3520 3380 3420,0 87,17
9 9000 3800 3900 3880 3860,0 52,91
10 10000 4270 4360 4390 4340,0 62,44

Raw SQL

Số lượt Số bài viết Lượt 1 (ms) Lượt 2 (ms) Lượt 3 (ms) Trung bình cộng (ms) Sai lệch (ms)
1 1000 195,81 189,62 180,1 188,5 7,91
2 2000 322,78 335,64 307,01 321,8 14,33
3 3000 413,4 437,62 458,24 436,4 22,44
4 4000 598,87 567,57 559,01 575,2 20,98
5 5000 725,5 740,94 780,15 748,9 28,17
6 6000 867,93 869,12 891,27 876,1 13,14
7 7000 1000 996,14 949,05 981,7 28,36
8 8000 1150 1120 1130 1133,3 15,27
9 9000 1190 1270 1220 1226,7 40,41
10 10000 1430 1380 1450 1420,0 36,05

(Hình ảnh minh họa biểu đồ, cần thay thế bằng biểu đồ thực tế)

4.3 Select

Thử nghiệm select với ba lượt khác nhau:

  • Lượt 1: Lấy ra 3000 bài viết được đăng bởi một user.
  • Lượt 2: Lấy ra 3000 bài viết có tag là “sport” và được đăng bởi một user.
  • Lượt 3: Lấy ra 3000 bài viết có tag là “music”, được đăng bởi một user và có thời gian đăng sau ngày 2015-05-21 22:00:00.

Eloquent ORM

Số lượt Số bảng join Lượt 1 (ms) Lượt 2 (ms) Lượt 3 (ms) Trung bình cộng (ms) Sai lệch (ms)
1 1 157,85 161,51 167,27 162,2 4,74
2 3 1000 1002 1006 1002,7 3,055
3 4 1510 1540 1570 1540,0 30

Raw SQL

Số lượt Số bảng join Lượt 1 (ms) Lượt 2 (ms) Lượt 3 (ms) Trung bình cộng (ms) Sai lệch (ms)
1 1 98,87 126,54 123,83 116,4 15,25
2 3 143,18 123,12 125,52 130,6 10,95
3 4 158,33 141,16 166,23 155,2 12,81

4.4 Đánh giá và phân tích

Từ các số liệu trên, có thể thấy hiệu năng của Raw SQL tốt hơn so với ORM trong tất cả các hành động (select, insert, update).

Lý do là vì ORM cần thời gian để tạo Model instances, tạo ra các property cho model và transform đối tượng model thành dữ liệu quan hệ trước khi thực hiện việc giao tiếp với database. Laravel đã convert từng code Eloquent ORM thành câu lệnh SQL thích hợp và sau đó thực thi trên tầng database và trả lại kết quả lại tầng application. Do đó, thời gian để thực thi một tác vụ luôn mất nhiều thời gian hơn so với việc dùng Raw SQL.

Tuy nhiên, không nên vội vàng kết luận ORM là vô dụng.

Trong thực tế, không ai thực hiện việc select hoặc insert hàng nghìn bản ghi trong một lúc. Khi số lượng bản ghi ít, độ chênh lệch về hiệu suất giảm đi đáng kể. Lúc đó, việc sử dụng các kỹ thuật cache của ORM có thể giúp ứng dụng của bạn đạt hiệu năng chấp nhận được, đồng thời mang lại những lợi ích về tốc độ phát triển ứng dụng, loại bỏ code lặp đi lặp lại, tăng cường bảo mật và nhiều ưu điểm khác.

Kết luận

Bài viết đã mô tả cách hoạt động của ORM, nêu ra các ưu điểm và nhược điểm. Việc có nên sử dụng ORM hay không vẫn là một chủ đề tranh luận.

Từ kinh nghiệm cá nhân, tôi thường kết hợp giữa ORM và Raw SQL để đạt hiệu quả cao nhất. Với các truy vấn thông thường, ORM là lựa chọn ưu tiên. Với các tình huống phải thao tác với nhiều bản ghi, Raw SQL có vẻ hợp lý hơn. Tuy nhiên, khi sử dụng Raw SQL, cần đặc biệt cẩn thận về vấn đề SQL injection để tránh các lỗ hổng bảo mật.

Cảm ơn bạn đã theo dõi bài viết.

Tài liệu tham khảo:

  1. Số liệu thống kê về thời gian truy vấn
  2. ORM là gì?