JPA EntityManager: ทำไมต้องใช้ persist () มากกว่า merge ()


951

EntityManager.merge() สามารถแทรกวัตถุใหม่และปรับปรุงวัตถุที่มีอยู่

ทำไมหนึ่งต้องการใช้persist()(ซึ่งสามารถสร้างวัตถุใหม่เท่านั้น)


13
techblog.bozho.net/?p=266เกี่ยวข้อง
Bozho

2
ถ้าคุณชอบไดอะแกรม อ้างอิงสิ่งนี้: spitballer.blogspot.in/2010/04/…
RBz

คำตอบ:


1615

ทั้งสองวิธีจะเพิ่มเอนทิตีใน PersistenceContext ความแตกต่างคือสิ่งที่คุณทำกับเอนทิตีในภายหลัง

ยังคงใช้อินสแตนซ์ของเอนทิตีเพิ่มไปยังบริบทและทำให้อินสแตนซ์นั้นได้รับการจัดการ (เช่นการอัพเดตในอนาคตของเอนทิตีจะถูกติดตาม)

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

บางทีตัวอย่างรหัสอาจช่วยได้

MyEntity e = new MyEntity();

// scenario 1
// tran starts
em.persist(e); 
e.setSomeField(someValue); 
// tran ends, and the row for someField is updated in the database

// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue); 
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)

// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue); 
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)

สถานการณ์ที่ 1 และ 3 มีความเท่าเทียมกัน แต่มีบางสถานการณ์ที่คุณต้องการใช้สถานการณ์ 2


3
@dma_k: ดูเหมือนว่าคุณกำลังใช้ Hibernate ฉันคุ้นเคยกับ Hibernate น้อยกว่า JPA - แต่ใน JPA ถ้าคุณโทร EntityManager.persist () และส่งผ่านเอนทิตีที่แยกออกมาคุณจะ: ก) รับ EntityExistsException ทันทีหรือข) รับ PersistenceException อีกครั้งในเวลาล้าง / ส่งมอบ บางทีฉันอาจเข้าใจผิดคำถามที่นี่?
Mike

49
คำตอบนี้อาจได้รับการปรับปรุงหากครอบคลุมกรณีที่มีการรวมกิจการ / คงอยู่แล้วในบริบทถาวร (หรืออย่างน้อยก็ทำให้ชัดเจนยิ่งขึ้นว่าจะอธิบายพฤติกรรมเท่านั้นเมื่อเอนทิตีที่ถูกรวม / รวมอยู่)
Henry

2
เป็นวิธีการหนึ่งที่มีประสิทธิภาพมากกว่าหรือไม่? บางทีmergeสำเนาเต็มของวัตถุก่อนที่จะจัดการมันมีผลกระทบต่อประสิทธิภาพหรือไม่
เควินเมเรดิ ธ

2
รหัสเกี่ยวกับอะไร ถ้าฉันมี@GeneratedIdฉันจะได้รับในสถานการณ์ที่ 2?
rascio

7
Mike: "ผสานสร้างอินสแตนซ์ใหม่ ... ": นี่ไม่จริงเสมอไป หาก EntityManager ค้นหานิติบุคคลที่ได้รับการจัดการแล้วในบริบทจะส่งคืนอินสแตนซ์นี้ (หลังจากอัปเดตฟิลด์) โปรดแก้ไขคำตอบของคุณแล้วฉันจะลงคะแนนให้
Heri

181

การคงอยู่และการผสานนั้นมีไว้เพื่อวัตถุประสงค์ที่แตกต่างกันสองประการ (ไม่ใช่ทางเลือกเลย)

(แก้ไขเพื่อขยายข้อมูลความแตกต่าง)

ยังคงมีอยู่:

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

รวม:

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

ยังคงมีอยู่ () ประสิทธิภาพ:

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

ยังคงมีอยู่ () ความหมาย:

  • ทำให้แน่ใจว่าคุณกำลังแทรกและไม่อัปเดตโดยไม่ได้ตั้งใจ

