ไฮเบอร์เนตการออกแบบแอปพลิเคชันแบบโหลดขี้เกียจ


87

ฉันมักจะใช้Hibernateร่วมกับSpring framework และเป็นความสามารถในการแบ่งเขตธุรกรรมแบบเปิดเผย (เช่น@Transactional )

อย่างที่เราทุกคนทราบกันดีว่าไฮเบอร์เนตพยายามที่จะไม่รุกรานและโปร่งใสที่สุดเท่าที่จะเป็นไปได้อย่างไรก็ตามสิ่งนี้พิสูจน์ได้ว่าท้าทายกว่าเล็กน้อยเมื่อใช้lazy-loadedความสัมพันธ์


ฉันเห็นทางเลือกในการออกแบบจำนวนมากที่มีระดับความโปร่งใสแตกต่างกัน

  1. ทำให้ความสัมพันธ์ไม่ขี้เกียจโหลด (เช่น fetchType=FetchType.EAGER)
    • นี่มันม่วงทั้งไอเดียขี้เกียจโหลด ..
  2. เริ่มต้นคอลเลกชันโดยใช้ Hibernate.initialize(proxyObj);
    • นี่หมายถึงการมีเพศสัมพันธ์ที่ค่อนข้างสูงกับ DAO
    • แม้ว่าเราสามารถกำหนดอินเทอร์เฟซได้initializeแต่การใช้งานอื่น ๆ ก็ไม่รับประกันว่าจะให้สิ่งที่เทียบเท่า
  3. เพิ่มลักษณะการทำธุรกรรมให้กับModelอ็อบเจ็กต์ถาวร(โดยใช้พร็อกซีแบบไดนามิกหรือ@Transactional )
    • ฉันไม่ได้ลองใช้วิธีพร็อกซีแบบไดนามิกแม้ว่าฉันจะไม่เคยให้ @Transactional ทำงานกับวัตถุถาวรด้วยตัวเอง อาจเป็นเพราะการจำศีลนั้นกำลังดำเนินการกับพร็อกซีที่จะเข้ามา
    • สูญเสียการควบคุมเมื่อมีการทำธุรกรรมเกิดขึ้นจริง
  4. ระบุทั้ง lazy / non-lazy API เช่นloadData()และloadDataWithDeps()
    • บังคับให้แอปพลิเคชันรู้ว่าเมื่อใดควรใช้รูทีนใดการมีเพศสัมพันธ์ที่แน่นหนาอีกครั้ง
    • วิธีล้นloadDataWithA(),, .... ,loadDataWithX()
  5. บังคับให้ค้นหาการอ้างอิงเช่นโดยจัดเตรียมbyId()การดำเนิน การเท่านั้น
    • ต้องใช้กิจวัตรที่ไม่ใช่เชิงวัตถุจำนวนมากเช่นfindZzzById(zid)แล้วgetYyyIds(zid)แทนที่จะเป็นz.getY()
    • การดึงแต่ละวัตถุในคอลเลกชันทีละรายการจะเป็นประโยชน์หากมีค่าใช้จ่ายในการประมวลผลระหว่างธุรกรรมจำนวนมาก
  6. สร้างส่วนหนึ่งของแอปพลิเคชัน @Transactional แทนDAOเท่านั้น
    • ข้อควรพิจารณาที่เป็นไปได้ของธุรกรรมที่ซ้อนกัน
    • ต้องใช้กิจวัตรที่ปรับให้เหมาะกับการจัดการธุรกรรม (เช่นเล็กน้อยพอเพียง)
    • ผลกระทบทางโปรแกรมเล็กน้อยแม้ว่าอาจทำให้เกิดธุรกรรมจำนวนมาก
  7. จัดเตรียม DAO ด้วยโปรไฟล์การดึงข้อมูลแบบไดนามิกเช่นloadData(id, fetchProfile);
    • แอปพลิเคชันต้องรู้ว่าจะใช้โปรไฟล์ใดเมื่อใด
  8. ประเภทของธุรกรรม AoP เช่นสกัดกั้นการดำเนินการและทำธุรกรรมเมื่อจำเป็น
    • ต้องมีการจัดการรหัสไบต์หรือการใช้พร็อกซี
    • สูญเสียการควบคุมเมื่อมีการทำธุรกรรม
    • มนต์ดำเช่นเคย :)

