Hãy bỏ JPA/Hibernate đi
(stemlaur.com)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
- 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ố.
- Không thể ngăn kế thừa chỉ bằng cách đặt từ khóa
final,abstractvào lớp entity. - Reflection/Introspection bỏ qua nguyên tắc đóng gói của OOP.
- Ai đó có thể cài mã độc để xóa sạch toàn bộ dữ liệu.
Lazy loading và cache
- Annotation
@Lazylà 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ó. - 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
- 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.
- 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...
- Đừng để mức truy cập của constructor mặc định và setter là
public. - Hãy dùng chuỗi như UUID thay vì ID do SQL sinh ra.
- Dùng tên
XXXDaothay vìXXXRepository. - Đừng dùng annotation
@SequenceGenerator. - Tách lớp domain và lớp DAO bằng
interface. - Đừ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ì?
- JPA/Hibernate
- Một công nghệ ORM khác
22 bình luận
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
joinphức tạp,subquery, hàmprocedurecó 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..
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
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.
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...)
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.
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.
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...
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
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.
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.
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
joinlớ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
joinlà 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.
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...
Ừ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.
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.
Ồ, vậy thì có lẽ không cần nhìn theo hướng tiêu cực nữa.
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.
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.
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".
Nghe như một câu chuyện huyền thoại vậy.
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..
Tuy có nhược điểm là phải tự làm lại bánh xe từ đầu.
Cũng có ưu điểm chứ..
2. MyBatis (không phải ORM nhưng mà hihi)
Đáng lẽ tôi nên chuyển sang
daothay vì Orm mới phải.