20 điểm bởi carnoxen 2024-12-05 | 22 bình luận | Chia sẻ qua WhatsApp

Tóm tắt

JPA/Hibernate đã trở thành framework được sử dụng rộng rãi vì khiến người ta nghĩ rằng không còn cần viết SQL trong mã Java nữa. Tuy nhiên, tôi muốn lập luận rằng không nên dùng nó cho các dự án mới.

Lý do

Tài liệu chính thức quá dài

Nếu chuyển tài liệu chính thức sang PDF thì nó dài tới 406 trang, còn nhiều hơn cả Chúa tể những chiếc nhẫn (231 trang) và tài liệu chuẩn SQL (288 trang). Không ai cần học đến bậc thạc sĩ chỉ để nắm được cách truy vấn cơ sở dữ liệu.

Tính có thể thay đổi

  1. Dù một entity cần một thành phần nào đó, nó vẫn bị ép phải có constructor không tham số.
  2. Không thể ngăn kế thừa chỉ bằng cách đặt từ khóa final, abstract vào lớp entity.
  3. Reflection/Introspection bỏ qua nguyên tắc đóng gói của OOP.
  4. Ai đó có thể cài mã độc để xóa sạch toàn bộ dữ liệu.

Lazy loading và cache

  1. Annotation @Lazy là một trong những kỹ thuật tệ nhất đối với người mới bắt đầu. Nhưng khi thiết kế domain không hợp với Hibernate hoặc không thể tự viết truy vấn thì rất khó tránh khỏi nó.
  2. Cơ chế cache rất khó hiểu. Hơn nữa, kể cả khi đã hiểu thì bạn vẫn phải lưu chính entity vào cache chứ không phải kết quả truy vấn.

Đồng bộ bộ nhớ-cơ sở dữ liệu (Flush)

Kỹ thuật Flush đồng bộ các đối tượng đang lưu trong bộ nhớ với cơ sở dữ liệu. Điều này gây ra hai vấn đề.

  • Khi Flush chạy, các chỉnh sửa trong bộ nhớ bị chốt lại, nên gần như không thể nghĩ đến việc dùng công cụ persistence nào khác ngoài Hibernate, và
  • Nếu xảy ra xung đột trong lúc Flush, có thể xuất hiện lỗi Stack Trace không liên quan trực tiếp đến đoạn mã đang viết.

Chỉ lấy một cột cụ thể của một bảng

Nếu chỉ muốn truy cập một cột của một entity nào đó, cách làm bằng SQL đơn giản như sau.

select url from image   
where id = 'F462E8D9-9DF7-4A58-9112-EDE0434B4ACE';  

Nhưng Hibernate mặc định sẽ truy vấn tất cả các cột của entity. Muốn tránh điều đó phải trải qua quy trình khá phức tạp.

Định nghĩa ràng buộc (Constraint) cho cột

Để định nghĩa ràng buộc cho một cột, phải gắn nhiều annotation như bên dưới.

...  
    @NotNull  
    @NotEmpty  
    @Email  
    private String email;  
...  

Cách này gây ra các vấn đề sau.

  • Không thể viết unit test cho các điều kiện này.
  • Đến lúc đang Flush mới đi lần theo quá trình xử lý thì đã là quá muộn.
  • Các exception phát sinh thường quá chung chung và không hữu ích.
  • Quy tắc nghiệp vụ bị đối xử như những quy tắc kỹ thuật đơn thuần.

Vấn đề về chiến lược

  1. Framework cập nhật theo cách tệ hại, phớt lờ tính tương thích ngược và khiến bạn phụ thuộc vô điều kiện. Công ty tạo ra nó sẽ dần xem việc dùng chính framework của họ để độc chiếm là điều hiển nhiên. Cần chấm dứt vòng luẩn quẩn này.
  2. Việc chỉ tìm kiếm Proof of Concept thông qua framework sẽ chỉ làm hẹp góc nhìn của bạn, và với JPA/Hibernate thì lại càng như vậy. Không nên dung thứ dù chỉ một chút.

Vậy nên làm gì?

Hãy dùng SQL

Chỉ với SQL là đủ làm mọi thứ. Mọi lập trình viên đều biết nó, truy vấn trực quan, và không cần đến framework.

Nếu quản lý bắt dùng Hibernate thì sao?

