เป็นวิธีที่เหมาะสมในการแนบวัตถุเดี่ยวออกใน Hibernate คืออะไร


186

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

ตอนนี้ฉันสามารถทำหนึ่งในสองสิ่งนี้

  1. getHibernateTemplate().update( obj ) มันจะทำงานถ้าหากวัตถุนั้นไม่มีอยู่ในเซสชั่นไฮเบอร์เนต มีการโยนข้อยกเว้นที่ระบุวัตถุที่มีตัวระบุที่กำหนดอยู่แล้วในเซสชันเมื่อฉันต้องการในภายหลัง

  2. getHibernateTemplate().merge( obj ) มันจะทำงานถ้าวัตถุนั้นมีอยู่ในเซสชั่นไฮเบอร์เนต มีการโยนข้อยกเว้นเมื่อฉันต้องการให้วัตถุอยู่ในเซสชันในภายหลังหากฉันใช้สิ่งนี้

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

คำตอบ:


181

ดังนั้นจึงดูเหมือนว่าไม่มีวิธีที่จะแนบเอนทิตีที่แยกออกเก่าใน JPA

merge() จะผลักดันสถานะเก่าไปยังฐานข้อมูลและเขียนทับการปรับปรุงใด ๆ ที่แทรกแซง

refresh() ไม่สามารถเรียกใช้ในเอนทิตีที่แยกออกมาได้

lock() ไม่สามารถเรียกใช้เอนทิตีที่แยกออกมาได้และถึงแม้ว่าจะทำได้และได้ติดตั้งเอนทิตีอีกครั้งเรียก 'ล็อก' ด้วยอาร์กิวเมนต์ 'LockMode.NONE' ซึ่งหมายความว่าคุณกำลังล็อค แต่ไม่ล็อคเป็นส่วนที่ออกแบบได้ง่ายที่สุดของการออกแบบ API ฉันเคยเห็น

ดังนั้นคุณติดอยู่ มีเป็นdetach()วิธีการ แต่ไม่มีหรือattach() reattach()ขั้นตอนที่ชัดเจนในวงจรชีวิตของวัตถุไม่สามารถใช้ได้สำหรับคุณ

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

ดูเหมือนว่าวิธีเดียวที่จะทำได้คือละทิ้งเอนทิตีเดี่ยวที่ค้างอยู่ของคุณและค้นหาข้อความค้นหาที่มี ID เดียวกันซึ่งจะกด L2 หรือ DB

Mik


1
ฉันสงสัยว่ามีเหตุผลที่ JPA spec ไม่อนุญาตให้ใช้refresh()กับเอนทิตี้เดี่ยวหรือไม่? เมื่อมองดูข้อมูลจำเพาะ 2.0 ฉันไม่เห็นเหตุผลใด ๆ แค่ว่ามันไม่ได้รับอนุญาต
FGreg

11
สิ่งนี้ไม่ถูกต้องแน่นอน จาก JPwH: *Reattaching a modified detached instance* A detached instance may be reattached to a new Session (and managed by this new persistence context) by calling update() on the detached object. In our experience, it may be easier for you to understand the following code if you rename the update() method in your mind to reattach()—however, there is a good reason it’s called updating.พบเพิ่มเติมได้ในหัวข้อ 9.3.2
cwash

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

2
ตามไฮเบอร์เนต javadoc (แต่ไม่ใช่ JPA) lock(LockMode.NONE)จริง ๆ แล้วสามารถถูกเรียกบนวัตถุชั่วคราวและมัน reattach เอนทิตี้ของเซสชั่น ดูstackoverflow.com/a/3683370/14379
seanf

การล็อคไม่ทำงานสำหรับฉัน: java.lang.IllegalArgumentException: เอนทิตีที่ไม่ได้อยู่ในบริบทการคงอยู่ที่ org.hibernate.internal.SessionImpl.lock (SessionImpl.java:3491) ที่ org.hibernate.internal.SessionImpl.lock (SessionImpl java: 3482) ที่ com.github.vok.framework.DisableTransactionControlEMDelegate.lock (DB.kt)
Martin Vysny

