ดังที่ฉันได้อธิบายไว้ในบทความนี้คุณควรชอบวิธีการ 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 จะได้รับการจัดการโดยอัตโนมัติในเวลาล้างบริบทการคงอยู่
สำหรับรายละเอียดเพิ่มเติมโปรดดูบทความนี้