Tại sao Phiên mở Hibernate trong Chế độ xem được coi là một phương pháp không tốt?


107

Và bạn sử dụng loại chiến lược thay thế nào để tránh LazyLoadExceptions?

Tôi hiểu rằng phiên mở trong chế độ xem có vấn đề với:

  • Các ứng dụng phân lớp chạy trong các jvm khác nhau
  • Các giao dịch chỉ được cam kết khi kết thúc, và hầu hết có thể bạn muốn kết quả trước đó.

Nhưng, nếu bạn biết rằng ứng dụng của mình đang chạy trên một vm duy nhất, tại sao không giảm bớt nỗi đau của bạn bằng cách sử dụng chiến lược phiên mở trong chế độ xem?


12
OSIV có được coi là một thực hành xấu? Bởi ai?
Johannes Brodwall

4
Và - những lựa chọn thay thế tốt là gì?
David Rabinowitz,

7
Văn bản yên bình này nếu từ các nhà phát triển đường may: Có một số vấn đề với việc triển khai này, nghiêm trọng nhất là chúng tôi không bao giờ có thể chắc chắn rằng một giao dịch thành công cho đến khi chúng tôi cam kết nhưng vào thời điểm giao dịch "mở phiên trong chế độ xem" được cam kết, chế độ xem được hiển thị đầy đủ và phản hồi được hiển thị có thể đã được gửi cho khách hàng. Làm cách nào để chúng tôi có thể thông báo cho người dùng rằng giao dịch của họ không thành công?
darpet,


2
Xem bài đăng trên blog này để biết ưu và nhược điểm và kinh nghiệm của riêng tôi về nó - blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Câu trả lời:


46

Bởi vì việc gửi các Proxy có thể chưa được giám sát, đặc biệt là các bộ sưu tập, trong lớp xem và kích hoạt tải ở chế độ ngủ đông từ đó có thể gây rắc rối từ cả quan điểm hiệu suất và hiểu biết.

Hiểu biết :

Việc sử dụng OSIV 'gây ô nhiễm' cho lớp xem với các mối quan tâm liên quan đến lớp truy cập dữ liệu.

Lớp xem không chuẩn bị để xử lý một HibernateExceptionđiều có thể xảy ra khi tải chậm, nhưng có lẽ là lớp truy cập dữ liệu.

Hiệu suất :

OSIV có xu hướng kéo quá trình tải thực thể phù hợp dưới thảm - bạn có xu hướng không nhận thấy rằng các bộ sưu tập hoặc thực thể của mình được khởi tạo một cách lười biếng (có thể là N + 1). Tiện lợi hơn, kiểm soát ít hơn.


Cập nhật: xem Phản vật chất của OpenSessionInView để có một cuộc thảo luận lớn hơn về chủ đề này. Tác giả liệt kê ba điểm quan trọng:

  1. mỗi lần khởi tạo lười biếng sẽ nhận được cho bạn một truy vấn nghĩa là mỗi thực thể sẽ cần N + 1 truy vấn, trong đó N là số lượng liên kết lười biếng. Nếu màn hình của bạn hiển thị dữ liệu dạng bảng, đọc nhật ký của Hibernate là một gợi ý lớn mà bạn không nên làm
  2. điều này hoàn toàn đánh bại kiến ​​trúc phân lớp, vì bạn hoàn toàn đóng móng bằng DB trong lớp trình bày. Đây là một khái niệm sai lầm, vì vậy tôi có thể sống với nó nhưng có một hệ lụy
  3. cuối cùng nhưng không kém phần quan trọng, nếu một ngoại lệ xảy ra trong khi tìm nạp phiên, nó sẽ xảy ra trong quá trình ghi trang: bạn không thể trình bày trang lỗi rõ ràng cho người dùng và điều duy nhất bạn có thể làm là viết thông báo lỗi trong phần nội dung

13
Ok, nó 'gây ô nhiễm' lớp xem với ngoại lệ ngủ đông. Tuy nhiên, về hiệu suất, tôi nghĩ vấn đề khá giống với việc truy cập vào một lớp dịch vụ sẽ trả về dto của bạn. Nếu bạn gặp phải vấn đề về hiệu suất, thì bạn nên tối ưu hóa vấn đề cụ thể đó bằng một truy vấn thông minh hơn hoặc một dto nhẹ hơn. Nếu bạn phải phát triển quá nhiều phương thức dịch vụ để xử lý các khả năng bạn có thể cần trong chế độ xem, bạn cũng đang 'làm ô nhiễm' lớp dịch vụ. Không?
HeDinges