ฉันพลาดตัวเลือกใดไปหรือไม่?


ซึ่งเป็นแนวทางที่คุณต้องการเมื่อพยายามลดผลกระทบของ lazy-loadedความสัมพันธ์ในการออกแบบแอปพลิเคชันของคุณ

(โอ้และขอโทษสำหรับWoT )


ตัวอย่างตัวเลือก 2 และ 5: m-hewedy.blogspot.ch/2010/03/…
Adrien Be

คุณช่วยยกตัวอย่างตัวเลือก 4 ได้ไหม
degreesightdc

คำตอบ:


26

อย่างที่เราทราบกันดีว่าไฮเบอร์เนตพยายามที่จะไม่รุกรานและโปร่งใสที่สุดเท่าที่จะทำได้

ฉันจะบอกว่าข้อสันนิษฐานเริ่มต้นนั้นผิด ความคงอยู่ของการเปลี่ยนผ่านเป็นเรื่องเข้าใจได้เนื่องจากแอปพลิเคชันควรดูแลวงจรชีวิตของเอนทิตีและขนาดของกราฟวัตถุที่โหลดอยู่เสมอ

โปรดทราบว่าไฮเบอร์เนตไม่สามารถอ่านความคิดได้ดังนั้นหากคุณรู้ว่าคุณต้องการชุดการอ้างอิงเฉพาะสำหรับการดำเนินการบางอย่างคุณจำเป็นต้องแสดงเจตจำนงต่อไฮเบอร์เนตอย่างใดอย่างหนึ่ง

จากมุมมองนี้โซลูชันที่แสดงเจตนาเหล่านี้อย่างชัดเจน (กล่าวคือ 2, 4 และ 7) ดูสมเหตุสมผลและไม่ต้องทนทุกข์ทรมานจากการขาดความโปร่งใส


แน่นอนว่าคุณคิดถูกแล้วที่โปร่งใสที่สุดเท่าที่จะทำได้เท่านั้น นี่คือตัวเลือกที่ดีที่คุณเลือก
Johan Sjöberg

IMHO: คำตอบที่ถูกต้องสมบูรณ์ แท้จริงมันคือตำนาน BTW: การโหวตของฉันคือสำหรับตัวเลือก 4 และ 7 (หรือย้ายออกจาก ORM เลย)
G.Demecki

7

ฉันไม่แน่ใจว่าปัญหาใด (เกิดจากความขี้เกียจ) ที่คุณกำลังบอกใบ้ แต่สำหรับฉันความเจ็บปวดที่ยิ่งใหญ่ที่สุดคือการหลีกเลี่ยงการสูญเสียบริบทเซสชันในแคชแอปพลิเคชันของฉันเอง กรณีทั่วไป:

  • วัตถุfooถูกโหลดและใส่ลงในแผนที่
  • เธรดอื่นนำวัตถุนี้จากแผนที่และเรียกfoo.getBar()(สิ่งที่ไม่เคยเรียกมาก่อนและขี้เกียจประเมิน)
  • ตูม!