32

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

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

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

if ( session.contains( myEntity ) ) {
    // nothing to do... myEntity is already associated with the session
}
else {
    session.saveOrUpdate( myEntity );
}

แจ้งให้ทราบล่วงหน้าฉันใช้ saveOrUpdate () มากกว่า update () หากคุณไม่ต้องการข้อมูลที่ยังไม่ได้แทรกที่นี่ให้ใช้ update () แทน ...


3
นี่คือคำตอบที่ถูกสำหรับคำถามนี้ - คดีปิด!
cwash

2
Session.contains(Object)ตรวจสอบโดยการอ้างอิง หากมีเอนทิตีอื่นที่แสดงแถวเดียวกันในเซสชันแล้วและคุณผ่านอินสแตนซ์ที่แยกออกคุณจะได้รับข้อยกเว้น
djmj

ขณะที่Session.contains(Object)การตรวจสอบโดยการอ้างอิงถ้ามีอีก Entity ตัวแทนแถวเดียวกันในเซสชั่นมัน wil กลับเท็จและจะปรับปรุงมัน
AxelWass

19

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

คำตอบทูต:นี้ได้อธิบายไว้ในเอกสาร Hibernate หากคุณต้องการคำชี้แจงเพิ่มเติมให้ดูหัวข้อ 9.3.2 ของJava Persistence with Hibernate ที่เรียกว่า "Working with Detached Objects" ฉันขอแนะนำให้คุณรับหนังสือเล่มนี้หากคุณกำลังทำอะไรมากกว่า CRUD ด้วย Hibernate


5
จากseamframework.org : "การพัฒนาอย่างแข็งขันของ Seam 3 ได้ถูกระงับโดย Red Hat" การเชื่อมโยง "เอกสารของ Seam ชิ้นนี้" ก็ตายเช่นกัน
badbishop

14

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

session.lock(entity, LockMode.NONE);

มันจะล็อคอะไร แต่มันจะได้รับเอนทิตีจากแคชเซสชันหรือ (ถ้าไม่พบที่นั่น) อ่านจากฐานข้อมูล

มันมีประโยชน์มากในการป้องกัน LazyInitException เมื่อคุณนำทางความสัมพันธ์จากเอนทิตี "เก่า" (จาก HttpSession เป็นต้น) เอนทิตี คุณก่อน "แนบอีกครั้ง" นิติบุคคล

การใช้ get อาจทำงานได้เช่นกันยกเว้นเมื่อคุณได้รับการแม็พการสืบทอด (ซึ่งจะทำให้เกิดข้อยกเว้นใน getId ())

entity = session.get(entity.getClass(), entity.getId());

2
ฉันต้องการเชื่อมโยงเอนทิตีกับเซสชันอีกครั้ง น่าเสียดายที่Session.lock(entity, LockMode.NONE)ล้มเหลวโดยมีข้อยกเว้นว่า: ไม่สามารถเชื่อมโยงคอลเลกชันชั่วคราวที่ไม่ได้กำหนดค่าใหม่ จะเอาชนะสิ่งนี้ได้อย่างไร
dma_k