1
Một điểm khác biệt là nó trì hoãn việc đóng phiên Hibernate. Bạn sẽ đợi JSP được kết xuất / ghi / vv và điều đó giữ các đối tượng trong bộ nhớ lâu hơn. Đó có thể là một vấn đề, đặc biệt nếu bạn cần ghi dữ liệu trên cam kết phiên.
Robert Munteanu, 09/07/09

8
Sẽ không hợp lý khi nói rằng OSIV làm tổn hại đến Hiệu suất. Có những lựa chọn thay thế nào ngoại trừ việc sử dụng DTO? Trong trường hợp đó, bạn sẽ luôn có hiệu suất thấp hơn vì dữ liệu được sử dụng bởi bất kỳ chế độ xem nào sẽ phải được tải ngay cả đối với các chế độ xem không cần nó.
Johannes Brodwall

11
Tôi nghĩ rằng ô nhiễm hoạt động theo cách khác. Nếu tôi cần tải dữ liệu một cách háo hức, thì lớp logic (hoặc tệ hơn là lớp truy cập dữ liệu) cần biết một đối tượng sẽ được hiển thị theo cách nào. Thay đổi chế độ xem và cuối cùng bạn tải những thứ bạn không cần hoặc thiếu những đối tượng bạn cần. Hibernate Exception là một lỗi và cũng giống như bất kỳ ngoại lệ bất ngờ nào khác. Nhưng hiệu suất là một vấn đề. Hiệu suất và khả năng mở rộng vấn đề này sẽ buộc bạn phải đặt nhiều suy nghĩ và làm việc tại lớp truy cập dữ liệu của bạn, và có thể buộc phiên để đóng cửa trước đó
Jens Schauder

1
@JensSchauder "Thay đổi chế độ xem và cuối cùng bạn tải những thứ bạn không cần hoặc thiếu những đối tượng bạn cần". Đây chính xác là nó. Nếu bạn thay đổi chế độ xem, tốt hơn nhiều là tải những thứ bạn không cần (vì bạn có nhiều khả năng muốn tìm nạp chúng hơn) hoặc tìm ra các đối tượng bị thiếu vì bạn sẽ nhận được ngoại lệ tải Lazy, hơn là để chế độ xem tải nó lười biếng vì điều đó sẽ dẫn đến vấn đề N + 1 và bạn thậm chí sẽ không biết nó đang xảy ra. Vì vậy, IMO tốt hơn cho lớp dịch vụ (và bạn) biết những gì nó được gửi đi hơn là chế độ xem tải một cách lười biếng và bạn không biết gì về nó.
Jeshurun

40

Để có mô tả dài hơn, bạn có thể đọc bài viết Chống mô hình phiên mở trong chế độ xem của tôi . Nếu không, đây là bản tóm tắt lý do tại sao bạn không nên sử dụng Open Session In View.

Mở phiên trong chế độ xem có một cách tiếp cận xấu để tìm nạp dữ liệu. Thay vì để lớp nghiệp vụ quyết định cách tốt nhất để tìm nạp tất cả các liên kết mà lớp Chế độ xem cần, nó buộc Ngữ cảnh bền vững luôn mở để lớp Chế độ xem có thể kích hoạt khởi tạo Proxy.