ดังนั้นเพื่อแก้ไขปัญหานี้เรามีกฎหลายประการ:

  • สรุปเซสชันอย่างโปร่งใสที่สุด (เช่น OpenSessionInViewFilterสำหรับเว็บแอป)
  • มี API ทั่วไปสำหรับเธรด / เธรดพูลที่เซสชัน db bind / unbind ถูกทำที่ใดที่หนึ่งในลำดับชั้นสูง (รวมอยู่ใน try/finally ) ดังนั้นคลาสย่อยจึงไม่ต้องคิดเกี่ยวกับมัน
  • เมื่อส่งผ่านอ็อบเจ็กต์ระหว่างเธรดให้ส่ง ID แทนอ็อบเจ็กต์ด้วยกันเอง การรับเธรดสามารถโหลดอ็อบเจ็กต์ได้หากต้องการ;
  • เมื่อแคชวัตถุอย่าแคชวัตถุ แต่เป็นรหัสของพวกเขา มีเมธอดนามธรรมในคลาส DAO หรือตัวจัดการของคุณเพื่อโหลดอ็อบเจ็กต์จากแคชไฮเบอร์เนตระดับ 2 เมื่อคุณทราบ ID ค่าใช้จ่ายในการดึงอ็อบเจ็กต์จากแคชไฮเบอร์เนตระดับ 2 ยังคงถูกกว่าการไปที่ DB มาก

นี้ที่คุณสามารถดูแน่นอนไม่มีที่ไหนเลยใกล้กับการไม่รุกรานและโปร่งใส แต่ค่าใช้จ่ายยังคงรับได้เมื่อเทียบกับราคาที่ฉันต้องจ่ายสำหรับการโหลดอย่างกระตือรือร้น ปัญหาหลังคือบางครั้งมันนำไปสู่เอฟเฟกต์ผีเสื้อเมื่อโหลดอ็อบเจ็กต์ที่อ้างอิงเดี่ยวนับประสาอะไรกับคอลเลกชันของเอนทิตี การใช้หน่วยความจำการใช้งาน CPU และเวลาแฝงที่ต้องพูดถึงอย่างน้อยก็แย่ลงมากดังนั้นฉันเดาว่าฉันสามารถอยู่กับมันได้


ขอบคุณสำหรับการตอบกลับของคุณ. การสูญเสียtransparencyมาจากการบังคับให้แอปพลิเคชันดูแลเกี่ยวกับการโหลดวัตถุที่ขี้เกียจ หากทุกอย่างถูกดึงข้อมูลอย่างกระตือรือร้นแอปพลิเคชันอาจไม่ทราบว่าวัตถุนั้นยังคงอยู่ในฐานข้อมูลหรือไม่เนื่องจากFoo.getBar()จะประสบความสำเร็จเสมอ > when passing objects between threads, pass IDsใช่สิ่งนี้จะสอดคล้องกับ # 5
Johan Sjöberg

3

รูปแบบทั่วไปคือการใช้OpenEntityManagerInViewFilterหากคุณกำลังสร้างเว็บแอปพลิเคชัน

หากคุณกำลังสร้างบริการฉันจะเปิด TX ด้วยวิธีการสาธารณะของบริการแทนที่จะใช้ DAO ซึ่งบ่อยครั้งที่ต้องใช้วิธีการเพื่อรับหรืออัปเดตเอนทิตีหลายรายการ

วิธีนี้จะแก้ปัญหา "ข้อยกเว้นการโหลดขี้เกียจ" หากคุณต้องการสิ่งที่ทันสมัยกว่านี้สำหรับการปรับแต่งประสิทธิภาพฉันคิดว่าการดึงข้อมูลโปรไฟล์เป็นวิธีที่จะไป


1
ผมคิดว่าคุณอยากจะบอกว่า: antipattern ที่พบบ่อยมาก ... แม้ว่าฉันจะเห็นด้วยกับการเปิด TX ในระดับบริการ แต่การใช้งานOSIVยังคงเป็นการต่อต้านและนำไปสู่ปัญหาร้ายแรงเช่นไม่สามารถจัดการกับข้อยกเว้นหรือการลดประสิทธิภาพได้อย่างสง่างาม สรุป: IMHO OSIV เป็นวิธีแก้ปัญหาที่ง่าย แต่เหมาะสำหรับโครงการของเล่นเท่านั้น
G.Demecki
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.