Hãy nghỉ việc, hoặc tách mã của bạn ra khỏi framework.

Nếu đã dùng rồi...

  1. Đừng để mức truy cập của constructor mặc định và setter là public.
  2. Hãy dùng chuỗi như UUID thay vì ID do SQL sinh ra.
  3. Dùng tên XXXDao thay vì XXXRepository.
  4. Đừng dùng annotation @SequenceGenerator.
  5. Tách lớp domain và lớp DAO bằng interface.
  6. Đừng dùng quan hệ nhiều-* như @OneToMany; tốt hơn là tránh mapping entity nếu có thể.

Kết luận

JPA/Hibernate, hãy bỏ nó đi.

  • Có những tài liệu ngắn hơn và tốt hơn để giải quyết bài toán nghiệp vụ.
  • Đừng cố chấp bám vào cách thiết kế chỉ vì nó có vẻ nhanh.
  • Hãy rộng lượng với lập trình viên sẽ phải bảo trì mã của bạn sau này.

Còn bạn đang dùng gì?

  1. JPA/Hibernate
  2. Một công nghệ ORM khác

22 bình luận

 
askaskm 2024-12-16

Tôi phản đối quan điểm cho rằng nên bỏ JPA/Hibernate.

"Phần tài liệu chính thức rất dài"
Ngay cả SQL cũng khó khi mới học. Việc hiểu hoàn toàn các join phức tạp, subquery, hàm procedure có dễ không?
Với JPA, chỉ cần hiểu các khái niệm cốt lõi lúc ban đầu là đã đủ để bắt đầu. Những nội dung sâu hơn có thể tra cứu khi cần.
Và giờ còn có cả LLM.

"Vấn đề mutability và Reflection"
Đây là kiểu lo ngại xuất phát từ việc chưa hiểu cách framework vận hành.
Trong thực tế, hầu như không phát sinh vấn đề đáng kể nào vì điều này.
Ngược lại, nhờ Reflection mà việc ánh xạ object được tự động hóa, giúp năng suất tăng lên đáng kể.

"Lazy loading và cache"
@Lazy là "công nghệ tệ nhất" ư? Đây là tính năng rất hữu ích để giải quyết vấn đề N+1 và tối ưu hiệu năng.
Cơ chế cache ngược lại còn hỗ trợ cải thiện hiệu năng rất nhiều.

"Chỉ lấy một số cột cụ thể của một bảng"
Nếu dùng JPQL hoặc Projection thì có thể dễ dàng truy vấn chỉ những cột cần thiết.
Và có thể dùng cùng với QueryDSL.

Tôi nghĩ mục đích của ORM không phải là thay thế hoàn toàn SQL, mà là giúp lập trình viên tập trung nhiều hơn vào business logic..

 
bbulbum 2024-12-09

Tôi vốn là người bi quan với ORM, nhưng có vẻ bài viết cũng chưa đưa ra được một phương án thay thế đủ thuyết phục.

Khi đi quá nặng theo ORM thì thật sự là không có hồi kết, và như đã nhắc ở trên, có khi bạn sẽ vật lộn trong một phạm vi tài liệu còn rộng hơn cả tài liệu SQL rồi héo mòn mà chết.

Gần đây tôi đang phát triển một dự án cá nhân mà không dùng ORM, nhưng rồi khi cố thiết kế theo hướng có tính tái sử dụng, đôi lúc lại thấy mình đang phát triển theo hướng như thể đang tự tạo ra một ORM vậy. haha

 
ilbanin00 2024-12-07

Có vẻ như ưu điểm của việc dùng framework là có thể chia sẻ cùng một mô hình tư duy với các lập trình viên khác cũng đang dùng cùng framework đó luôn bị bỏ qua trong những bài viết kiểu kêu gọi đừng dùng thứ gì đó như thế này.

 
dothx 2024-12-06

Khi có nhiều bảng và cũng nhiều cột (ví dụ có 50 bảng, mỗi bảng hơn 100 cột), nếu cứ viết SQL thuần thì đúng là địa ngục.

Tuy vậy, tôi nghĩ dùng JPA/Hibernate để làm một dịch vụ nhỏ là sự lãng phí rất lớn.

Đúng là những ý kiến kiểu này còn tùy từng trường hợp.