ตัวอย่าง:

{
    AnyEntity newEntity;
    AnyEntity nonAttachedEntity;
    AnyEntity attachedEntity;

    // Create a new entity and persist it        
    newEntity = new AnyEntity();
    em.persist(newEntity);

    // Save 1 to the database at next flush
    newEntity.setValue(1);

    // Create a new entity with the same Id than the persisted one.
    AnyEntity nonAttachedEntity = new AnyEntity();
    nonAttachedEntity.setId(newEntity.getId());

    // Save 2 to the database at next flush instead of 1!!!
    nonAttachedEntity.setValue(2);
    attachedEntity = em.merge(nonAttachedEntity);

    // This condition returns true
    // merge has found the already attached object (newEntity) and returns it.
    if(attachedEntity==newEntity) {
            System.out.print("They are the same object!");
    }

    // Set 3 to value
    attachedEntity.setValue(3);
    // Really, now both are the same object. Prints 3
    System.out.println(newEntity.getValue());

    // Modify the un attached object has no effect to the entity manager
    // nor to the other objects
    nonAttachedEntity.setValue(42);
}

วิธีนี้มีอยู่ 1 วัตถุที่แนบมาสำหรับการลงทะเบียนใด ๆ ในผู้จัดการกิจการ

ผสาน () สำหรับเอนทิตีที่มีรหัสเป็นสิ่งที่ต้องการ:

AnyEntity myMerge(AnyEntity entityToSave) {
    AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
    if(attached==null) {
            attached = new AnyEntity();
            em.persist(attached);
    }
    BeanUtils.copyProperties(attached, entityToSave);

    return attached;
}

แม้ว่าหากเชื่อมต่อกับ MySQL merge () อาจมีประสิทธิภาพเท่ากับ persist () โดยใช้การเรียก INSERT ด้วย ON DUPLICATE KEY UPDATE ตัวเลือก JPA เป็นโปรแกรมระดับสูงมาก


คุณสามารถระบุชื่อเคสที่ไม่สามารถแทนที่em.persist(x)ด้วยได้x = em.merge(x)หรือไม่?
Aaron Digulla

20
persist () สามารถส่ง EntityExistsException หากคุณต้องการให้แน่ใจว่ารหัสของคุณกำลังทำการแทรกและไม่ใช่การปรับปรุงข้อมูลคุณต้องใช้การคงอยู่
Josep Panadero

1
merge()ยังสามารถขว้างEntityExistsException
Sean

1
@ ไม่มีมันอาจเป็นเพราะมันเป็นRuntimeExceptionแต่ไม่ได้กล่าวถึงใน Javadoc
Martin

154

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

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

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

เมื่อองค์กรได้รับการจัดการอย่างแข็งขันโดย Hibernate การเปลี่ยนแปลงทั้งหมดจะถูกเผยแพร่โดยอัตโนมัติไปยังฐานข้อมูล

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

เพื่อทำความเข้าใจกับการเปลี่ยนสถานะ JPA ให้ดีขึ้นคุณสามารถเห็นภาพไดอะแกรมต่อไปนี้:

การเปลี่ยนสถานะนิติบุคคล JPA

หรือถ้าคุณใช้ API เฉพาะของไฮเบอร์เนต:

การเปลี่ยนสถานะของนิติบุคคลที่ไฮเบอร์เนต

