อะไรคือความแตกต่างระหว่างวิธีการประหยัดที่แตกต่างกันใน Hibernate


199

Hibernate มีวิธีการหลายวิธีที่จะนำวัตถุของคุณไปไว้ในฐานข้อมูล อะไรคือความแตกต่างระหว่างพวกเขาเมื่อไรที่จะใช้และทำไมไม่มีวิธีการอันชาญฉลาดเพียงวิธีเดียวที่รู้ว่าเมื่อไรที่จะใช้อะไร

วิธีการที่ฉันได้ระบุไว้คือ:

  • save()
  • update()
  • saveOrUpdate()
  • saveOrUpdateCopy()
  • merge()
  • persist()

คำตอบ:


117

นี่คือความเข้าใจของฉันของวิธีการ ส่วนใหญ่สิ่งเหล่านี้จะขึ้นอยู่กับAPIแต่เนื่องจากฉันไม่ได้ใช้สิ่งเหล่านี้ในทางปฏิบัติ

saveOrUpdate การ โทรบันทึกหรืออัพเดตขึ้นอยู่กับการตรวจสอบบางอย่าง เช่นถ้าไม่มีตัวบ่งชี้บันทึกจะถูกเรียก มิฉะนั้นจะเรียกว่าการอัปเดต

ประหยัด ยังคงมีกิจการ จะกำหนดตัวระบุหากไม่มีอยู่ ถ้ามีใครแสดงว่ามันกำลังอัพเดทอยู่ ส่งคืน ID ที่สร้างของเอนทิตี

อัปเดต ความพยายามที่จะยืนยันเอนทิตีโดยใช้ตัวระบุที่มีอยู่ หากไม่มีตัวระบุอยู่ฉันเชื่อว่ามีข้อผิดพลาดเกิดขึ้น

saveOrUpdateCopy สิ่งนี้เลิกใช้แล้วและไม่ควรใช้อีกต่อไป แทนที่จะมี ...

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

persist ดังกล่าวข้างต้นนี้จะใช้กับวัตถุชั่วคราว มันจะไม่ส่งคืน ID ที่สร้างขึ้น


22
ฉันต้องการยอมรับสิ่งนี้เป็นคำตอบ แต่สิ่งหนึ่งที่ยังไม่ชัดเจน: เนื่องจาก save () กลับมาอัปเดต () หากมีรายการดังกล่าวอยู่ในทางปฏิบัติแล้ว saveOrUpdate () แตกต่างกันอย่างไร
Henrik Paul

มีการระบุไว้ที่ไหนบันทึกนั้นจะทำงานในอินสแตนซ์ที่แยกออก
jrudolph

2
หากคำอธิบายของคุณเกี่ยวกับการผสาน / คงอยู่เพียง แต่มีความสำคัญกับวัตถุชั่วคราวสิ่งนี้จะทำให้รู้สึกเข้าท่าและเหมาะสมกับวิธีที่เราใช้ไฮเบอร์เนต นอกจากนี้โปรดทราบว่าการผสานมักจะมีข้อ จำกัด ด้านประสิทธิภาพเมื่อเทียบกับการอัปเดตเนื่องจากดูเหมือนว่าจะดึงข้อมูลเพิ่มเติมสำหรับการตรวจสอบความสมบูรณ์ของการจัดเรียงบางอย่าง
Martin Dale Lyness

1
คำตอบโดย jrudolph ด้านล่างมีความแม่นยำมากขึ้น
azerole

2
ได้รับจำศีลอาจรู้ว่าวัตถุอยู่ในสถานะทำไมเราต้องทำด้วยตนเองเมื่อเขียนโปรแกรม ควรมีวิธีการบันทึกเพียงวิธีเดียว
masterxilo

116
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
    METHOD                TRANSIENT                      DETACHED            
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id if doesn't         sets new id even if object   
    save()         exist, persists to db,        already has it, persists    
                  returns attached object     to DB, returns attached object 
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id on object                    throws             
   persist()       persists object to DB            PersistenceException     
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
   update()              Exception                persists and reattaches    
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                copy the state of object in      copy the state of obj in    
    merge()        DB, doesn't attach it,    ║      DB, doesn't attach it,    
                  returns attached object         returns attached object    
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
saveOrUpdate()║           as save()                       as update()         
                                                                             
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝

updateวัตถุชั่วคราวดีฉันไม่ได้รับการยกเว้น
GMsoF

สิ่งที่ฉันรู้เราไม่สามารถคงอยู่ชั่วคราวในทั้งสองทาง ฉันคิดว่าความแตกต่างอาจอยู่ระหว่างเดี่ยวและถาวร โปรดแก้ไขฉัน
Ram