1
ในความเป็นจริงฉันไม่ถูกต้องสมบูรณ์ การใช้ lock () แนบเอนทิตีของคุณอีกครั้ง แต่ไม่ใช่เอนทิตีอื่น ๆ ที่ผูกไว้กับมัน ดังนั้นถ้าคุณทำเอนทิตี getOtherEntity (). getYetAnotherEntity () คุณอาจมีข้อยกเว้น LazyInit วิธีเดียวที่ฉันรู้ที่จะเอาชนะนั่นคือใช้การค้นหา entity = em.find (entity.getClass (), entity.getId ();
John Rizzo

ไม่มีSession.find()วิธี API Session.load(Object object, Serializable id)บางทีคุณอาจจะหมายถึง
dma_k

11

เนื่องจากนี่เป็นคำถามที่พบบ่อยมากฉันจึงเขียน บทความนี้ขึ้นมาซึ่งคำตอบนี้ขึ้นอยู่กับ

สถานะเอนทิตี

JPA กำหนดสถานะเอนทิตีต่อไปนี้:

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

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

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

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

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

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

สันโดษ

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

การเปลี่ยนสถานะเอนทิตี

คุณสามารถเปลี่ยนสถานะเอนทิตีได้โดยใช้วิธีการต่าง ๆ ที่กำหนดโดยEntityManagerอินเตอร์เฟส

เพื่อให้เข้าใจถึงการเปลี่ยนสถานะของเอนทิตี JPA ได้ดีขึ้นให้พิจารณาแผนภาพต่อไปนี้:

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

เมื่อใช้ JPA เพื่อเชื่อมโยงเอนทิตีที่EntityManagerดึงกลับเข้ากับแอคทีฟคุณสามารถใช้การดำเนินการผสาน

เมื่อใช้ Native Hibernate API นอกเหนือจากmergeคุณสามารถแนบเอนทิตีที่แยกออกไปยัง Hibernate ที่ใช้งานอยู่โดยใช้วิธีการอัพเดตดังที่แสดงในแผนภาพต่อไปนี้:

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

การผสานเอนทิตีที่แยกออกมา

การผสานจะคัดลอกสถานะเอนทิตีที่ดึงออกมา (ต้นทาง) ไปยังอินสแตนซ์เอนทิตีที่ได้รับการจัดการ (ปลายทาง)

พิจารณาว่าเรายังคงยืนยันBookเอนทิตีต่อไปนี้และขณะนี้เอนทิตีถูกดึงออกมาตามEntityManagerที่เคยใช้เพื่อยืนยันเอนทิตีที่ถูกปิด:

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

    entityManager.persist(book);

    return book;
});

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

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

ตอนนี้เราต้องการเผยแพร่การเปลี่ยนแปลงไปยังฐานข้อมูลเพื่อให้เราสามารถเรียกmergeวิธีการ:

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

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

    assertFalse(book == _book);
});

และไฮเบอร์เนตจะทำการประมวลผลคำสั่ง SQL ต่อไปนี้:

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

หากเอนทิตีที่ผสานไม่เทียบเท่าในปัจจุบันEntityManagerสแนปชอตเอนทิตีสดจะถูกดึงมาจากฐานข้อมูล

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

ดังนั้นเมื่อใช้mergeอินสแตนซ์ออบเจ็กต์ที่ดึงออกมาจะยังคงถูกแยกออกแม้หลังจากการดำเนินการผสาน

ติดตั้งเอนทิตีซ้ำอีกครั้ง

ไฮเบอร์เนต แต่ไม่ใช่ JPA รองรับการติดตั้งupdateซ้ำผ่านวิธีการ

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

สามารถแนบเอนทิตีได้ต่อเมื่อไม่มีวัตถุ JVM อื่น ๆ (จับคู่แถวฐานข้อมูลเดียวกัน) ที่เชื่อมโยงกับ Hibernate ปัจจุบันSessionแล้ว

พิจารณาว่าเราได้ยืนยันBookเอนทิตีและเราได้ทำการแก้ไขเมื่อBookเอนทิตีอยู่ในสถานะแยกออก:

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

    entityManager.persist(book);

    return book;
});

_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");
});

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

-- Updating the Book entity

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

updateวิธีคุณจะต้องกับ HibernateunwrapEntityManagerSession

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

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

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

    //Code omitted for brevity
}

ระวัง NonUniqueObjectException

ปัญหาหนึ่งที่สามารถเกิดขึ้นได้updateคือถ้าบริบทการมีอยู่มีการอ้างอิงเอนทิตีที่มี 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)

ข้อสรุป

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

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