nhập mô tả hình ảnh ở đây

  • Các OpenSessionInViewFiltercuộc gọi openSessionphương thức của cơ sở SessionFactoryvà nhận được một mới Session.
  • Các Sessionràng buộc với TransactionSynchronizationManager.
  • Các OpenSessionInViewFiltergọi doFiltercủa javax.servlet.FilterChaintham chiếu đối tượng và yêu cầu được tiếp tục chế biến
  • DispatcherServletđược gọi và nó định tuyến yêu cầu HTTP đến bên dưới PostController.
  • Các PostControllerlệnh gọi PostServiceđể lấy danh sách các Postthực thể.
  • Giao dịch PostServicemở ra một giao dịch mới và HibernateTransactionManagersử dụng lại giao dịch Sessionđã được mở bởi OpenSessionInViewFilter.
  • Tìm PostDAOnạp danh sách các Postthực thể mà không cần khởi tạo bất kỳ liên kết lười biếng nào.
  • Giao dịch PostServicecam kết giao dịch cơ bản, nhưng Sessionkhông bị đóng vì nó được mở bên ngoài.
  • Các DispatcherServletrender giao diện người dùng, trong đó, đến lượt nó, điều hướng các hiệp hội lười biếng và gây nên khởi tạo của họ bắt đầu.
  • OpenSessionInViewFilterthể đóng Session, và kết nối cơ sở dữ liệu cơ bản cũng được phát hành.

Thoạt nhìn, điều này có vẻ không phải là một việc tồi tệ để làm, nhưng một khi bạn xem nó từ góc độ cơ sở dữ liệu, một loạt lỗ hổng bắt đầu trở nên rõ ràng hơn.

Lớp dịch vụ mở và đóng một giao dịch cơ sở dữ liệu, nhưng sau đó, không có giao dịch rõ ràng nào diễn ra. Vì lý do này, mọi câu lệnh bổ sung được đưa ra từ giai đoạn kết xuất giao diện người dùng được thực thi ở chế độ cam kết tự động. Tự động cam kết gây áp lực lên máy chủ cơ sở dữ liệu vì mỗi câu lệnh phải chuyển nhật ký giao dịch vào đĩa, do đó gây ra nhiều lưu lượng I / O trên phía cơ sở dữ liệu. Một tối ưu hóa sẽ là đánh dấu Connectionlà chỉ đọc, điều này sẽ cho phép máy chủ cơ sở dữ liệu tránh ghi vào nhật ký giao dịch.

Không có sự tách biệt của các mối quan tâm nữa vì các câu lệnh được tạo ra bởi cả lớp dịch vụ và quá trình kết xuất giao diện người dùng. Viết các bài kiểm tra tích hợp để xác nhận số lượng câu lệnh được tạo yêu cầu phải đi qua tất cả các lớp (web, dịch vụ, DAO), trong khi ứng dụng được triển khai trên vùng chứa web. Ngay cả khi sử dụng cơ sở dữ liệu trong bộ nhớ (ví dụ: HSQLDB) và máy chủ web nhẹ (ví dụ: Jetty), các thử nghiệm tích hợp này sẽ thực thi chậm hơn so với nếu các lớp được tách biệt và các thử nghiệm tích hợp phía sau sử dụng cơ sở dữ liệu, trong khi các bài kiểm tra tích hợp front-end đang chế nhạo hoàn toàn lớp dịch vụ.

Lớp giao diện người dùng được giới hạn trong việc điều hướng các liên kết có thể kích hoạt N + 1 sự cố truy vấn. Mặc dù Hibernate cung cấp @BatchSizecác liên kết tìm nạp theo lô và FetchMode.SUBSELECTđể đối phó với trường hợp này, các chú thích đang ảnh hưởng đến kế hoạch tìm nạp mặc định, vì vậy chúng được áp dụng cho mọi trường hợp sử dụng kinh doanh. Vì lý do này, truy vấn lớp truy cập dữ liệu phù hợp hơn nhiều vì nó có thể được điều chỉnh cho phù hợp với các yêu cầu tìm nạp dữ liệu ca sử dụng hiện tại.

Cuối cùng nhưng không kém phần quan trọng, kết nối cơ sở dữ liệu có thể được giữ trong suốt giai đoạn kết xuất giao diện người dùng (tùy thuộc vào chế độ phát hành kết nối của bạn), điều này làm tăng thời gian thuê kết nối và hạn chế thông lượng giao dịch tổng thể do tắc nghẽn trên nhóm kết nối cơ sở dữ liệu. Càng giữ nhiều kết nối, thì càng có nhiều yêu cầu đồng thời khác sẽ phải đợi để nhận được kết nối từ nhóm.

Vì vậy, hoặc bạn bị giữ kết nối quá lâu, hoặc bạn có được / giải phóng nhiều kết nối cho một yêu cầu HTTP duy nhất, do đó gây áp lực lên nhóm kết nối cơ bản và hạn chế khả năng mở rộng.