มีข้อผิดพลาดมากมายที่นี่ ... เช่น 1)´save () ´ไม่ส่งคืน "วัตถุที่แนบ" มันจะส่งกลับ theid´; 2)´persist () ´ไม่รับประกันว่าจะตั้ง´id´ ไม่ว่าจะเป็น "ยังคงคัดค้านฐานข้อมูล" ...
Eugen Labun

67
  • ดูฟอรัมไฮเบอร์เนตเพื่อดูคำอธิบายของความแตกต่างเล็กน้อยระหว่างการคงอยู่และการบันทึก ดูเหมือนความแตกต่างคือเวลาที่คำสั่ง INSERT จะถูกดำเนินการในที่สุด เนื่องจากการบันทึกส่งคืนตัวบ่งชี้คำสั่ง INSERT จะต้องถูกดำเนินการทันทีโดยไม่คำนึงถึงสถานะของการทำธุรกรรม (ซึ่งโดยทั่วไปจะเป็นสิ่งที่ไม่ดี) ยังคงมีอยู่จะไม่ดำเนินการคำสั่งใด ๆ นอกเหนือจากการทำธุรกรรมที่กำลังทำงานอยู่เพียงเพื่อกำหนดตัวระบุ บันทึก / คงอยู่ทั้งสองทำงานบนอินสแตนซ์ชั่วคราวเช่นอินสแตนซ์ที่ยังไม่มีตัวระบุที่กำหนดและเช่นนั้นจะไม่ถูกบันทึกในฐานข้อมูล

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


คำสั่งแทรกยังไม่เกิดขึ้นหากคุณมีเครื่องกำเนิดไฟฟ้า Id ของคุณเอง
kommradHomer

ดังนั้นในกรณีที่วัตถุเดี่ยว ๆ ผสานจะทำให้เกิดการเลือกในขณะที่การปรับปรุงจะไม่?
Gab

1
save() - If an INSERT has to be executed to get the identifier, then this INSERT happens immediately, no matter if you are inside or outside of a transaction. This is problematic in a long-running conversation with an extended Session/persistence context.คุณช่วยบอกฉันทีว่าการแทรกเกิดขึ้นนอกเซสชันได้อย่างไรและทำไมมันถึงไม่ดี?
Erran Morad

คำเตือน: ฉันไม่ได้ใช้ไฮเบอร์เนตเป็นเวลานาน IMO ปัญหาคือ: ลายเซ็นและสัญญาการบันทึก () ต้องการให้การบันทึกส่งคืนตัวระบุสำหรับวัตถุใหม่ ขึ้นอยู่กับกลยุทธ์การสร้างรหัสที่คุณเลือกตัวระบุจะถูกสร้างขึ้นโดย DB เมื่อค่าถูกINSERTแก้ไข ดังนั้นในกรณีที่คุณไม่สามารถกลับตัวระบุในขณะนี้โดยไม่ต้องมีมันสร้างขึ้นและเพื่อสร้างมันคุณจะต้องมีการทำงานในขณะนี้INSERT เนื่องจากธุรกรรมที่ดำเนินมายาวนานไม่ได้ดำเนินการในขณะนี้แต่มีเพียงการส่งมอบวิธีเดียวที่จะดำเนินการในINSERTตอนนี้คือการดำเนินการนอก tx
jrudolph

12

ลิงก์นี้อธิบายอย่างดี:

http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/

เราทุกคนมีปัญหาเหล่านั้นที่เราพบบ่อยครั้งพอที่เมื่อเราเห็นพวกเขาอีกครั้งเรารู้ว่าเราได้แก้ไขปัญหานี้ แต่จำไม่ได้ว่า

NonUniqueObjectException ที่ส่งออกมาเมื่อใช้ Session.saveOrUpdate () ใน Hibernate เป็นหนึ่งในของฉัน ฉันจะเพิ่มฟังก์ชันการทำงานใหม่ให้กับแอปพลิเคชันที่ซับซ้อน การทดสอบหน่วยของฉันทั้งหมดทำงานได้ดี จากนั้นในการทดสอบ UI พยายามบันทึกวัตถุฉันเริ่มได้รับการยกเว้นด้วยข้อความ“ วัตถุอื่นที่มีค่าตัวระบุเดียวกันเชื่อมโยงกับเซสชันแล้ว” นี่คือตัวอย่างโค้ดจาก Java Persistence with Hibernate

            Session session = sessionFactory1.openSession();
            Transaction tx = session.beginTransaction();
            Item item = (Item) session.get(Item.class, new Long(1234));
            tx.commit();
            session.close(); // end of first session, item is detached

            item.getId(); // The database identity is "1234"
            item.setDescription("my new description");
            Session session2 = sessionFactory.openSession();
            Transaction tx2 = session2.beginTransaction();
            Item item2 = (Item) session2.get(Item.class, new Long(1234));
            session2.update(item); // Throws NonUniqueObjectException
            tx2.commit();
            session2.close();

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