คำตอบที่ดี ฉันสงสัยเกี่ยวกับ@SelectBeforeUpdateคำอธิบายประกอบ การเลือกเริ่มทำงานเมื่อใด ในการโทรupdateขวาก่อนที่จะล้างข้อมูลหรือไม่สำคัญ (อาจเกิดขึ้นได้ถ้าไฮเบอร์เนตดึงเอนทิตีที่ทำหมายเหตุประกอบไว้ทั้งหมดไว้ในการโทรเดียวก่อนจะล้างข้อมูล)
Andronicus

@SelectBeforeUpdateทริกเกอร์ SELECT ในช่วงบริบทความคงทนflushการดำเนินงาน ตรวจสอบวิธีการในรายละเอียดเพิ่มเติม getDatabaseSnapshotDefaultFlushEntityEventListener
Vlad Mihalcea

10

ฉันกลับไปที่ JavaDoc สำหรับorg.hibernate.Sessionและพบสิ่งต่อไปนี้:

กรณีชั่วคราวอาจจะทำแบบถาวรโดยการโทรsave(), หรือpersist() กรณีถาวรอาจจะทำโดยการเรียกชั่วคราวsaveOrUpdate() delete()อินสแตนซ์ใด ๆ ที่ส่งคืนโดยวิธีget()หรือload()เป็นแบบถาวร กรณีเดี่ยวอาจจะทำแบบถาวรโดยการโทรupdate(), saveOrUpdate(), หรือlock() สถานะของอินสแตนซ์ชั่วคราวหรือแฝดยังอาจจะทำแบบถาวรเป็นตัวอย่างถาวรใหม่โดยการเรียกreplicate()merge()

ดังนั้นupdate(), saveOrUpdate(), lock(), replicate()และmerge()เป็นตัวเลือกที่ผู้สมัคร

update(): จะส่งข้อยกเว้นหากมีอินสแตนซ์ถาวรด้วยตัวระบุเดียวกัน

saveOrUpdate(): บันทึกหรืออัปเดต

lock(): เลิกใช้แล้ว

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

merge(): ส่งคืนออบเจกต์ถาวรด้วยตัวระบุเดียวกัน อินสแตนซ์ที่กำหนดไม่เกี่ยวข้องกับเซสชัน

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


7

ฉันทำอย่างนั้นใน C # กับ NHibernate แต่ควรทำงานในวิธีเดียวกันใน Java:

public virtual void Attach()
{
    if (!HibernateSessionManager.Instance.GetSession().Contains(this))
    {
        ISession session = HibernateSessionManager.Instance.GetSession();
        using (ITransaction t = session.BeginTransaction())
        {
            session.Lock(this, NHibernate.LockMode.None);
            t.Commit();
        }
    }
}

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

public override bool Equals(object obj)
{
    if (this == obj) { 
        return true;
    } 
    if (GetType() != obj.GetType()) {
        return false;
    }
    if (Id != ((BaseObject)obj).Id)
    {
        return false;
    }
    return true;
}

4

Session.contains(Object obj) ตรวจสอบการอ้างอิงและจะไม่ตรวจพบอินสแตนซ์อื่นที่แสดงถึงแถวเดียวกันและแนบไปกับมันแล้ว

นี่คือโซลูชันทั่วไปของฉันสำหรับเอนทิตีที่มีคุณสมบัติตัวระบุ

public static void update(final Session session, final Object entity)
{
    // if the given instance is in session, nothing to do
    if (session.contains(entity))
        return;

    // check if there is already a different attached instance representing the same row
    final ClassMetadata classMetadata = session.getSessionFactory().getClassMetadata(entity.getClass());
    final Serializable identifier = classMetadata.getIdentifier(entity, (SessionImplementor) session);

    final Object sessionEntity = session.load(entity.getClass(), identifier);
    // override changes, last call to update wins
    if (sessionEntity != null)
        session.evict(sessionEntity);
    session.update(entity);
}