Khởi động mùa xuân

Rất tiếc, Open Session in View được bật theo mặc định trong Spring Boot .

Vì vậy, hãy đảm bảo rằng trong application.propertiestệp cấu hình, bạn có mục nhập sau:

spring.jpa.open-in-view=false

Thao tác này sẽ vô hiệu hóa OSIV, để bạn có thể xử lý LazyInitializationExceptionđúng cách .


3
Có thể sử dụng Phiên mở trong Chế độ xem với cam kết tự động nhưng không phải theo cách mà các nhà phát triển Hibernate dự định. Vì vậy, mặc dù Open Session in View có nhược điểm của nó, nhưng tự động cam kết không phải là một vì bạn chỉ cần tắt nó đi và vẫn sử dụng được.
stefan.m

Bạn đang nói về những gì xảy ra bên trong một giao dịch, và điều đó đúng. Nhưng giai đoạn kết xuất Lớp web xảy ra bên ngoài Hibernate, do đó bạn sẽ có chế độ gửi tự động. Có ý nghĩa?
Vlad Mihalcea

Tôi nghĩ đó là một biến thể không phải là biến thể tối ưu cho Phiên mở trong Chế độ xem. Phiên và giao dịch sẽ vẫn mở cho đến khi chế độ xem được hiển thị, sau đó không cần chế độ tự động gửi.
stefan.m

2
Phiên vẫn mở. Nhưng giao dịch thì không. Việc kéo dài giao dịch trong toàn bộ quy trình cũng không phải là tối ưu vì nó làm tăng độ dài của giao dịch và các khóa được giữ lâu hơn mức cần thiết. Hãy tưởng tượng điều gì sẽ xảy ra nếu khung nhìn ném một RuntimeException. Giao dịch có khôi phục do hiển thị giao diện người dùng không thành công không?
Vlad Mihalcea

Cảm ơn bạn rất nhiều vì câu trả lời rất chi tiết! Cuối cùng thì tôi chỉ thay đổi hướng dẫn vì người dùng khởi động mùa xuân có thể sẽ không sử dụng jpa theo cách đó.
Skeeve

24
  • các giao dịch có thể được cam kết trong lớp dịch vụ - các giao dịch không liên quan đến OSIV. Nó Sessionvẫn mở, không phải là một giao dịch - đang chạy.

  • nếu các lớp ứng dụng của bạn trải rộng trên nhiều máy, thì bạn hầu như không thể sử dụng OSIV - bạn phải khởi tạo mọi thứ bạn cần trước khi gửi đối tượng qua dây.

  • OSIV là một cách tốt đẹp và minh bạch (tức là - không có mã nào của bạn biết rằng điều đó xảy ra) để tận dụng các lợi ích về hiệu suất của việc tải chậm


2
Về điểm đầu tiên, điều này ít nhất không đúng với OSIV ban đầu từ JBoss wiki, nó cũng xử lý phân giới giao dịch xung quanh yêu cầu.
Pascal Thivent 23/09/10

@PascalThivent Phần nào khiến bạn nghĩ như vậy?
Sanghyun Lee

13

Tôi sẽ không nói rằng Open Session In View được coi là một thực tiễn xấu; điều gì mang lại cho bạn ấn tượng đó?

Open-Session-In-View là một cách tiếp cận đơn giản để xử lý các phiên với Hibernate. Bởi vì nó đơn giản, nó đôi khi đơn giản. Nếu bạn cần kiểm soát chi tiết các giao dịch của mình, chẳng hạn như có nhiều giao dịch trong một yêu cầu thì Open-Session-In-View không phải lúc nào cũng là một cách tiếp cận tốt.

Như những người khác đã chỉ ra, có một số sự đánh đổi đối với OSIV - bạn dễ gặp vấn đề N + 1 hơn vì bạn ít có khả năng nhận ra những giao dịch bạn đang thực hiện. Đồng thời, điều đó có nghĩa là bạn không cần phải thay đổi lớp dịch vụ của mình để thích ứng với những thay đổi nhỏ trong chế độ xem của bạn.


5