เมื่อเราปิด Hibernate Session แต่ละรายการวัตถุถาวรที่เรากำลังทำงานด้วยจะถูกแยกออก ซึ่งหมายความว่าข้อมูลยังอยู่ในหน่วยความจำของแอปพลิเคชัน แต่ไฮเบอร์เนตจะไม่รับผิดชอบต่อการติดตามการเปลี่ยนแปลงของวัตถุ

หากเราปรับเปลี่ยนวัตถุที่แยกออกและต้องการอัปเดตเราต้องทำการติดตั้งวัตถุอีกครั้ง ในระหว่างขั้นตอนการติดตั้งใหม่ไฮเบอร์เนตจะตรวจสอบเพื่อดูว่ามีสำเนาอื่น ๆ ของวัตถุเดียวกันหรือไม่ หากพบสิ่งใดต้องบอกเราว่าไม่ทราบว่าสำเนา "ของจริง" มีอะไรอีก อาจมีการเปลี่ยนแปลงอื่น ๆ กับสำเนาอื่น ๆ ที่เราคาดว่าจะได้รับการบันทึก แต่ไฮเบอร์เนตไม่ทราบเกี่ยวกับพวกเขาเพราะมันไม่ได้จัดการพวกเขาในเวลา

แทนที่จะบันทึกข้อมูลที่ไม่ดีไฮเบอร์เนตจะบอกเราเกี่ยวกับปัญหาผ่านทาง NonUniqueObjectException

แล้วเราจะทำอย่างไรดี? ใน Hibernate 3 เรามีการรวม () (ใน Hibernate 2 ให้ใช้ saveOrUpdateCopy ()) วิธีนี้จะบังคับให้ไฮเบอร์เนตคัดลอกการเปลี่ยนแปลงใด ๆ จากอินสแตนซ์เดี่ยวอื่น ๆ ไปยังอินสแตนซ์ที่คุณต้องการบันทึกและรวมการเปลี่ยนแปลงทั้งหมดในหน่วยความจำก่อนที่จะบันทึก

        Session session = sessionFactory1.openSession();
        Transaction tx = session.beginTransaction();
        Item item = (Item) session.get(Item.class, new Long(1234));
        tx.commit();
        session.close(); // end of first session, item is detached

        item.getId(); // The database identity is "1234"
        item.setDescription("my new description");
        Session session2 = sessionFactory.openSession();
        Transaction tx2 = session2.beginTransaction();
        Item item2 = (Item) session2.get(Item.class, new Long(1234));
        Item item3 = session2.merge(item); // Success!
        tx2.commit();
        session2.close();

โปรดทราบว่าการผสานจะส่งคืนการอ้างอิงไปยังอินสแตนซ์รุ่นที่อัปเดตใหม่ มันไม่ได้ทำการแนบไอเท็มเข้ากับเซสชันอีกครั้ง หากคุณทดสอบความเท่าเทียมกันของอินสแตนซ์ (item == item3) คุณจะพบว่ามันคืนค่าเท็จในกรณีนี้ คุณอาจต้องการทำงานกับรายการ 3 จากจุดนี้ไปข้างหน้า

สิ่งสำคัญคือต้องทราบว่า Java Persistence API (JPA) ไม่มีแนวคิดเกี่ยวกับวัตถุที่แยกออกและใส่กลับเข้าไปใหม่และใช้ EntityManager.persist () และ EntityManager.merge ()

ฉันพบโดยทั่วไปว่าเมื่อใช้ Hibernate แล้ว saveOrUpdate () จะเพียงพอสำหรับความต้องการของฉัน ฉันมักจะต้องใช้ผสานเมื่อฉันมีวัตถุที่สามารถมีการอ้างอิงถึงวัตถุประเภทเดียวกัน ล่าสุดสาเหตุของข้อยกเว้นอยู่ในรหัสตรวจสอบว่าข้อมูลอ้างอิงไม่ซ้ำ ฉันกำลังโหลดวัตถุเดียวกันในเซสชันของฉันซึ่งเป็นส่วนหนึ่งของการตรวจสอบความถูกต้องทำให้เกิดข้อผิดพลาด

คุณพบปัญหานี้ที่ไหน ผสานทำงานให้คุณหรือคุณต้องการโซลูชันอื่นหรือไม่? คุณต้องการใช้การผสานเสมอหรือต้องการใช้เฉพาะในกรณีที่จำเป็นเท่านั้น


ลิงก์ไปยังบทความเกี่ยวกับ webarchive เนื่องจากไม่มีต้นฉบับ: web.archive.org/web/20160521091122/http://www.stevideter.com:80/…
Eugen Labun

5