(Ví dụ đưa ra ở đây cũng chỉ là loại có 3~4 cột...)

 
jpumpkin94 2024-12-06

Có vẻ như câu hỏi cuối cùng trong bài viết trên cần được chỉnh lại đôi chút.

Trong hệ sinh thái Java, có thể tóm gọn thành 1. ORM vs 2. Non-ORM.

  1. ORM thì trên thực tế có thể xem như chỉ dùng tổ hợp JPA/Hibernate.
  2. Có MyBatis, JOOQ, SpringDataJDBC, v.v. Chủ yếu là sẽ trực tiếp xử lý SQL.

Cả 1 và 2 đều có ưu và nhược điểm rất rõ ràng, nên việc đưa ra kết luận cực đoan như bài viết trên là không phù hợp.

Trường hợp của bên tôi thì,
vừa sử dụng JPA/Hibernate/QueryDSL là ORM, đồng thời cũng dùng MyBatis.

Tận dụng ORM để tối đa hóa năng suất,
và với những truy vấn mà ORM khó bao quát thì dùng MyBatis.

Và dù ở trên chọn 1 hay 2, thì vẫn phải hiểu rõ SQL.

 
carnoxen 2024-12-06

Tôi cũng muốn sửa lắm, nhưng vì trên trang không có chức năng đó nên...

 
kallare 2024-12-06

Có vẻ như mọi người đang làm ngơ lý do ngay từ đầu khiến ORM trở nên phổ biến.

Dù chi phí học ban đầu có hơi cao, nhưng khi đã quen thì rõ ràng năng suất được cải thiện.

SQL có vẻ đơn giản, nhưng cái cảm giác mệt mỏi khi phải code từng câu SQL một... Chưa kể khi bảng thay đổi thì tất cả các truy vấn liên quan cũng phải sửa lại từng chút một, nên việc bảo trì SQL cũng tuyệt đối không hề dễ. Càng nhỏ và đơn giản thì khối lượng công việc lại càng tăng lên (nên câu chuyện về năng suất cứ luôn đi kèm).

Thêm nữa, lỗi phát sinh từ SQL thường nổ ra ở runtime nên cũng khó bắt, rồi nếu cứ phải tự tay phòng thủ trước các kiểu tấn công như SQL injection từng chút một... thì cuối cùng lại phải thêm code để sinh query (thường bắt đầu từ dạng template đơn giản...). Làm một hồi rồi rốt cuộc lại cho ra thứ gì đó na ná ORM, vậy thì thà dùng ORM luôn có phải hơn không..?

Tôi lại nhớ đến bài được đăng mấy hôm trước. https://vi.news.hada.io/topic?id=17955

 
laracool 2024-12-06

Tôi đồng ý.
Có vẻ như nhiều người thường chưa thực sự hiểu đầy đủ lý do sử dụng ORM và những ưu điểm của nó.

Ngoài ra, cũng có vẻ như không nhiều người cố gắng phân tích hoặc hiểu SQL thực sự được thực thi thông qua ORM.
Tôi hy vọng mọi người sẽ biết nhiều hơn rằng nó không chỉ đơn thuần mang lại sự tiện lợi, mà còn có thể giúp hiểu sâu hơn về tối ưu hóa SQL và cách cơ sở dữ liệu vận hành.

 
jamsya 2024-12-06

Tôi nghĩ không cần phải nghiêng hẳn về một trong hai cực đoan là nhất định phải dùng hay tuyệt đối không được dùng đâu haha;;
Nếu cần năng suất, tôi sẽ tận dụng ORM,
còn với những truy vấn phức tạp mà ORM không thể bao quát được hoặc những truy vấn cần tối ưu thêm thì tôi xử lý bằng raw query.
Tôi nghĩ nên lựa chọn ORM hay raw query một cách phù hợp tùy theo tình huống, dựa trên việc bạn muốn xây dựng cái gì và như thế nào.

 
xhfleodhkd 2024-12-06

