Thiết kế ứng dụng tải lười biếng ngủ đông


87

Tôi có xu hướng sử dụng Hibernate kết hợp với Spring framework và khả năng phân định giao dịch mang tính khai báo của nó (ví dụ: @Transactional ).

Như chúng ta đã biết, chế độ ngủ đông cố gắng không xâm lấn và càng minh bạch càng tốt, tuy nhiên điều này chứng tỏ một chút thách thức hơn khi sử dụng các lazy-loadedmối quan hệ.


Tôi thấy một số phương án thiết kế với các mức độ minh bạch khác nhau.

  1. Làm cho các mối quan hệ không bị tải chậm (ví dụ: fetchType=FetchType.EAGER)
    • Điều này xác minh toàn bộ ý tưởng về tải chậm ..
  2. Khởi tạo bộ sưu tập bằng Hibernate.initialize(proxyObj);
    • Điều này ngụ ý khả năng ghép nối tương đối cao với DAO
    • Mặc dù chúng ta có thể xác định giao diện với initialize, nhưng các triển khai khác không được đảm bảo cung cấp bất kỳ giao diện nào tương đương.
  3. Thêm hành vi giao dịch vào chính các Modelđối tượng liên tục (sử dụng proxy động hoặc @Transactional)
    • Tôi chưa thử phương pháp proxy động, mặc dù dường như tôi chưa bao giờ thấy @Transactional hoạt động trên chính các đối tượng liên tục. Có thể do ngủ đông đó là hoạt động trên một proxy để kết nối.
    • Mất kiểm soát khi giao dịch thực sự diễn ra
  4. Cung cấp cả API lười biếng / không lười biếng, ví dụ: loadData()loadDataWithDeps()
    • Buộc ứng dụng biết khi nào nên sử dụng quy trình nào, một lần nữa khớp nối chặt chẽ
    • Phương thức tràn loadDataWithA(),, ....,loadDataWithX()
  5. Buộc tra cứu các phần phụ thuộc, ví dụ, bằng cách chỉ cung cấp các byId()hoạt động
    • Yêu cầu nhiều quy trình không hướng đối tượng, ví dụ findZzzById(zid), và sau đó getYyyIds(zid)thay vìz.getY()
    • Có thể hữu ích khi tìm nạp từng đối tượng trong bộ sưu tập một nếu có chi phí xử lý lớn giữa các giao dịch.
  6. Tạo một phần của ứng dụng @Transactional thay vì chỉ DAO
    • Các cân nhắc có thể xảy ra đối với các giao dịch lồng nhau
    • Yêu cầu các quy trình được điều chỉnh để quản lý giao dịch (ví dụ: vừa đủ nhỏ)
    • Tác động có lập trình nhỏ, mặc dù có thể dẫn đến các giao dịch lớn
  7. Cung cấp cho DAO các cấu hình tìm nạp động , ví dụ:loadData(id, fetchProfile);
    • Ứng dụng phải biết hồ sơ nào sẽ sử dụng khi
  8. Loại giao dịch AoP, ví dụ: chặn hoạt động và thực hiện giao dịch khi cần thiết
    • Yêu cầu thao tác mã byte hoặc sử dụng proxy
    • Mất kiểm soát khi giao dịch được thực hiện
    • Ma thuật đen, như mọi khi :)

Tôi có bỏ lỡ bất kỳ lựa chọn nào không?


Cách tiếp cận ưa thích của bạn là gì khi cố gắng giảm thiểu tác động của các lazy-loadedmối quan hệ trong thiết kế ứng dụng của bạn?

(Ồ, và xin lỗi WoT )


ví dụ cho tùy chọn 2 & 5: m-hewedy.blogspot.ch/2010/03/…
Adrien Be

Bạn có thể vui lòng cung cấp một ví dụ cho tùy chọn 4?
Degightdc

Câu trả lời:


26

Như chúng ta đã biết, chế độ ngủ đông cố gắng không xâm lấn và càng minh bạch càng tốt

Tôi sẽ nói rằng giả định ban đầu là sai. Độ bền xuyên suốt là một huyền thoại, vì ứng dụng luôn phải quan tâm đến vòng đời của thực thể và kích thước của đồ thị đối tượng đang được tải.

Lưu ý rằng Hibernate không thể đọc suy nghĩ, do đó nếu bạn biết rằng bạn cần một tập hợp phụ thuộc cụ thể cho một hoạt động cụ thể, bạn cần thể hiện ý định của mình với Hibernate bằng cách nào đó.

Từ quan điểm này, các giải pháp thể hiện những ý định này một cách rõ ràng (cụ thể là 2, 4 và 7) có vẻ hợp lý và không bị thiếu minh bạch.