ความแตกต่างระหว่างไฮเบอร์เนตsave()กับpersist()เมธอดขึ้นอยู่กับคลาสของเครื่องกำเนิดไฟฟ้าที่เราใช้

ถ้าคลาสตัวกำเนิดของเราถูกกำหนดดังนั้นจะไม่มีความแตกต่างระหว่างsave()และpersist() วิธีการ เนื่องจากเครื่องกำเนิด 'กำหนด' หมายถึงในฐานะโปรแกรมเมอร์เราจำเป็นต้องให้ค่าคีย์หลักเพื่อบันทึกในฐานข้อมูลที่ถูกต้อง [หวังว่าคุณจะรู้แนวคิดเครื่องกำเนิดไฟฟ้านี้] ในกรณีอื่นนอกเหนือจากระดับเครื่องกำเนิดไฟฟ้าที่กำหนดให้สมมติว่าชื่อระดับเครื่องกำเนิดของเราเพิ่มขึ้น จำศีลด้วยตนเองจะกำหนดค่ารหัสคีย์หลักในฐานข้อมูลด้านขวา [อื่น ๆ นอกเหนือจากเครื่องกำเนิดที่ได้รับมอบหมายจำศีลใช้เพียงเพื่อดูแลค่ารหัสคีย์หลักจำ] ดังนั้นในกรณีนี้ถ้าเราเรียกsave()หรือpersist()วิธีการแล้วมันจะแทรกบันทึกลงใน ฐานข้อมูลตามปกติ แต่สิ่งที่ได้ยินคือ save()วิธีสามารถคืนค่ารหัสคีย์หลักที่สร้างขึ้นโดยจำศีลและเราสามารถดูได้โดย

long s = session.save(k);

ในกรณีเดียวกันนี้persist()จะไม่ให้ค่าใด ๆ กลับไปยังลูกค้า


5

ฉันพบตัวอย่างที่ดีที่แสดงความแตกต่างระหว่างวิธีการบันทึกการจำศีลทั้งหมด:

http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example

โดยสังเขปตามลิงค์ด้านบน:

บันทึก ()

  • เราสามารถเรียกใช้วิธีนี้นอกธุรกรรม หากเราใช้สิ่งนี้โดยไม่มีการทำธุรกรรมและเรามีการเชื่อมโยงระหว่างหน่วยงานดังนั้นเฉพาะหน่วยงานหลักที่ได้รับการบันทึกเว้นแต่ว่าเราจะทำการล้างข้อมูล
  • ดังนั้นหากมีวัตถุอื่นที่แมปจากวัตถุหลักวัตถุเหล่านั้นจะถูกบันทึกในเวลาที่ทำธุรกรรมหรือเมื่อเราล้างข้อมูลในเซสชัน

ยังคงมีอยู่ ()

  • มันคล้ายกับการใช้ save () ในการทำธุรกรรมดังนั้นจึงปลอดภัยและดูแลวัตถุที่เรียงซ้อน

SaveOrUpdate ()

  • สามารถใช้กับหรือไม่มีธุรกรรมและเช่นเดียวกับ save () หากใช้โดยไม่มีธุรกรรมหน่วยงานที่แมปจะไม่ถูกบันทึก un; ess เราจะล้างเซสชั่น

  • ผลลัพธ์ในการแทรกหรือปรับปรุงแบบสอบถามตามข้อมูลที่ให้ไว้ หากมีข้อมูลอยู่ในฐานข้อมูลจะมีการดำเนินการคิวรีการอัพเดท

ปรับปรุง ()

  • ควรใช้การอัปเดตไฮเบอร์เนตที่เรารู้ว่าเรากำลังอัปเดตข้อมูลเอนทิตีเท่านั้น การดำเนินการนี้เพิ่มออบเจ็กต์เอนทิตีไปยังบริบทถาวรและมีการติดตามและบันทึกการเปลี่ยนแปลงเพิ่มเติมเมื่อมีการทำธุรกรรม
  • ดังนั้นแม้หลังจากเรียกการอัพเดทหากเราตั้งค่าใด ๆ ในเอนทิตีพวกเขาจะได้รับการปรับปรุงเมื่อมีการทำธุรกรรม

ผสาน()

  • ไฮเบอร์เนตผสานสามารถใช้ในการปรับปรุงค่าที่มีอยู่อย่างไรก็ตามวิธีนี้สร้างสำเนาจากวัตถุนิติบุคคลผ่านและส่งกลับ วัตถุที่ส่งคืนเป็นส่วนหนึ่งของบริบทถาวรและติดตามการเปลี่ยนแปลงใด ๆ วัตถุที่ส่งผ่านจะไม่ถูกติดตาม นี่คือความแตกต่างที่สำคัญกับการผสาน () จากวิธีอื่นทั้งหมด