Nói chung, tôi nghĩ điều đó có thể đúng với những dữ liệu được chuẩn hóa DB tốt và hầu như không cần join lớn.
Nhưng nếu ngay từ khâu chuẩn hóa DB cho đến việc quản lý mọi thứ một cách bài bản thông qua DBA đều không thể làm được, thì tôi nghĩ ORM cũng có thể là một lựa chọn tốt. Đặc biệt, những lợi ích phát sinh khi lấy dữ liệu dưới dạng relationship thay vì phía phải lấy về thông qua join là một ví dụ rất điển hình cho thấy vì sao người ta lại dùng ORM.
Tất nhiên, tôi đồng ý với ý kiến rằng framework hạn chế sự phát triển của lập trình viên và nên giảm mức độ phụ thuộc vào framework.
Nhưng tôi khó có thể dễ dàng đồng ý với quan điểm rằng cứ tuyệt đối không dùng ORM.
Có cảm giác như quan điểm đó đang đặt tiền đề rằng mọi công ty đều có DBA và đều phát triển bằng những phương pháp bài bản như DDD hay TDD.
Nếu trong thực tế công việc mà đúng là làm theo kiểu đó, tôi cũng không biết code rồi sẽ còn trở nên hỗn loạn đến mức nào nữa.

 
aer0700 2024-12-06

Khi làm backend bằng PYTHON, mình hầu như lần nào cũng dùng SQLALCHEMY hoặc DJANGO ORM.
Vì khi đã quen rồi thì cảm giác giữa việc viết SQL trực tiếp và dùng ORM cũng không khác biệt mấy, nên trước giờ mình không nghĩ nhiều. Hóa ra cũng có ý kiến cho rằng không nên dùng ORM.
Mình đồng ý với quan điểm nên giảm mức độ phụ thuộc vào framework. Chỉ biết dùng mỗi DJANGO ORM mà lại không biết xử lý SQL thì chắc chắn là không ổn...

 
beoks 2024-12-06

Ừm, tôi thì không đồng ý. Hiện tôi đang vận hành một dịch vụ có khoảng 3.000 bảng, và vì domain quá phức tạp nên chỉ để tạo ra một truy vấn thôi cũng đã phải viết mặc định hơn vài chục dòng. Nếu tính cả truy vấn động nữa thì thực sự rất đau đầu. Vì phức tạp nên lỗi cũng phát sinh nhiều và việc bảo trì cũng khó khăn. Tôi nghĩ rằng trong những domain phức tạp, ORM có lợi thế hơn.

 
xhfleodhkd 2024-12-06

Trường hợp của tôi là từng có kinh nghiệm bảo trì một cơ sở dữ liệu chưa được chuẩn hóa.
Khi đó, tôi viết các truy vấn động bằng SQL thông thường thay vì dùng ORM,
nhưng vì thế đôi khi mã nguồn lại trở nên khó đọc hơn.
Tôi nghĩ không chỉ các domain phức tạp mà cả những domain thiếu chuẩn hóa cũng hoàn toàn có chỗ để áp dụng.

 
carnoxen 2024-12-06

Ồ, vậy thì có lẽ không cần nhìn theo hướng tiêu cực nữa.

 
tsboard 2024-12-06

Cá nhân tôi cũng muốn khuyên cứ dùng thẳng SQL. Trong thế giới JS, người ta dùng khá nhiều công cụ như Prisma, nhưng SQL cũng không phải là một ngôn ngữ khó đến mức đó, và tôi hơi ngần ngại vì có cảm giác nó đòi hỏi quá nhiều lớp trừu tượng không cần thiết chỉ để xử lý database I/O.

 
znjadong 2024-12-06

Có lẽ cũng vì các ORM bên mảng js/ts có quá nhiều sản phẩm dở dở ương ương.

 
carnoxen 2024-12-06

Chắc chỉ cần thứ như Jdbc là được nhỉ? Tôi chợt nhớ có người từng làm việc cùng tôi trước đây đã nói rằng "JPA chậm nên hãy dùng thứ khác".

 
roxie 2024-12-06

Nghe như một câu chuyện huyền thoại vậy.

 
caniel 2024-12-06

Có vẻ như xu hướng là quay về với nền tảng cốt lõi thay vì framework.
HTMX, SQL, v.v..

 
carnoxen 2024-12-06

Tuy có nhược điểm là phải tự làm lại bánh xe từ đầu.

 
misolab 2024-12-06

Cũng có ưu điểm chứ..
2. MyBatis (không phải ORM nhưng mà hihi)

 
carnoxen 2024-12-06

Đáng lẽ tôi nên chuyển sang dao thay vì Orm mới phải.