Nếu bạn đang sử dụng vùng chứa Inversion of Control (IoC) chẳng hạn như Spring, bạn có thể muốn đọc phạm vi bean . Về cơ bản, tôi đang nói với Spring cung cấp cho tôi một Sessionđối tượng Hibernate có vòng đời kéo dài toàn bộ yêu cầu (tức là, nó được tạo và hủy ở đầu và cuối của yêu cầu HTTP). Tôi không phải lo lắng về LazyLoadExceptions cũng như đóng phiên vì vùng chứa IoC quản lý điều đó cho tôi.

Như đã đề cập, bạn sẽ phải suy nghĩ về các vấn đề hiệu suất N + 1 SELECT. Bạn luôn có thể định cấu hình thực thể Hibernate của mình sau đó để thực hiện tải tham gia háo hức ở những nơi có vấn đề về hiệu suất.

Giải pháp xác định phạm vi bean không phải là giải pháp dành riêng cho Spring. Tôi biết PicoContainer cung cấp khả năng tương tự và tôi chắc chắn rằng các thùng chứa IoC trưởng thành khác cũng cung cấp điều gì đó tương tự.


1
Bạn có con trỏ đến việc triển khai thực tế các phiên Hibernate đang được cung cấp trong chế độ xem thông qua các bean phạm vi yêu cầu không?
Marvo

4

Theo kinh nghiệm của riêng tôi, OSIV không quá tệ. Sự sắp xếp duy nhất mà tôi thực hiện là sử dụng hai giao dịch khác nhau: - giao dịch đầu tiên, được mở trong "lớp dịch vụ", nơi tôi có "logic nghiệp vụ" - giao dịch thứ hai được mở ngay trước khi hiển thị chế độ xem


3

Tôi vừa đăng một bài về một số nguyên tắc về thời điểm sử dụng phiên mở trong chế độ xem trong blog của mình. Kiểm tra nó ra nếu bạn quan tâm.

http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/


1
Theo nguyên tắc chung SO, nếu bạn đang cung cấp câu trả lời, tốt nhất bạn nên làm nhiều hơn là chỉ liên kết ở nơi khác. Có lẽ cung cấp một hoặc hai câu hoặc các mục được liệt kê cung cấp ý chính. Bạn có thể liên kết, nhưng bạn muốn cung cấp thêm một chút giá trị. Nếu không, bạn có thể chỉ muốn nhận xét và đặt liên kết ở đó.
DWright

liên kết trong câu trả lời này đáng đọc, nó cung cấp hướng dẫn tốt về thời điểm nên sử dụng OSIV và không
ams

1

Tôi không biết gì về Hibernate .. nhưng tôi nghĩ có thể có nhiều giao dịch trong một phiên Hibernate. Vì vậy, ranh giới giao dịch của bạn không nhất thiết phải giống với các sự kiện bắt đầu / dừng phiên.

OSIV, imo, chủ yếu là hữu ích vì chúng ta có thể tránh viết mã để bắt đầu 'ngữ cảnh liên tục' (hay còn gọi là phiên) mỗi khi yêu cầu thực hiện quyền truy cập DB.

Trong lớp dịch vụ của mình, bạn có thể sẽ cần thực hiện các lệnh gọi đến các phương thức có nhu cầu giao dịch khác nhau, chẳng hạn như 'Bắt ​​buộc, Yêu cầu mới, v.v.' Điều duy nhất mà các phương pháp này cần là ai đó (tức là bộ lọc OSIV) đã khởi động ngữ cảnh liên tục, vì vậy điều duy nhất họ phải lo lắng là - "này, hãy cho tôi phiên ngủ đông cho chuỗi này .. Tôi cần làm một số Thứ DB ”.


1

Điều này sẽ không giúp ích quá nhiều nhưng bạn có thể kiểm tra chủ đề của tôi tại đây: * Hibernate Cache1 OutOfMemory với OpenSessionInView

Tôi gặp một số vấn đề về OutOfMemory do OpenSessionInView và rất nhiều thực thể được tải, vì chúng ở trong bộ đệm Hibernate cấp 1 và không được thu thập rác (tôi tải rất nhiều thực thể với 500 mục mỗi trang, nhưng tất cả các thực thể đều ở trong bộ nhớ cache)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.