นอกจากนี้สำหรับตัวอย่างที่ใช้งานได้จริงของสิ่งเหล่านี้โปรดดูที่ลิงก์ที่กล่าวถึงข้างต้นซึ่งแสดงตัวอย่างสำหรับวิธีการที่แตกต่างกัน


3

ดังที่ฉันได้อธิบายไว้ในบทความนี้คุณควรชอบวิธีการ JPA เป็นส่วนใหญ่และupdateสำหรับการประมวลผลแบบแบตช์

กิจการ JPA หรือไฮเบอร์เนตสามารถอยู่ในสถานะใดสถานะหนึ่งในสี่สถานะต่อไปนี้:

  • ชั่วคราว (ใหม่)
  • จัดการ (ต่อเนื่อง)
  • สันโดษ
  • ลบออก (ถูกลบ)

การเปลี่ยนจากสถานะหนึ่งไปอีกสถานะหนึ่งทำได้โดยใช้วิธี EntityManager หรือเซสชัน

ตัวอย่างเช่น JPA EntityManagerจัดเตรียมวิธีการเปลี่ยนสถานะเอนทิตีต่อไปนี้

ป้อนคำอธิบายรูปภาพที่นี่

ไฮเบอร์เนตSessionดำเนินการทั้งหมด JPA EntityManagerวิธีการและมีวิธีการของรัฐนิติบุคคลการเปลี่ยนแปลงบางอย่างเพิ่มเติมเช่นsave, และsaveOrUpdateupdate

ป้อนคำอธิบายรูปภาพที่นี่

สู้

ในการเปลี่ยนสถานะของกิจการจาก Transient (ใหม่) เพื่อการจัดการ (ยืนยัน) เราสามารถใช้persistวิธีการที่นำเสนอโดย JPA ซึ่งเป็นที่สืบเชื้อสายมาจากไฮเบอร์เนตEntityManagerSession

persistวิธีการทริกเกอร์PersistEventซึ่งเป็นที่จัดการโดยDefaultPersistEventListenerฟังเหตุการณ์ Hibernate

ดังนั้นเมื่อดำเนินการกรณีทดสอบต่อไปนี้:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    LOGGER.info(
        "Persisting the Book entity with the id: {}", 
        book.getId()
    );
});

ไฮเบอร์เนตสร้างคำสั่ง SQL ต่อไปนี้:

CALL NEXT VALUE FOR hibernate_sequence

-- Persisting the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

ขอให้สังเกตว่าidได้รับมอบหมายก่อนที่จะแนบBookนิติบุคคลไปยังบริบทการมีอยู่ปัจจุบัน สิ่งนี้จำเป็นเนื่องจากเอนทิตีที่ได้รับการจัดการถูกเก็บไว้ในMapโครงสร้างที่คีย์ถูกสร้างขึ้นตามชนิดเอนทิตีและตัวระบุและค่าเป็นการอ้างอิงเอนทิตี นี่คือเหตุผลว่าทำไม JPA EntityManagerและไฮเบอร์เนตSessionจึงเป็นที่รู้จักในฐานะแคชระดับแรก

เมื่อโทรpersistกิจการจะแนบเฉพาะกับ Persistence Context ที่กำลังทำงานอยู่เท่านั้นและสามารถเลื่อน INSERT จนกว่าflushจะมีการเรียก

ข้อยกเว้นเพียงอย่างเดียวคือตัวสร้างIDENTITYซึ่งเรียกใช้ INSERT ทันทีเนื่องจากเป็นวิธีเดียวที่จะได้รับตัวระบุเอนทิตี ด้วยเหตุนี้ไฮเบอร์เนตจึงไม่สามารถแทรกแบตช์สำหรับเอนทิตีที่ใช้เครื่องกำเนิด IDENTITY สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับหัวข้อนี้ตรวจสอบบทความนี้

บันทึก

saveวิธีเฉพาะของไฮเบอร์เนตจะนำหน้า JPA และใช้ได้ตั้งแต่จุดเริ่มต้นของโครงการไฮเบอร์เนต

saveวิธีการทริกเกอร์SaveOrUpdateEventซึ่งเป็นที่จัดการโดยDefaultSaveOrUpdateEventListenerฟังเหตุการณ์ Hibernate ดังนั้นsaveวิธีการดังกล่าวจะเทียบเท่ากับupdateและsaveOrUpdateวิธีการ

เมื่อต้องการดูวิธีการsaveใช้งานให้พิจารณากรณีทดสอบต่อไปนี้:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);

    Long id = (Long) session.save(book);

    LOGGER.info(
        "Saving the Book entity with the id: {}", 
        id
    );
});

เมื่อเรียกใช้กรณีทดสอบด้านบน Hibernate จะสร้างคำสั่ง SQL ต่อไปนี้:

