EntityManager.merge()
สามารถแทรกวัตถุใหม่และปรับปรุงวัตถุที่มีอยู่
ทำไมหนึ่งต้องการใช้persist()
(ซึ่งสามารถสร้างวัตถุใหม่เท่านั้น)
EntityManager.merge()
สามารถแทรกวัตถุใหม่และปรับปรุงวัตถุที่มีอยู่
ทำไมหนึ่งต้องการใช้persist()
(ซึ่งสามารถสร้างวัตถุใหม่เท่านั้น)
คำตอบ:
ทั้งสองวิธีจะเพิ่มเอนทิตีใน 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
merge
สำเนาเต็มของวัตถุก่อนที่จะจัดการมันมีผลกระทบต่อประสิทธิภาพหรือไม่
@GeneratedId
ฉันจะได้รับในสถานการณ์ที่ 2?
การคงอยู่และการผสานนั้นมีไว้เพื่อวัตถุประสงค์ที่แตกต่างกันสองประการ (ไม่ใช่ทางเลือกเลย)
(แก้ไขเพื่อขยายข้อมูลความแตกต่าง)
ยังคงมีอยู่:
รวม:
ยังคงมีอยู่ () ประสิทธิภาพ:
ยังคงมีอยู่ () ความหมาย:
ตัวอย่าง:
{
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)
หรือไม่?
merge()
ยังสามารถขว้างEntityExistsException
RuntimeException
แต่ไม่ได้กล่าวถึงใน Javadoc
หากคุณกำลังใช้ตัวสร้างที่ได้รับมอบหมายการใช้การผสานแทนการคงอยู่อาจทำให้คำสั่ง SQL ซ้ำซ้อนดังนั้นส่งผลต่อประสิทธิภาพ
นอกจากนี้ยังเรียกร้องผสานสำหรับองค์กรที่มีการจัดการยังเป็นความผิดพลาดตั้งแต่หน่วยงานที่มีการจัดการที่มีการจัดการโดยอัตโนมัติโดยการไฮเบอร์เนตและรัฐของพวกเขาจะตรงกับบันทึกฐานข้อมูลโดยกลไกตรวจสอบสกปรกเมื่อล้างบริบทความคงทน
เพื่อให้เข้าใจถึงวิธีการทำงานทั้งหมดนี้คุณควรรู้ว่า Hibernate กะความคิดนักพัฒนาจากงบ SQL เพื่อเปลี่ยนสถานะนิติบุคคล
เมื่อองค์กรได้รับการจัดการอย่างแข็งขันโดย Hibernate การเปลี่ยนแปลงทั้งหมดจะถูกเผยแพร่โดยอัตโนมัติไปยังฐานข้อมูล
ไฮเบอร์เนตมอนิเตอร์หน่วยงานที่แนบมาในปัจจุบัน แต่สำหรับนิติบุคคลที่จะจัดการมันจะต้องอยู่ในสถานะนิติบุคคลที่เหมาะสม
เพื่อทำความเข้าใจกับการเปลี่ยนสถานะ 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 ฐานข้อมูลจริงจะถูกดำเนินการในระหว่างช่วงเวลาล้างข้อมูลเซสชัน
ฉันสังเกตเห็นว่าเมื่อฉันใช้em.merge
ฉันได้รับคำSELECT
สั่งสำหรับทุกคนINSERT
แม้ว่าจะไม่มีเขตข้อมูลที่ JPA สร้างขึ้นสำหรับฉัน - เขตข้อมูลคีย์หลักคือ UUID ที่ฉันตั้งค่าตัวเอง ฉันเปลี่ยนเป็นem.persist(myEntityObject)
และได้รับเพียงINSERT
งบแล้ว
merge()
ฉันต้องเผชิญกับปัญหาที่คล้ายกัน ฉันมีฐานข้อมูล PostgreSQL พร้อมมุมมองที่ซับซ้อน: มุมมองที่รวบรวมข้อมูลจากหลาย ๆ ตาราง (ตารางมีโครงสร้างที่เหมือนกัน แต่มีชื่อต่างกัน) ดังนั้น JPA พยายามทำmerge()
แต่จริง ๆ แล้ว JPA แรกทำSELECT
(ฐานข้อมูลเนื่องจากการดูการตั้งค่าสามารถส่งกลับหลายระเบียนด้วยคีย์หลักเดียวกันจากตารางที่แตกต่างกัน!) แล้ว JPA (Hibernate เป็น implementatiion) ล้มเหลว: มีหลายระเบียนที่มีคีย์เดียวกัน ( org.hibernate.HibernateException: More than one row with the given identifier was found
) ในกรณีของฉันpersist()
ช่วยฉัน
ข้อกำหนด JPA persist()
กล่าวว่าต่อไปนี้เกี่ยวกับ
ถ้าXเป็นวัตถุเดี่ยว
EntityExistsException
อาจถูกโยนทิ้งเมื่อมีการเรียกใช้การดำเนินการคงอยู่EntityExistsException
หรือPersistenceException
อาจถูกโยนทิ้งหรือล้างทิ้งหรือล้างเวลา
ดังนั้นการใช้persist()
จะเหมาะสมเมื่อวัตถุไม่ควรเป็นวัตถุเดี่ยว คุณอาจต้องการให้รหัสโยนPersistenceException
เพื่อให้มันล้มเหลวอย่างรวดเร็ว
แม้ว่าสเปคก็ไม่มีความชัดเจน , persist()
อาจจะตั้งค่า@GeneratedValue
@Id
สำหรับวัตถุ merge()
อย่างไรก็ตามจะต้องมีวัตถุที่@Id
ถูกสร้างขึ้นแล้ว
merge()
แต่ต้องมีวัตถุที่มีที่@Id
สร้างแล้ว . " เมื่อใดก็ตามที่ EntityManager ไม่พบค่าสำหรับฟิลด์ของ ID วัตถุจะยังคงอยู่ (แทรก) ลงในฐานข้อมูล
รายละเอียดเพิ่มเติมเกี่ยวกับการรวมซึ่งจะช่วยให้คุณใช้การผสานมากกว่าการคงอยู่:
การส่งคืนอินสแตนซ์ที่มีการจัดการนอกเหนือจากเอนทิตีดั้งเดิมเป็นส่วนสำคัญของกระบวนการผสาน หากอินสแตนซ์ของเอนทิตีที่มีตัวระบุเดียวกันมีอยู่แล้วในบริบทการคงอยู่ผู้ให้บริการจะเขียนทับสถานะของตนด้วยสถานะของเอนทิตีที่กำลังผสาน แต่เวอร์ชันที่มีการจัดการที่มีอยู่แล้วจะต้องส่งคืนไปยังไคลเอนต์ ใช้ หากผู้ให้บริการไม่ได้อัปเดตอินสแตนซ์ของพนักงานในบริบทการมีอยู่การอ้างอิงใด ๆ กับอินสแตนซ์นั้นจะไม่สอดคล้องกับสถานะใหม่ที่ถูกรวมเข้าด้วยกัน
เมื่อผสาน () ถูกเรียกใช้ในเอนทิตีใหม่มันจะทำงานคล้ายกับการดำเนินการคงอยู่ () มันเพิ่มเอนทิตีในบริบทการคงอยู่ แต่แทนที่จะเพิ่มอินสแตนซ์เอนทิตีดั้งเดิมจะสร้างสำเนาใหม่และจัดการอินสแตนซ์นั้นแทน การคัดลอกที่สร้างขึ้นโดยการดำเนินการผสาน () จะคงอยู่เสมือนว่ามีการเรียกใช้เมธอด persist ()
ในการมีอยู่ของความสัมพันธ์การดำเนินการผสาน () จะพยายามอัปเดตเอนทิตีที่มีการจัดการให้ชี้ไปที่เวอร์ชันที่มีการจัดการของเอนทิตีที่อ้างอิงโดยเอนทิตีที่แยกออกมา ถ้าเอนทิตีมีความสัมพันธ์กับวัตถุที่ไม่มีข้อมูลประจำตัวถาวรผลลัพธ์ของการดำเนินการผสานจะไม่ได้กำหนด ผู้ให้บริการบางรายอาจอนุญาตให้คัดลอกที่มีการจัดการชี้ไปที่วัตถุที่ไม่ขัดข้องในขณะที่ผู้ให้บริการรายอื่นอาจส่งข้อยกเว้นทันที การดำเนินการผสาน () สามารถจัดเรียงแบบเผื่อเลือกในกรณีเหล่านี้เพื่อป้องกันข้อยกเว้นไม่ให้เกิดขึ้น เราจะครอบคลุมการดำเนินการเวียน () การผสานในภายหลังในส่วนนี้ หากเอนทิตีที่ถูกรวมคะแนนไปยังเอนทิตีที่ถูกลบออกข้อยกเว้น IllegalArgumentException จะถูกส่งออกไป
ความสัมพันธ์ของการโหลดแบบขี้เกียจเป็นกรณีพิเศษในการดำเนินการผสาน ถ้าความสัมพันธ์ที่โหลดแบบสันหลังยาวไม่ได้ถูกทริกเกอร์บนเอนทิตีก่อนที่จะถูกแยกออกความสัมพันธ์นั้นจะถูกละเว้นเมื่อรวมเอนทิตีเข้าด้วยกัน ถ้าความสัมพันธ์ถูกทริกเกอร์ในขณะที่ได้รับการจัดการและจากนั้นตั้งค่าเป็นโมฆะในขณะที่เอนทิตีถูกดึงออกเวอร์ชันที่ได้รับการจัดการของเอนทิตีก็จะถูกลบความสัมพันธ์ระหว่างการผสานเช่นกัน "
ข้อมูลข้างต้นทั้งหมดมาจาก "Pro JPA 2 Mastering the Java ™ Persistence API" โดย Mike Keith และ Merrick Schnicariol บทที่ 6 การปลดและรวมส่วน จริง ๆ แล้วหนังสือเล่มนี้เป็นหนังสือเล่มที่สองที่อุทิศให้กับ JPA โดยผู้แต่ง หนังสือเล่มใหม่นี้มีข้อมูลใหม่มากมายจากนั้นจึงเป็นข้อมูลเดิม ฉันขอแนะนำให้อ่านหนังสือเล่มนี้สำหรับคนที่จะเกี่ยวข้องกับ JPA อย่างจริงจัง ฉันขอโทษที่โพสต์คำตอบแรกของฉันโดยไม่ชักช้า
มีความแตกต่างระหว่าง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 ถ้าคุณเอากิจการแล้วตัดสินใจที่จะยังคงมีอยู่กลับนิติบุคคลที่คุณอาจทำเช่นนั้นเท่านั้นที่มียังคงมีอยู่ () เพราะจะโยนmerge
IllegalArgumentException
D3 หากคุณตัดสินใจที่จะดูแล ID ของคุณด้วยตนเอง (เช่นโดยใช้ UUIDs) การmerge
ดำเนินการจะเรียกคิวรีที่ตามมาSELECT
เพื่อค้นหาเอนทิตีที่มีอยู่ด้วย ID นั้นในขณะที่persist
อาจไม่ต้องการคิวรีเหล่านั้น
D4 มีหลายกรณีเมื่อคุณเพียงแค่ไม่ไว้วางใจรหัสที่เรียกรหัสของคุณและเพื่อที่จะให้แน่ใจว่าไม่มีการปรับปรุงข้อมูล persist
แต่ถูกแทรกที่คุณต้องใช้
ฉันได้รับ 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!!
ฉันพบคำอธิบายนี้จากเอกสาร Hibernate ที่ให้ความกระจ่างเพราะมีกรณีการใช้งาน:
การใช้งานและความหมายของการผสาน () ดูเหมือนจะสับสนสำหรับผู้ใช้ใหม่ ประการแรกตราบใดที่คุณไม่ได้พยายามใช้สถานะวัตถุที่โหลดในตัวจัดการเอนทิตี้หนึ่งในตัวจัดการเอนทิตีใหม่อีกตัวคุณไม่ควรใช้การผสาน ()เลย แอปพลิเคชั่นทั้งหมดจะไม่ใช้วิธีนี้
โดยปกติการผสาน () จะใช้ในสถานการณ์ต่อไปนี้:
- แอปพลิเคชันโหลดวัตถุในตัวจัดการเอนทิตีแรก
- วัตถุถูกส่งผ่านไปยังเลเยอร์การนำเสนอ
- การดัดแปลงบางอย่างทำกับวัตถุ
- วัตถุถูกส่งกลับไปยังเลเยอร์ตรรกะทางธุรกิจ
- แอปพลิเคชันยังคงมีการแก้ไขเหล่านี้โดยการเรียก merge () ในตัวจัดการเอนทิตีที่สอง
นี่คือความหมายที่แท้จริงของการผสาน ():
- หากมีอินสแตนซ์ที่จัดการซึ่งมีตัวระบุเดียวกันที่เชื่อมโยงกับบริบทการคงอยู่ให้คัดลอกสถานะของวัตถุที่กำหนดไปยังอินสแตนซ์ที่ได้รับการจัดการ
- หากไม่มีอินสแตนซ์ที่ได้รับการจัดการที่เชื่อมโยงกับบริบทการคงอยู่ลองโหลดจากฐานข้อมูลหรือสร้างอินสแตนซ์ที่ได้รับการจัดการใหม่
- อินสแตนซ์ที่ได้รับการจัดการจะถูกส่งคืน
- อินสแตนซ์ที่กำหนดไม่เกี่ยวข้องกับบริบทการคงอยู่มันยังแยกออกและมักจะถูกละทิ้ง
จาก: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
หากทำตามคำตอบมีรายละเอียดบางอย่างหายไปเกี่ยวกับการสร้าง 'การเรียงซ้อน' และการสร้างรหัส ดูคำถาม
นอกจากนี้ควรพูดถึงว่าคุณสามารถมีCascade
บันทึกย่อแยกต่างหากสำหรับการรวมและการคงอยู่: Cascade.MERGE
และCascade.PERSIST
จะได้รับการปฏิบัติตามวิธีการที่ใช้
สเป็คคือเพื่อนของคุณ;)
JPA นั้นเป็นความเรียบง่ายที่ยอดเยี่ยมในโดเมนของแอพพลิเคชั่นระดับองค์กรที่สร้างขึ้นบนแพลตฟอร์ม Java ในฐานะนักพัฒนาที่ต้องรับมือกับความซับซ้อนของเอนทิตีถั่วเก่าใน J2EE ฉันเห็นการรวม JPA ไว้ในข้อกำหนด Java EE ว่าเป็นการก้าวกระโดดครั้งใหญ่ อย่างไรก็ตามในขณะที่เจาะลึกลงไปในรายละเอียด JPA ฉันพบสิ่งที่ไม่ง่าย ในบทความนี้ฉันจัดการกับการเปรียบเทียบวิธีการผสานและการคงอยู่ของ EntityManager ซึ่งพฤติกรรมที่ทับซ้อนกันอาจทำให้เกิดความสับสนไม่เพียงกับมือใหม่ นอกจากนี้ฉันเสนอการวางนัยทั่วไปที่เห็นทั้งสองวิธีเป็นกรณีพิเศษของวิธีการทั่วไปที่รวมกันมากขึ้น
นิติบุคคลที่ยังคงอยู่
ตรงกันข้ามกับวิธีการผสานวิธีการคงอยู่ค่อนข้างตรงไปตรงมาและใช้งานง่าย สถานการณ์ที่พบบ่อยที่สุดของการใช้วิธีการคงอยู่สามารถสรุปได้ดังนี้
"อินสแตนซ์ที่สร้างขึ้นใหม่ของคลาสเอนทิตี้ถูกส่งผ่านไปยังวิธีการคงอยู่หลังจากเมธอดนี้ส่งคืนเอนทิตีถูกจัดการและวางแผนสำหรับการแทรกลงในฐานข้อมูลมันอาจเกิดขึ้นที่หรือก่อนธุรกรรม หากเอนทิตีอ้างอิงเอนทิตีอื่นผ่านความสัมพันธ์ที่ทำเครื่องหมายด้วยกลยุทธ์การเรียงซ้อน PERSIST ขั้นตอนนี้จะนำไปใช้กับกิจการด้วยเช่นกัน "
สเปคจะมีรายละเอียดมากขึ้นอย่างไรก็ตามการจำได้ไม่สำคัญเนื่องจากรายละเอียดเหล่านี้ครอบคลุมสถานการณ์ที่แปลกใหม่มากหรือน้อยเท่านั้น
เอนทิตีผสาน
เมื่อเปรียบเทียบกับการยืนยันคำอธิบายของพฤติกรรมการผสานนั้นไม่ง่ายนัก ไม่มีสถานการณ์หลักเนื่องจากในกรณีที่มีอยู่และโปรแกรมเมอร์จะต้องจำสถานการณ์ทั้งหมดเพื่อเขียนรหัสที่ถูกต้อง สำหรับฉันดูเหมือนว่าผู้ออกแบบ JPA ต้องการมีวิธีการบางอย่างที่ความกังวลหลักจะจัดการกับเอนทิตีที่แยกออกมา (ตรงกันข้ามกับวิธีการยืนยันที่เกี่ยวข้องกับเอนทิตีที่สร้างขึ้นใหม่เป็นหลัก) ภารกิจหลักของวิธีการผสานคือการโอนสถานะจาก เอนทิตีที่ไม่มีการจัดการ (ส่งผ่านเป็นอาร์กิวเมนต์) ไปยังคู่ที่ได้รับการจัดการภายในบริบทการคงอยู่ อย่างไรก็ตามงานนี้แบ่งออกเป็นหลาย ๆ สถานการณ์ซึ่งทำให้ความเข้าใจในพฤติกรรมของวิธีการโดยรวมแย่ลง
แทนที่จะทำซ้ำย่อหน้าจากข้อกำหนด JPA ฉันได้เตรียมแผนภาพการไหลที่แผนผังแสดงพฤติกรรมของวิธีการผสาน:
ดังนั้นเมื่อใดฉันจึงควรใช้ความพยายามและเมื่อรวม?
สู้
ผสาน
สถานการณ์ 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!!
ข้อสังเกตอื่น:
merge()
จะดูแลเกี่ยวกับรหัสที่สร้างขึ้นอัตโนมัติ (ทดสอบIDENTITY
และSEQUENCE
) เมื่อระเบียนที่มีรหัสดังกล่าวมีอยู่แล้วในตารางของคุณ ในกรณีmerge()
นั้นจะพยายามอัปเดตบันทึก อย่างไรก็ตามหาก id ไม่อยู่หรือไม่ตรงกับบันทึกใด ๆ ที่มีอยู่merge()
จะไม่สนใจมันอย่างสมบูรณ์และขอให้ db จัดสรรใหม่ บางครั้งนี่เป็นแหล่งที่มาของข้อบกพร่องมากมาย อย่าใช้merge()
เพื่อบังคับ id สำหรับบันทึกใหม่
persist()
ในทางกลับกันจะไม่มีวันยอมให้คุณแม้แต่ส่งรหัสไปให้ มันจะล้มเหลวทันที ในกรณีของฉันมันคือ:
เกิดจาก: org.hibernate.PersistentObjectException: เอนทิตีที่ถูกดึงออกไปเพื่อคงอยู่
hibernate-jpa javadoc มีคำใบ้:
พ่น : javax.persistence.EntityExistsException - ถ้ามีเอนทิตีอยู่แล้ว (ถ้ามีเอนทิตีอยู่แล้ว EntityExistsException อาจถูกส่งออกมาเมื่อมีการเรียกใช้การดำเนินการคงอยู่หรือ EntityExistsException หรือ PersistenceException อื่นอาจถูกส่งออกไปในเวลาล้างข้อมูลหรือส่งมอบ)
persist()
จะไม่บ่นว่ามี ID แต่จะบ่นเฉพาะเมื่อสิ่งที่มี ID เดียวกันอยู่ในฐานข้อมูลแล้ว
คุณอาจจะได้มาที่นี่คำแนะนำเกี่ยวกับการใช้เวลาที่จะยังคงมีอยู่และเมื่อใช้การผสาน ฉันคิดว่ามันขึ้นอยู่กับสถานการณ์ว่าเป็นไปได้มากเพียงใดที่คุณต้องการสร้างเรกคอร์ดใหม่และยากที่จะดึงข้อมูลที่เก็บไว้
สมมติว่าคุณสามารถใช้คีย์ธรรมชาติ / ตัวระบุ
ข้อมูลจะต้องคงอยู่ แต่อีกครั้งในขณะที่มีการบันทึกและมีการเรียกใช้การปรับปรุง ในกรณีนี้คุณสามารถลองยืนยันและถ้ามันโยน EntityExistsException คุณค้นหาและรวมข้อมูล:
ลอง {entityManager.persist (เอนทิตี)}
catch (EntityExistsException exception) {/ * ดึงข้อมูลและผสาน * /}
ข้อมูลที่มีการยืนยันจะต้องได้รับการอัปเดต แต่ในบางครั้งก็ยังไม่มีการบันทึกข้อมูล ในกรณีนี้คุณค้นหาและทำต่อไปถ้าเอนทิตีหายไป:
entity = entityManager.find (กุญแจ);
if (entity == null) {entityManager.persist (เอนทิตี); }
อื่น {/ * ผสาน * /}
หากคุณไม่มีคีย์ / ตัวระบุธรรมดาคุณจะมีเวลามากขึ้นในการพิจารณาว่ามีเอนทิตีอยู่หรือไม่หรือจะค้นหาได้อย่างไร
การรวมสามารถจัดการได้สองวิธีเช่นกัน:
ยังคงมีอยู่ (เอนทิตี) ควรใช้กับเอนทิตีใหม่ทั้งหมดเพื่อเพิ่มลงในฐานข้อมูล (ถ้ามีเอนทิตีที่มีอยู่แล้วในฐานข้อมูล
ควรใช้การผสาน (เอนทิตี) เพื่อนำเอนทิตีกลับไปยังบริบทการคงอยู่หากเอนทิตีถูกถอดออกและถูกเปลี่ยน
อาจยังคงมีอยู่กำลังสร้างคำสั่ง INSERT sql และรวมคำสั่ง UPDATE sql (แต่ฉันไม่แน่ใจ)