ดังที่ไดอะแกรมข้างต้นเอนทิตีสามารถอยู่ในสถานะใดสถานะหนึ่งในสี่สถานะต่อไปนี้:

  • ใหม่ (ชั่วคราว)

    วัตถุที่สร้างขึ้นใหม่ที่ไม่เคยเกี่ยวข้องกับ Hibernate Session(aka Persistence Context) และไม่ได้แมปเข้ากับแถวของตารางฐานข้อมูลใด ๆ จะถือว่าอยู่ในสถานะใหม่ (ชั่วคราว)

    ในการที่จะคงอยู่เราต้องเรียกEntityManager#persistวิธีการอย่างชัดเจนหรือใช้กลไกการคงอยู่ของสกรรมกริยา

  • ถาวร (จัดการ)

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

  • สันโดษ

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

    ในการเชื่อมโยงเอนทิตีที่แยกออกไปกับเซสชันไฮเบอร์เนตที่ใช้งานอยู่คุณสามารถเลือกหนึ่งในตัวเลือกต่อไปนี้:

    • reattaching

      ไฮเบอร์เนต (แต่ไม่ใช่ JPA 2.1) รองรับการติดตั้งซ้ำผ่านวิธีการอัพเดตเซสชัน # เซสชันไฮเบอร์เนตสามารถเชื่อมโยงวัตถุหนึ่งรายการสำหรับแถวฐานข้อมูลที่กำหนดเท่านั้น นี่เป็นเพราะบริบทการมีอยู่ทำหน้าที่เป็นแคชในหน่วยความจำ (แคชระดับแรก) และมีเพียงหนึ่งค่า (เอนทิตี) ที่เชื่อมโยงกับคีย์ที่กำหนด (ประเภทเอนทิตีและตัวระบุฐานข้อมูล) สามารถแนบเอนทิตีได้ต่อเมื่อไม่มีวัตถุ JVM อื่น ๆ (จับคู่แถวฐานข้อมูลเดียวกัน) ที่เชื่อมโยงกับ Hibernate Session ปัจจุบัน

    • การผสม

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

  • ลบออก

    แม้ว่า JPA จะต้องการให้ลบเอนทิตีที่ได้รับการจัดการเท่านั้น แต่ไฮเบอร์เนตยังสามารถลบเอนทิตีที่ดึงออกได้ (แต่ผ่านการเรียกเมธอด Session # delete เท่านั้น) เอนทิตีที่ถูกลบถูกกำหนดเวลาไว้สำหรับการลบเท่านั้นและคำสั่ง DELETE ฐานข้อมูลจริงจะถูกดำเนินการในระหว่างช่วงเวลาล้างข้อมูลเซสชัน


คุณดูที่stackoverflow.com/questions/46214322// ได้หรือไม่?
gstackoverflow

@gstackoverflow คำตอบที่คุณได้คือคำตอบที่เหมาะสม สำหรับรายละเอียดเพิ่มเติมลองอ่านบทความนี้หรือหนังสือของฉันความคงทนของ Java ที่มีประสิทธิภาพสูง
Vlad Mihalcea

ดังนั้นจึงไม่มีความเป็นไปได้ที่จะเปลี่ยนลำดับการดำเนินการสำหรับ orphanremoval = true?
gstackoverflow

บทความของคุณเกี่ยวกับลำดับการดำเนินการในกรณีปกติ คำถามของฉันเฉพาะสำหรับorphanRemoval
gstackoverflow

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

37

ฉันสังเกตเห็นว่าเมื่อฉันใช้em.mergeฉันได้รับคำSELECTสั่งสำหรับทุกคนINSERTแม้ว่าจะไม่มีเขตข้อมูลที่ JPA สร้างขึ้นสำหรับฉัน - เขตข้อมูลคีย์หลักคือ UUID ที่ฉันตั้งค่าตัวเอง ฉันเปลี่ยนเป็นem.persist(myEntityObject)และได้รับเพียงINSERTงบแล้ว


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

merge()ฉันต้องเผชิญกับปัญหาที่คล้ายกัน ฉันมีฐานข้อมูล PostgreSQL พร้อมมุมมองที่ซับซ้อน: มุมมองที่รวบรวมข้อมูลจากหลาย ๆ ตาราง (ตารางมีโครงสร้างที่เหมือนกัน แต่มีชื่อต่างกัน) ดังนั้น JPA พยายามทำmerge()แต่จริง ๆ แล้ว JPA แรกทำSELECT(ฐานข้อมูลเนื่องจากการดูการตั้งค่าสามารถส่งกลับหลายระเบียนด้วยคีย์หลักเดียวกันจากตารางที่แตกต่างกัน!) แล้ว JPA (Hibernate เป็น implementatiion) ล้มเหลว: มีหลายระเบียนที่มีคีย์เดียวกัน ( org.hibernate.HibernateException: More than one row with the given identifier was found) ในกรณีของฉันpersist()ช่วยฉัน
flaz14

29

ข้อกำหนด JPA persist()กล่าวว่าต่อไปนี้เกี่ยวกับ

ถ้าXเป็นวัตถุเดี่ยวEntityExistsExceptionอาจถูกโยนทิ้งเมื่อมีการเรียกใช้การดำเนินการคงอยู่EntityExistsExceptionหรือPersistenceExceptionอาจถูกโยนทิ้งหรือล้างทิ้งหรือล้างเวลา

ดังนั้นการใช้persist()จะเหมาะสมเมื่อวัตถุไม่ควรเป็นวัตถุเดี่ยว คุณอาจต้องการให้รหัสโยนPersistenceExceptionเพื่อให้มันล้มเหลวอย่างรวดเร็ว

แม้ว่าสเปคก็ไม่มีความชัดเจน , persist()อาจจะตั้งค่า@GeneratedValue @Idสำหรับวัตถุ merge()อย่างไรก็ตามจะต้องมีวัตถุที่@Idถูกสร้างขึ้นแล้ว


5
+1 สำหรับ " merge()แต่ต้องมีวัตถุที่มีที่@Id สร้างแล้ว . " เมื่อใดก็ตามที่ EntityManager ไม่พบค่าสำหรับฟิลด์ของ ID วัตถุจะยังคงอยู่ (แทรก) ลงในฐานข้อมูล
Omar

ฉันไม่เข้าใจสิ่งนี้ก่อนเพราะฉันไม่ชัดเจนในรัฐ หวังว่านี่จะช่วยใครซักคนที่ทำเพื่อฉัน docs.jboss.org/hibernate/core/3.6/reference/en-US/html/…
RBz

1
@GeneratedValue ไม่มีความหมายที่แตกต่างกันสำหรับการผสาน () และการคงอยู่ ()
SandeepGodara

17

รายละเอียดเพิ่มเติมเกี่ยวกับการรวมซึ่งจะช่วยให้คุณใช้การผสานมากกว่าการคงอยู่:

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

เมื่อผสาน () ถูกเรียกใช้ในเอนทิตีใหม่มันจะทำงานคล้ายกับการดำเนินการคงอยู่ () มันเพิ่มเอนทิตีในบริบทการคงอยู่ แต่แทนที่จะเพิ่มอินสแตนซ์เอนทิตีดั้งเดิมจะสร้างสำเนาใหม่และจัดการอินสแตนซ์นั้นแทน การคัดลอกที่สร้างขึ้นโดยการดำเนินการผสาน () จะคงอยู่เสมือนว่ามีการเรียกใช้เมธอด persist ()

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

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

ข้อมูลข้างต้นทั้งหมดมาจาก "Pro JPA 2 Mastering the Java ™ Persistence API" โดย Mike Keith และ Merrick Schnicariol บทที่ 6 การปลดและรวมส่วน จริง ๆ แล้วหนังสือเล่มนี้เป็นหนังสือเล่มที่สองที่อุทิศให้กับ JPA โดยผู้แต่ง หนังสือเล่มใหม่นี้มีข้อมูลใหม่มากมายจากนั้นจึงเป็นข้อมูลเดิม ฉันขอแนะนำให้อ่านหนังสือเล่มนี้สำหรับคนที่จะเกี่ยวข้องกับ JPA อย่างจริงจัง ฉันขอโทษที่โพสต์คำตอบแรกของฉันโดยไม่ชักช้า


17

มีความแตกต่างระหว่างmergeและpersist(ฉันจะระบุอีกครั้งที่โพสต์ไว้ที่นี่แล้ว):

D1 mergeไม่ได้ทำให้เอนทิตีที่ผ่านการจัดการ แต่จะส่งคืนอินสแตนซ์อื่นที่ได้รับการจัดการ persistในอีกด้านหนึ่งจะทำให้นิติบุคคลที่ผ่านการจัดการ:

//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);

//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);

D2 ถ้าคุณเอากิจการแล้วตัดสินใจที่จะยังคงมีอยู่กลับนิติบุคคลที่คุณอาจทำเช่นนั้นเท่านั้นที่มียังคงมีอยู่ () เพราะจะโยนmergeIllegalArgumentException

D3 หากคุณตัดสินใจที่จะดูแล ID ของคุณด้วยตนเอง (เช่นโดยใช้ UUIDs) การmerge ดำเนินการจะเรียกคิวรีที่ตามมาSELECTเพื่อค้นหาเอนทิตีที่มีอยู่ด้วย ID นั้นในขณะที่persistอาจไม่ต้องการคิวรีเหล่านั้น

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


8

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

สิ่งที่ฉันจะทำคือในคำขอแยกต่างหากดึงเอนทิตีจากเซสชันแล้วลองเข้าถึงคอลเลกชันในหน้า jsp ซึ่งเป็นปัญหา

เพื่อบรรเทาสิ่งนี้ฉันได้อัปเดตเอนทิตีเดียวกันในคอนโทรลเลอร์ของฉันและส่งไปยัง jsp ของฉันแม้ว่าฉันจะนึกภาพเมื่อฉันบันทึกอีกครั้งในเซสชั่นว่ามันจะสามารถเข้าถึงได้แม้ว่าจะSessionScopeไม่เปิดตัวLazyLoadingExceptionก็ตาม

ต่อไปนี้ได้ผลสำหรับฉัน:

// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"

//access e from jsp and it will work dandy!!

7

ฉันพบคำอธิบายนี้จากเอกสาร Hibernate ที่ให้ความกระจ่างเพราะมีกรณีการใช้งาน:

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

โดยปกติการผสาน () จะใช้ในสถานการณ์ต่อไปนี้:

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

นี่คือความหมายที่แท้จริงของการผสาน ():

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

จาก: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html


6

หากทำตามคำตอบมีรายละเอียดบางอย่างหายไปเกี่ยวกับการสร้าง 'การเรียงซ้อน' และการสร้างรหัส ดูคำถาม

นอกจากนี้ควรพูดถึงว่าคุณสามารถมีCascadeบันทึกย่อแยกต่างหากสำหรับการรวมและการคงอยู่: Cascade.MERGEและCascade.PERSISTจะได้รับการปฏิบัติตามวิธีการที่ใช้

สเป็คคือเพื่อนของคุณ;)