CALL NEXT VALUE FOR hibernate_sequence

-- Saving the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

อย่างที่คุณเห็นผลลัพธ์จะเหมือนกับpersistการเรียกใช้เมธอด แต่แตกต่างจากpersistที่saveวิธีการส่งกลับระบุกิจการ

สำหรับรายละเอียดเพิ่มเติมโปรดดูบทความนี้

ปรับปรุง

updateวิธีการเฉพาะของไฮเบอร์เนตหมายถึงการเลี่ยงผ่านกลไกการตรวจสอบที่สกปรกและบังคับให้มีการอัพเดตเอนทิตีในเวลาที่ต้องล้างข้อมูล

updateวิธีการทริกเกอร์SaveOrUpdateEventซึ่งเป็นที่จัดการโดยDefaultSaveOrUpdateEventListenerฟังเหตุการณ์ Hibernate ดังนั้นupdateวิธีการดังกล่าวจะเทียบเท่ากับsaveและsaveOrUpdateวิธีการ

ในการดูวิธีupdateการทำงานให้พิจารณาตัวอย่างต่อไปนี้ซึ่งยังคงมีBookเอนทิตีในธุรกรรมเดียวจากนั้นจะทำการปรับเปลี่ยนในขณะที่เอนทิตีอยู่ในสถานะแยกออกและบังคับให้ SQL UPDATE ใช้updateการเรียกใช้เมธอด

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);

    LOGGER.info("Updating the Book entity");
});

เมื่อดำเนินการกรณีทดสอบด้านบน Hibernate จะสร้างคำสั่ง SQL ต่อไปนี้:

CALL NEXT VALUE FOR hibernate_sequence

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity
-- Updating the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

ขอให้สังเกตว่าUPDATEจะมีการดำเนินการในระหว่างการคงอยู่บริบทล้างก่อนที่จะกระทำและนั่นคือเหตุผลที่Updating the Book entityข้อความถูกบันทึกไว้ก่อน

ใช้@SelectBeforeUpdateเพื่อหลีกเลี่ยงการอัพเดตที่ไม่จำเป็น

ตอนนี้ UPDATE จะถูกดำเนินการเสมอแม้ว่าเอนทิตีจะไม่เปลี่ยนแปลงในขณะที่อยู่ในสถานะแยกออก เพื่อป้องกันสิ่งนี้คุณสามารถใช้@SelectBeforeUpdateคำอธิบายประกอบแบบไฮเบอร์เนตซึ่งจะทริกเกอร์SELECTคำสั่งที่นำมาใช้loaded stateซึ่งถูกใช้โดยกลไกการตรวจสอบที่สกปรก

ดังนั้นหากเราใส่คำอธิบายประกอบBookโดยใช้@SelectBeforeUpdateคำอธิบายประกอบ:

@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {

    //Code omitted for brevity
}

และดำเนินการกรณีทดสอบต่อไปนี้:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);
});

ไฮเบอร์เนตดำเนินการคำสั่ง SQL ต่อไปนี้:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

SELECT 
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM 
    book b
WHERE 
    b.id = 1

ขอให้สังเกตว่าในครั้งนี้ไม่มีUPDATEการดำเนินการเนื่องจากกลไกตรวจสอบสกปรกของไฮเบอร์เนตตรวจพบว่าไม่มีการแก้ไขเอนทิตี

SaveOrUpdate

ไฮเบอร์เนตเฉพาะsaveOrUpdateวิธีการเป็นเพียงนามแฝงสำหรับและsaveupdate

saveOrUpdateวิธีการทริกเกอร์SaveOrUpdateEventซึ่งเป็นที่จัดการโดยDefaultSaveOrUpdateEventListenerฟังเหตุการณ์ Hibernate ดังนั้นupdateวิธีการดังกล่าวจะเทียบเท่ากับsaveและsaveOrUpdateวิธีการ

ตอนนี้คุณสามารถใช้saveOrUpdateเมื่อคุณต้องการสานต่อเอนทิตีหรือบังคับให้มีUPDATEภาพประกอบตามตัวอย่างต่อไปนี้

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle("High-Performance Java Persistence, 2nd edition");

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(_book);
});

ระวังของ NonUniqueObjectException

ปัญหาหนึ่งที่อาจเกิดขึ้นกับsave, updateและsaveOrUpdateคือถ้าบริบทการเก็บข้อมูลมีการอ้างอิงเอนทิตีที่มี id เดียวกันและประเภทเดียวกันในตัวอย่างต่อไปนี้:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

try {
    doInJPA(entityManager -> {
        Book book = entityManager.find(
            Book.class, 
            _book.getId()
        );

        Session session = entityManager.unwrap(Session.class);
        session.saveOrUpdate(_book);
    });
} catch (NonUniqueObjectException e) {
    LOGGER.error(
        "The Persistence Context cannot hold " +
        "two representations of the same entity", 
        e
    );
}