นี่คือหนึ่งในไม่กี่ด้านของ. Net EntityFramework ที่ฉันชอบตัวเลือกการแนบที่แตกต่างกันเกี่ยวกับเอนทิตีที่เปลี่ยนแปลงและคุณสมบัติของพวกเขา


3

ฉันคิดวิธีแก้ปัญหาเพื่อ "รีเฟรช" วัตถุจากร้านค้าที่ยังคงมีอยู่ซึ่งจะอธิบายวัตถุอื่น ๆ ที่อาจถูกแนบไปกับเซสชัน:

public void refreshDetached(T entity, Long id)
{
    // Check for any OTHER instances already attached to the session since
    // refresh will not work if there are any.
    T attached = (T) session.load(getPersistentClass(), id);
    if (attached != entity)
    {
        session.evict(attached);
        session.lock(entity, LockMode.NONE);
    }
    session.refresh(entity);
}

2

ขออภัยดูเหมือนจะไม่สามารถเพิ่มความคิดเห็น (ยัง?)

ใช้ Hibernate 3.5.0-Final

ในขณะที่Session#lockวิธีการนี้เลิกใช้แล้ว javadoc ไม่แนะนำให้ใช้Session#buildLockRequest(LockOptions)#lock(entity)และถ้าคุณแน่ใจว่ามีการเชื่อมโยงของคุณcascade=lockแล้วการโหลดสันหลังยาวก็ไม่เป็นปัญหาเช่นกัน

ดังนั้นวิธีแนบของฉันดูเหมือนเล็กน้อย

MyEntity attach(MyEntity entity) {
    if(getSession().contains(entity)) return entity;
    getSession().buildLockRequest(LockOptions.NONE).lock(entity);
    return entity;

การทดสอบเบื้องต้นแนะนำว่ามันทำงานได้ดี


2

บางทีมันอาจมีพฤติกรรมแตกต่างกันเล็กน้อยใน Eclipselink ในการแนบวัตถุเดี่ยวโดยไม่ได้รับข้อมูลเก่าฉันมักจะทำ:

Object obj = em.find(obj.getClass(), id);

และเป็นทางเลือกขั้นตอนที่สอง (เพื่อให้แคชใช้งานไม่ได้):

em.refresh(obj)


1

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

session.contains(obj)การทดสอบสำหรับการดำรงอยู่ในเซสชั่นคือ ดังนั้นฉันคิดว่าหลอกรหัสต่อไปนี้จะทำงาน:

if (session.contains(obj))
{
    session.update(obj);
}
else 
{
    session.merge(obj);
}

2
มี () ตรวจสอบการเปรียบเทียบโดยการอ้างอิง แต่ฟังก์ชั่นจำศีลทำงานโดยใช้ฐานข้อมูล session.merge จะไม่ถูกเรียกในรหัสของคุณ
Verena Haunschmid

1

เมื่อต้องการใส่วัตถุอีกครั้งคุณต้องใช้การผสาน ();

methode นี้ยอมรับในพารามิเตอร์เอนทิตีของคุณและส่งคืนเอนทิตีจะถูกแนบและโหลดจากฐานข้อมูล

Example :
    Lot objAttach = em.merge(oldObjDetached);
    objAttach.setEtat(...);
    em.persist(objAttach);

0

การเรียก first merge () (เพื่ออัปเดตอินสแตนซ์ถาวร) จากนั้นล็อค (LockMode.NONE) (เพื่อแนบอินสแตนซ์ปัจจุบันไม่ใช่ที่ถูกส่งกลับโดยการรวม ()) ดูเหมือนว่าจะใช้งานได้ในบางกรณี


0

สถานที่ให้บริการhibernate.allow_refresh_detached_entityได้หลอกลวงสำหรับฉัน แต่เป็นกฎทั่วไปดังนั้นจึงไม่เหมาะถ้าคุณต้องการทำในบางกรณีเท่านั้น ฉันหวังว่ามันจะช่วย

ทดสอบกับ Hibernate 5.4.9

SessionFactoryOptionsBuilder



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