6

JPA นั้นเป็นความเรียบง่ายที่ยอดเยี่ยมในโดเมนของแอพพลิเคชั่นระดับองค์กรที่สร้างขึ้นบนแพลตฟอร์ม Java ในฐานะนักพัฒนาที่ต้องรับมือกับความซับซ้อนของเอนทิตีถั่วเก่าใน J2EE ฉันเห็นการรวม JPA ไว้ในข้อกำหนด Java EE ว่าเป็นการก้าวกระโดดครั้งใหญ่ อย่างไรก็ตามในขณะที่เจาะลึกลงไปในรายละเอียด JPA ฉันพบสิ่งที่ไม่ง่าย ในบทความนี้ฉันจัดการกับการเปรียบเทียบวิธีการผสานและการคงอยู่ของ EntityManager ซึ่งพฤติกรรมที่ทับซ้อนกันอาจทำให้เกิดความสับสนไม่เพียงกับมือใหม่ นอกจากนี้ฉันเสนอการวางนัยทั่วไปที่เห็นทั้งสองวิธีเป็นกรณีพิเศษของวิธีการทั่วไปที่รวมกันมากขึ้น

นิติบุคคลที่ยังคงอยู่

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

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

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

สเปคจะมีรายละเอียดมากขึ้นอย่างไรก็ตามการจำได้ไม่สำคัญเนื่องจากรายละเอียดเหล่านี้ครอบคลุมสถานการณ์ที่แปลกใหม่มากหรือน้อยเท่านั้น