ตอนนี้เมื่อดำเนินการกรณีทดสอบข้างต้น Hibernate กำลังจะโยน a NonUniqueObjectExceptionเนื่องจากกรณีที่สองEntityManagerมีBookเอนทิตีที่มีตัวระบุเดียวกับที่เราส่งไปupdateแล้วและบริบทการมีอยู่ไม่สามารถเก็บสองการแสดงของเอนทิตีเดียวกัน

org.hibernate.NonUniqueObjectException: 
    A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
    at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
    at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
    at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)

ผสาน

เพื่อหลีกเลี่ยงการNonUniqueObjectExceptionคุณต้องใช้mergeวิธีการที่เสนอโดย JPA EntityManagerและสืบทอดโดยไฮเบอร์เนตSessionเช่นกัน

ดังที่อธิบายไว้ในบทความนี้การmergeดึงเอนทิตีสแนปชอตใหม่จากฐานข้อมูลหากไม่มีการอ้างอิงเอนทิตีที่พบในบริบทการคงอยู่และจะคัดลอกสถานะของเอนทิตีที่แยกออกไปที่mergeวิธีการ

mergeวิธีการทริกเกอร์MergeEventซึ่งเป็นที่จัดการโดยDefaultMergeEventListenerฟังเหตุการณ์ Hibernate

หากต้องการดูวิธีmergeการทำงานให้พิจารณาตัวอย่างต่อไปนี้ซึ่งยังคงมีBookเอนทิตีในธุรกรรมเดียวจากนั้นจะแก้ไขขณะที่เอนทิตีอยู่ในสถานะแยกออกและส่งเอนทิตีที่แยกออกไปmergeในบริบทการมีอยู่ของเนื้อหา

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Book book = entityManager.merge(_book);

    LOGGER.info("Merging the Book entity");

    assertFalse(book == _book);
});

เมื่อเรียกใช้กรณีทดสอบข้างต้น Hibernate จะดำเนินการคำสั่ง SQL ต่อไปนี้:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity

SELECT 
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM 
    book b
WHERE 
    b.id = 1

-- Merging the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

โปรดสังเกตว่าการอ้างอิงเอนทิตีที่ส่งคืนโดยmergeแตกต่างจากการแยกเดี่ยวที่เราส่งผ่านไปยังmergeเมธอด

ตอนนี้ถึงแม้ว่าคุณควรใช้ JPA mergeเมื่อคัดลอกสถานะเอนทิตีที่แยกออกมาSELECTได้ แต่ปัญหาอาจเกิดจากการประมวลผลแบตช์

ด้วยเหตุผลนี้คุณควรเลือกใช้updateเมื่อคุณแน่ใจว่าไม่มีการอ้างอิงเอนทิตีที่แนบมากับบริบทการคงอยู่ในปัจจุบันที่กำลังดำเนินการอยู่และมีการแก้ไขเอนทิตีที่ดึงออกได้

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับหัวข้อนี้ตรวจสอบบทความนี้

ข้อสรุป

เพื่อคงเอนทิตีคุณควรใช้persistวิธีJPA หากต้องการคัดลอกสถานะนิติบุคคลที่แยกออกมาmergeควรเลือกใช้ updateวิธีการจะเป็นประโยชน์สำหรับงานประมวลผลชุดเท่านั้น saveและsaveOrUpdateเป็นเพียงนามแฝงไปupdateและคุณไม่อาจจะใช้พวกเขาทั้งหมด

นักพัฒนาบางคนเรียกsaveแม้ว่านิติบุคคลได้รับการจัดการแล้ว แต่นี่เป็นข้อผิดพลาดและก่อให้เกิดเหตุการณ์ซ้ำซ้อนตั้งแต่สำหรับองค์กรที่มีการจัดการ UPDATE จะได้รับการจัดการโดยอัตโนมัติในเวลาล้างบริบทการคงอยู่

สำหรับรายละเอียดเพิ่มเติมโปรดดูบทความนี้


2

โปรดระวังว่าถ้าคุณเรียกใช้การปรับปรุงบนวัตถุที่แยกออกมาจะมีการอัปเดตในฐานข้อมูลเสมอไม่ว่าคุณจะเปลี่ยนวัตถุหรือไม่ก็ตาม หากไม่ใช่สิ่งที่คุณต้องการคุณควรใช้ Session.lock () กับ LockMode.None

คุณควรเรียกใช้การปรับปรุงเฉพาะเมื่อวัตถุถูกเปลี่ยนแปลงนอกขอบเขตของเซสชันปัจจุบันของคุณ (เมื่ออยู่ในโหมดเดี่ยว)


