ดังที่ฉันได้อธิบายไว้ในบทความนี้คุณควรชอบวิธีการ JPA เป็นส่วนใหญ่และupdate
สำหรับการประมวลผลแบบแบตช์
กิจการ JPA หรือไฮเบอร์เนตสามารถอยู่ในสถานะใดสถานะหนึ่งในสี่สถานะต่อไปนี้:
- ชั่วคราว (ใหม่)
- จัดการ (ต่อเนื่อง)
- สันโดษ
- ลบออก (ถูกลบ)
การเปลี่ยนจากสถานะหนึ่งไปอีกสถานะหนึ่งทำได้โดยใช้วิธี EntityManager หรือเซสชัน
ตัวอย่างเช่น JPA EntityManager
จัดเตรียมวิธีการเปลี่ยนสถานะเอนทิตีต่อไปนี้
ไฮเบอร์เนตSession
ดำเนินการทั้งหมด JPA EntityManager
วิธีการและมีวิธีการของรัฐนิติบุคคลการเปลี่ยนแปลงบางอย่างเพิ่มเติมเช่นsave
, และsaveOrUpdate
update
สู้
ในการเปลี่ยนสถานะของกิจการจาก Transient (ใหม่) เพื่อการจัดการ (ยืนยัน) เราสามารถใช้persist
วิธีการที่นำเสนอโดย JPA ซึ่งเป็นที่สืบเชื้อสายมาจากไฮเบอร์เนตEntityManager
Session
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
วิธีการเป็นเพียงนามแฝงสำหรับและsave
update
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 จะได้รับการจัดการโดยอัตโนมัติในเวลาล้างบริบทการคงอยู่
สำหรับรายละเอียดเพิ่มเติมโปรดดูบทความนี้