เอนทิตีผสาน

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

แทนที่จะทำซ้ำย่อหน้าจากข้อกำหนด JPA ฉันได้เตรียมแผนภาพการไหลที่แผนผังแสดงพฤติกรรมของวิธีการผสาน:

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

ดังนั้นเมื่อใดฉันจึงควรใช้ความพยายามและเมื่อรวม?

สู้

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

ผสาน

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

อะไรคือความแตกต่างระหว่าง E ที่ถูกจัดการและ PC มีเวอร์ชั่นที่ได้รับการจัดการของ E หรือไม่?
GingerBeer

5

สถานการณ์ X:

ตาราง: Spitter (หนึ่ง), ตาราง: Spittles (หลายคน) (Spittles เป็นเจ้าของความสัมพันธ์กับ FK: spitter_id)

ภาพจำลองนี้ส่งผลให้เกิดการประหยัด: The Spitter และ Spittles ทั้งสองราวกับเจ้าของ Spitter เดียวกัน

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.addSpittle(spittle3); // <--persist     
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

สถานการณ์ Y:

สิ่งนี้จะช่วยผู้ Spitter จะช่วย Spittles 2 ตัว แต่พวกเขาจะไม่อ้างอิง Spitter เดียวกัน!

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.save(spittle3); // <--merge!!       
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