1

คำตอบต่อไปนี้ไม่ถูกต้อง วิธีการทั้งหมดนี้ดูเหมือนจะเหมือนกัน แต่ในทางปฏิบัติแล้วจะทำสิ่งที่แตกต่างอย่างสิ้นเชิง เป็นการยากที่จะแสดงความคิดเห็นสั้น ๆ ดีกว่าที่จะให้ลิงก์ไปยังเอกสารฉบับเต็มเกี่ยวกับวิธีการเหล่านี้: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html


11
โปรดบอกเหตุผลที่คำตอบต่อไปนี้ไม่ถูกต้อง
Erran Morad

0

คำตอบข้างต้นไม่สมบูรณ์ แม้ว่า Leo Theobald จะดูคำตอบที่ใกล้ที่สุด

จุดพื้นฐานคือวิธีการจำศีลในการจัดการกับรัฐของหน่วยงานและวิธีการจัดการกับพวกเขาเมื่อมีการเปลี่ยนแปลงสถานะ ทุกสิ่งจะต้องถูกมองด้วยความเคารพต่อการชำระล้างและการผูกมัดเช่นกันซึ่งทุกคนดูเหมือนจะไม่สนใจอย่างสมบูรณ์

ไม่เคยใช้วิธีประหยัดจากการจำศีล ลืมไปเลยว่ามันมีอยู่ในโหมดไฮเบอร์เนต!

สู้

ตามที่ทุกคนอธิบายว่าโดยปกติแล้ว Persist จะเปลี่ยนเอนทิตีจากสถานะ "Transient" เป็น "Managed" ณ จุดนี้โคลนหรือคอมมิชชันสามารถสร้างคำสั่งแทรก แต่เอนทิตีจะยังคงอยู่ในสถานะ "จัดการ" ที่ไม่เปลี่ยนแปลงด้วยเปี่ยม

ณ จุดนี้หากคุณ "คงอยู่" อีกครั้งจะไม่มีการเปลี่ยนแปลง และจะไม่มีการประหยัดอีกต่อไปหากเราพยายามที่จะสานต่อเอนทิตีที่ยังคงอยู่

ความสนุกเริ่มต้นขึ้นเมื่อเราพยายามขับไล่เอนทิตี

การขับไล่เป็นฟังก์ชั่นพิเศษของ Hibernate ซึ่งจะเปลี่ยนเอนทิตีจาก "Managed" เป็น "Detached" เราไม่สามารถเรียกสิ่งที่ยังมีอยู่ในเอนทิตีที่แยกออกมาได้ หากเราทำเช่นนั้นไฮเบอร์เนตจะยกข้อยกเว้นและการทำธุรกรรมทั้งหมดจะถูกยกเลิก

ผสานกับการอัพเดท

ฟังก์ชั่นที่น่าสนใจ 2 อย่างนี้ทำสิ่งที่แตกต่างเมื่อทำในวิธีที่ต่างกัน ทั้งคู่พยายามเปลี่ยนเอนทิตีจากสถานะ "Detached" เป็นสถานะ "Managed" แต่ทำมันแตกต่างกัน

เข้าใจความจริงที่ Detached หมายถึงสถานะ "ออฟไลน์" และได้รับการจัดการหมายถึงสถานะ "ออนไลน์"

สังเกตรหัสด้านล่าง:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.merge(entity);

    ses1.delete(entity);

    tx1.commit();

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

เบื้องหลังการผสานจะเพิ่มคิวรีแบบค้นหาและส่งคืนสำเนาของเอนทิตีที่อยู่ในสถานะแนบ สังเกตรหัสด้านล่าง:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();
    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    HibEntity copied = (HibEntity)ses1.merge(entity);
    ses1.delete(copied);

    tx1.commit();

ตัวอย่างข้างต้นทำงานได้เนื่องจากการผสานได้นำเอนทิตีใหม่เข้ามาในบริบทที่อยู่ในสถานะที่ยังคงอยู่

เมื่อนำไปใช้กับการปรับปรุงการทำงานเดียวกันได้ดีเพราะการปรับปรุงไม่ได้นำสำเนาของเอนทิตีเช่นการผสาน

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.update(entity);

    ses1.delete(entity);

    tx1.commit();

ในเวลาเดียวกันในการติดตามการดีบั๊กเราจะเห็นว่าการอัพเดทไม่ได้ยกแบบสอบถาม SQL ที่เลือกเช่นการผสาน

ลบ

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

อย่างไรก็ตามเป็นไปได้ที่จะนำเอนทิตีกลับสู่สถานะ "จัดการ" จากสถานะ "ลบ" โดยใช้วิธีการคงอยู่

หวังว่าคำอธิบายข้างต้นชี้แจงข้อสงสัยใด ๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.