Tất nhiên, bạn đúng, càng minh bạch càng tốt chỉ hoạt động cho đến nay. Đó là một số lựa chọn tốt mà bạn đã chọn.
Johan Sjöberg

IMHO: câu trả lời hoàn toàn chính xác. Quả thực, đó là một huyền thoại. BTW: phiếu bầu của tôi sẽ dành cho các lựa chọn 4 và 7 (hoặc rời khỏi ORM chút nào)
G. Demecki

7

Tôi không chắc bạn đang ám chỉ vấn đề nào (gây ra bởi sự lười biếng), nhưng đối với tôi, nỗi đau lớn nhất là tránh làm mất ngữ cảnh phiên trong bộ đệm ứng dụng của riêng tôi. Trường hợp điển hình:

  • đối tượng foođược tải và đưa vào bản đồ;
  • một luồng khác lấy đối tượng này từ bản đồ và gọi foo.getBar()(một cái gì đó chưa bao giờ được gọi trước đây và được đánh giá lười biếng);
  • bùm!

Vì vậy, để giải quyết vấn đề này, chúng tôi có một số quy tắc:

  • gói các phiên một cách minh bạch nhất có thể (ví dụ: OpenSessionInViewFilterđối với các ứng dụng web);
  • có API chung cho luồng / nhóm luồng trong đó ràng buộc / hủy liên kết phiên db được thực hiện ở đâu đó cao trong hệ thống phân cấp (được bao bọc trong try/finally) để các lớp con không phải nghĩ về nó;
  • khi truyền các đối tượng giữa các luồng, hãy truyền ID thay vì chính các đối tượng. Chủ đề nhận có thể tải đối tượng nếu nó cần;
  • khi lưu vào bộ đệm các đối tượng, không bao giờ lưu vào bộ đệm các đối tượng trừ id của chúng. Có một phương thức trừu tượng trong DAO hoặc lớp người quản lý của bạn để tải đối tượng từ bộ đệm Hibernate cấp 2 khi bạn biết ID. Chi phí truy xuất các đối tượng từ bộ đệm Hibernate cấp 2 vẫn rẻ hơn nhiều so với việc truy cập vào DB.

Điều này, như bạn có thể thấy, thực sự không có nơi nào gần với không xâm lấn và minh bạch . Nhưng chi phí vẫn có thể chịu được, so với cái giá mà tôi phải trả cho việc tải hàng háo hức. Vấn đề với thứ hai là đôi khi nó dẫn đến hiệu ứng cánh bướm khi tải đối tượng được tham chiếu đơn lẻ, chưa nói đến một tập hợp các thực thể. Mức tiêu thụ bộ nhớ, mức sử dụng CPU và độ trễ ít nhất cũng kém hơn nhiều, vì vậy tôi đoán tôi có thể sống chung với nó.


Cảm ơn vì đã trả lời. Sự mất mát transparencylà từ việc buộc ứng dụng quan tâm đến việc tải các đối tượng lười biếng. Nếu mọi thứ được tìm nạp một cách háo hức, ứng dụng thể hoàn toàn không biết liệu các đối tượng có được lưu giữ trong cơ sở dữ liệu hay không, vì Foo.getBar()sẽ luôn thành công. > when passing objects between threads, pass IDs, vâng, điều này sẽ tương ứng với # 5.
Johan Sjöberg

3

Một mô hình rất phổ biến là sử dụng OpenEntityManagerInViewFilter nếu bạn đang xây dựng một ứng dụng web.

Nếu bạn đang xây dựng một dịch vụ, tôi sẽ mở TX trên phương thức công khai của dịch vụ, thay vì trên DAO, vì một phương thức thường yêu cầu lấy hoặc cập nhật một số thực thể.

Điều này sẽ giải quyết bất kỳ "ngoại lệ Lazy Load" nào. Nếu bạn cần một cái gì đó nâng cao hơn để điều chỉnh hiệu suất, tôi nghĩ tìm nạp cấu hình là cách tốt nhất.


1
Tôi đoán bạn muốn nói: Một antipattern rất phổ biến ... . Mặc dù tôi đồng ý với việc mở TX ở cấp độ dịch vụ, nhưng việc sử dụng hàm OSIVnày vẫn là một phản vật chất và dẫn đến các vấn đề rất nghiêm trọng như không thể xử lý khéo léo với các ngoại lệ hoặc giảm hiệu suất. Tóm lại: IMHO OSIV là một giải pháp dễ sử dụng, nhưng chỉ tốt cho các dự án đồ chơi.
G. Demecki
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.