1
Spitter เป็นวัตถุที่นำมาจากหนังสือ "Spring in Action" รุ่นที่สามโดย Graig Walls Spitters คือบุคคลที่พูดอะไรบางอย่างและ Spittle ของพวกเขาคือสิ่งที่พวกเขากำลังพูด ดังนั้นผู้ที่มีตัวแยกหลายตัวจึงหมายความว่าเขามีรายชื่อสายอักขระ
George Papatheodorou

1
คุณสามารถใช้ตัวอย่างที่อ่านได้ง่ายขึ้นโดยไม่ต้องอ่าน Spring in Action ...
wonderb0lt

1
คุณต้องการจริงอยากที่จะรู้ว่าสิ่งที่เป็นน้ำลายหรือ spitter ตั้งแต่ด้านบนก็มีเขียนว่า Spitter เป็นตาราง, spitter เป็นตารางที่เป็นเจ้าของอีก .. นี้และที่ ...
จอร์จ Papatheodorou

3

ข้อสังเกตอื่น:

merge()จะดูแลเกี่ยวกับรหัสที่สร้างขึ้นอัตโนมัติ (ทดสอบIDENTITYและSEQUENCE) เมื่อระเบียนที่มีรหัสดังกล่าวมีอยู่แล้วในตารางของคุณ ในกรณีmerge()นั้นจะพยายามอัปเดตบันทึก อย่างไรก็ตามหาก id ไม่อยู่หรือไม่ตรงกับบันทึกใด ๆ ที่มีอยู่merge()จะไม่สนใจมันอย่างสมบูรณ์และขอให้ db จัดสรรใหม่ บางครั้งนี่เป็นแหล่งที่มาของข้อบกพร่องมากมาย อย่าใช้merge()เพื่อบังคับ id สำหรับบันทึกใหม่

persist()ในทางกลับกันจะไม่มีวันยอมให้คุณแม้แต่ส่งรหัสไปให้ มันจะล้มเหลวทันที ในกรณีของฉันมันคือ:

