Như tôi đã giải thích trong bài viết này , bạn nên ưu tiên các phương pháp JPA hầu hết thời gian và updatecho các tác vụ xử lý hàng loạt.
Một thực thể JPA hoặc Hibernate có thể ở một trong bốn trạng thái sau:
- Tạm thời (Mới)
- Quản lý (Kiên trì)
- Tách rời
- Đã xóa (Đã xóa)
Việc chuyển đổi từ trạng thái này sang trạng thái khác được thực hiện thông qua các phương thức EntityManager hoặc Phiên.
Chẳng hạn, JPA EntityManagercung cấp các phương thức chuyển trạng thái thực thể sau đây.

Hibernate Sessionthực hiện tất cả các EntityManagerphương thức JPA và cung cấp một số phương thức chuyển đổi trạng thái thực thể bổ sung như save, saveOrUpdatevà update.

Kiên trì
Để thay đổi trạng thái của một thực thể từ Transient (Mới) sang Managed (Persisted), chúng ta có thể sử dụng persistphương thức được cung cấp bởi JPA EntityManagercũng được Hibernate kế thừa Session.
Các persistphương pháp gây nên một PersistEventđược xử lý bởi các DefaultPersistEventListenersự kiện nghe Hibernate.
Do đó, khi thực hiện trường hợp kiểm tra sau:
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()
);
});
Hibernate tạo các câu lệnh SQL sau:
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
)
Lưu ý rằng cái idđược gán trước khi gắn Bookthực thể vào Bối cảnh bền vững hiện tại. Điều này là cần thiết bởi vì các thực thể được quản lý được lưu trữ trong một Mapcấu trúc trong đó khóa được hình thành bởi loại thực thể và mã định danh của nó và giá trị là tham chiếu thực thể. Đây là lý do tại sao JPA EntityManagervà Hibernate Sessionđược gọi là Cache cấp độ đầu tiên.
Khi gọi persist, thực thể chỉ được đính kèm với Bối cảnh liên tục hiện đang chạy và INSERT có thể bị hoãn cho đến khi flushđược gọi.
Ngoại lệ duy nhất là trình tạo IDENTITY kích hoạt INSERT ngay lập tức vì đó là cách duy nhất để nó có thể nhận dạng định danh thực thể. Vì lý do này, Hibernate không thể chèn hàng loạt cho các thực thể bằng trình tạo IDENTITY. Để biết thêm chi tiết về chủ đề này, hãy xem bài viết này .
Tiết kiệm
savePhương pháp dành riêng cho Hibernate có trước JPA và nó đã có sẵn kể từ khi bắt đầu dự án Hibernate.
Các savephương pháp gây nên một SaveOrUpdateEventđược xử lý bởi các DefaultSaveOrUpdateEventListenersự kiện nghe Hibernate. Do đó, savephương thức này tương đương với updatevà saveOrUpdatephương thức.
Để xem savephương thức hoạt động như thế nào , hãy xem xét trường hợp thử nghiệm sau:
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
);
});
Khi chạy trường hợp thử nghiệm ở trên, Hibernate tạo các câu lệnh SQL sau:
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
)
Như bạn có thể thấy, kết quả giống hệt với lệnh persistgọi phương thức. Tuy nhiên, không giống như persist, savephương thức trả về định danh thực thể.
Để biết thêm chi tiết, hãy xem bài viết này .
Cập nhật
updatePhương pháp dành riêng cho Hibernate có nghĩa là bỏ qua cơ chế kiểm tra bẩn và buộc cập nhật thực thể tại thời điểm xả.
Các updatephương pháp gây nên một SaveOrUpdateEventđược xử lý bởi các DefaultSaveOrUpdateEventListenersự kiện nghe Hibernate. Do đó, updatephương thức này tương đương với savevà saveOrUpdatephương thức.
Để xem updatephương thức hoạt động như thế nào, hãy xem xét ví dụ sau để duy trì một Bookthực thể trong một giao dịch, sau đó nó sửa đổi nó trong khi thực thể ở trạng thái tách rời và nó buộc SQL UPDATE sử dụng lệnh updategọi phương thức.
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");
});
Khi thực hiện trường hợp thử nghiệm ở trên, Hibernate tạo các câu lệnh SQL sau:
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
Lưu ý rằng việc UPDATEđược thực thi trong quá trình xóa Bối cảnh liên tục, ngay trước khi xác nhận và đó là lý do tại sao Updating the Book entitythông báo được ghi lại trước tiên.
Sử dụng @SelectBeforeUpdateđể tránh cập nhật không cần thiết
Bây giờ, CẬP NHẬT luôn luôn được thực thi ngay cả khi thực thể không bị thay đổi trong khi ở trạng thái tách rời. Để ngăn chặn điều này, bạn có thể sử dụng @SelectBeforeUpdatechú thích Hibernate sẽ kích hoạt một SELECTcâu lệnh được tìm nạp loaded statesau đó được sử dụng bởi cơ chế kiểm tra bẩn.
Vì vậy, nếu chúng ta chú thích Bookthực thể với @SelectBeforeUpdatechú thích:
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Và thực hiện trường hợp kiểm tra sau:
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);
});
Hibernate thực thi các câu lệnh SQL sau:
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
Lưu ý rằng, lần này, không có UPDATEthực thi nào vì cơ chế kiểm tra bẩn Hibernate đã phát hiện ra rằng thực thể không được sửa đổi.
Lưu lại
saveOrUpdatePhương pháp dành riêng cho Hibernate chỉ là bí danh cho savevà update.
Các saveOrUpdatephương pháp gây nên một SaveOrUpdateEventđược xử lý bởi các DefaultSaveOrUpdateEventListenersự kiện nghe Hibernate. Do đó, updatephương thức này tương đương với savevà saveOrUpdatephương thức.
Bây giờ, bạn có thể sử dụng saveOrUpdatekhi bạn muốn duy trì một thực thể hoặc để buộc một thực thể UPDATEnhư được minh họa bằng ví dụ sau.
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);
});
Coi chừng NonUniqueObjectException
Một vấn đề có thể xảy ra với save, updatevà saveOrUpdatelà nếu Bối cảnh bền bỉ đã chứa một tham chiếu thực thể có cùng id và cùng loại như trong ví dụ sau:
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
);
}
Bây giờ, khi thực hiện trường hợp thử nghiệm ở trên, Hibernate sẽ ném một NonUniqueObjectExceptionvì thứ hai EntityManagerđã chứa một Bookthực thể có cùng định danh với cái mà chúng ta chuyển đến updatevà Bối cảnh liên tục có thể chứa hai biểu diễn của cùng một thực thể.
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)
Hợp nhất
Để tránh NonUniqueObjectException, bạn cần sử dụng mergephương pháp do JPA cung cấp EntityManagervà được kế thừa bởi Hibernate Session.
Như đã giải thích trong bài viết này , mergetìm nạp một ảnh chụp thực thể mới từ cơ sở dữ liệu nếu không có tham chiếu thực thể nào được tìm thấy trong Bối cảnh liên tục và nó sao chép trạng thái của thực thể tách rời được truyền cho mergephương thức.
Các mergephương pháp gây nên một MergeEventđược xử lý bởi các DefaultMergeEventListenersự kiện nghe Hibernate.
Để xem mergephương thức hoạt động như thế nào, hãy xem xét ví dụ sau để duy trì một Bookthực thể trong một giao dịch, sau đó nó sửa đổi nó trong khi thực thể ở trạng thái tách rời và chuyển thực thể tách rời sang mergetrong Bối cảnh liên tục sau đó.
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);
});
Khi chạy trường hợp thử nghiệm ở trên, Hibernate đã thực thi các câu lệnh SQL sau:
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
Lưu ý rằng tham chiếu thực thể được trả về bởi mergekhác với tham chiếu tách rời mà chúng ta đã truyền cho mergephương thức.
Bây giờ, mặc dù bạn nên sử dụng JPA mergekhi sao chép trạng thái thực thể tách rời, phần bổ sung SELECTcó thể gặp vấn đề khi thực hiện tác vụ xử lý hàng loạt.
Vì lý do này, bạn nên sử dụng updatekhi bạn chắc chắn rằng không có tham chiếu thực thể nào được đính kèm với Bối cảnh liên tục hiện đang chạy và thực thể tách rời đã được sửa đổi.
Để biết thêm chi tiết về chủ đề này, hãy xem bài viết này .
Phần kết luận
Để duy trì một thực thể, bạn nên sử dụng persistphương pháp JPA . Để sao chép trạng thái thực thể tách rời, mergenên được ưu tiên. Các updatephương pháp rất hữu ích cho chỉ nhiệm vụ xử lý hàng loạt. Các savevà saveOrUpdatechỉ là bí danh updatevà có lẽ bạn không nên sử dụng chúng.
Một số nhà phát triển gọi savengay cả khi thực thể đã được quản lý, nhưng đây là một lỗi và gây ra sự kiện dư thừa vì, đối với các thực thể được quản lý, CẬP NHẬT được tự động xử lý tại thời điểm xóa bối cảnh dai dẳng.
Để biết thêm chi tiết, hãy xem bài viết này .