เกิดจาก: org.hibernate.PersistentObjectException: เอนทิตีที่ถูกดึงออกไปเพื่อคงอยู่

hibernate-jpa javadoc มีคำใบ้:

พ่น : javax.persistence.EntityExistsException - ถ้ามีเอนทิตีอยู่แล้ว (ถ้ามีเอนทิตีอยู่แล้ว EntityExistsException อาจถูกส่งออกมาเมื่อมีการเรียกใช้การดำเนินการคงอยู่หรือ EntityExistsException หรือ PersistenceException อื่นอาจถูกส่งออกไปในเวลาล้างข้อมูลหรือส่งมอบ)


2
คุณไม่ได้ใช้ ID ที่สร้างขึ้นอัตโนมัติคุณจะต้องให้รหัสเอนทิตีใหม่ของคุณด้วยตนเอง persist()จะไม่บ่นว่ามี ID แต่จะบ่นเฉพาะเมื่อสิ่งที่มี ID เดียวกันอยู่ในฐานข้อมูลแล้ว
น.

1

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

สมมติว่าคุณสามารถใช้คีย์ธรรมชาติ / ตัวระบุ

  • ข้อมูลจะต้องคงอยู่ แต่อีกครั้งในขณะที่มีการบันทึกและมีการเรียกใช้การปรับปรุง ในกรณีนี้คุณสามารถลองยืนยันและถ้ามันโยน EntityExistsException คุณค้นหาและรวมข้อมูล:

    ลอง {entityManager.persist (เอนทิตี)}

    catch (EntityExistsException exception) {/ * ดึงข้อมูลและผสาน * /}

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

    entity = entityManager.find (กุญแจ);

    if (entity == null) {entityManager.persist (เอนทิตี); }

    อื่น {/ * ผสาน * /}

หากคุณไม่มีคีย์ / ตัวระบุธรรมดาคุณจะมีเวลามากขึ้นในการพิจารณาว่ามีเอนทิตีอยู่หรือไม่หรือจะค้นหาได้อย่างไร

การรวมสามารถจัดการได้สองวิธีเช่นกัน:

  1. หากการเปลี่ยนแปลงมีขนาดเล็กมักจะนำไปใช้กับนิติบุคคลที่มีการจัดการ
  2. หากการเปลี่ยนแปลงเป็นเรื่องปกติให้คัดลอก ID จากเอนทิตีที่ยังคงอยู่รวมถึงข้อมูลที่ไม่เปลี่ยนแปลง จากนั้นเรียก EntityManager :: merge () เพื่อแทนที่เนื้อหาเก่า

0

ยังคงมีอยู่ (เอนทิตี) ควรใช้กับเอนทิตีใหม่ทั้งหมดเพื่อเพิ่มลงในฐานข้อมูล (ถ้ามีเอนทิตีที่มีอยู่แล้วในฐานข้อมูล

ควรใช้การผสาน (เอนทิตี) เพื่อนำเอนทิตีกลับไปยังบริบทการคงอยู่หากเอนทิตีถูกถอดออกและถูกเปลี่ยน

อาจยังคงมีอยู่กำลังสร้างคำสั่ง INSERT sql และรวมคำสั่ง UPDATE sql (แต่ฉันไม่แน่ใจ)


สิ่งนี้ไม่ถูกต้อง หากคุณโทรหาการผสาน (e) กับ e ใหม่มันจะต้องคงอยู่
Pedro Lamarão

@ PedroLamarãowarren.chinalle.com/ wp
Krystian

จากข้อกำหนดคุณสมบัติ JPA เวอร์ชัน 2.1, ส่วน 3.2.7.1, หัวข้อย่อยที่สอง: "ถ้า X เป็นอินสแตนซ์เอนทิตีใหม่, อินสแตนซ์เอนทิตีที่ได้รับการจัดการใหม่ X 'จะถูกสร้างขึ้นและสถานะของ X จะถูกคัดลอกลงใน
Pedro Lamarão
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.