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à update
cho 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 EntityManager
cung cấp các phương thức chuyển trạng thái thực thể sau đây.
Hibernate Session
thực hiện tất cả các EntityManager
phươ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
, saveOrUpdate
và 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 persist
phương thức được cung cấp bởi JPA EntityManager
cũng được Hibernate kế thừa Session
.
Các persist
phương pháp gây nên một PersistEvent
được xử lý bởi các DefaultPersistEventListener
sự 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 Book
thự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 Map
cấ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 EntityManager
và 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
save
Phươ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 save
phương pháp gây nên một SaveOrUpdateEvent
được xử lý bởi các DefaultSaveOrUpdateEventListener
sự kiện nghe Hibernate. Do đó, save
phương thức này tương đương với update
và saveOrUpdate
phương thức.
Để xem save
phươ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 persist
gọi phương thức. Tuy nhiên, không giống như persist
, save
phươ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
update
Phươ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 update
phương pháp gây nên một SaveOrUpdateEvent
được xử lý bởi các DefaultSaveOrUpdateEventListener
sự kiện nghe Hibernate. Do đó, update
phương thức này tương đương với save
và saveOrUpdate
phương thức.
Để xem update
phương thức hoạt động như thế nào, hãy xem xét ví dụ sau để duy trì một Book
thự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 update
gọ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 entity
thô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 @SelectBeforeUpdate
chú thích Hibernate sẽ kích hoạt một SELECT
câu lệnh được tìm nạp loaded state
sau đó được sử dụng bởi cơ chế kiểm tra bẩn.
Vì vậy, nếu chúng ta chú thích Book
thực thể với @SelectBeforeUpdate
chú 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ó UPDATE
thự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
saveOrUpdate
Phương pháp dành riêng cho Hibernate chỉ là bí danh cho save
và update
.
Các saveOrUpdate
phương pháp gây nên một SaveOrUpdateEvent
được xử lý bởi các DefaultSaveOrUpdateEventListener
sự kiện nghe Hibernate. Do đó, update
phương thức này tương đương với save
và saveOrUpdate
phương thức.
Bây giờ, bạn có thể sử dụng saveOrUpdate
khi bạn muốn duy trì một thực thể hoặc để buộc một thực thể UPDATE
như đượ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
, update
và saveOrUpdate
là 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 NonUniqueObjectException
vì thứ hai EntityManager
đã chứa một Book
thực thể có cùng định danh với cái mà chúng ta chuyển đến update
và 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 merge
phương pháp do JPA cung cấp EntityManager
và được kế thừa bởi Hibernate Session
.
Như đã giải thích trong bài viết này , merge
tì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 merge
phương thức.
Các merge
phương pháp gây nên một MergeEvent
được xử lý bởi các DefaultMergeEventListener
sự kiện nghe Hibernate.
Để xem merge
phương thức hoạt động như thế nào, hãy xem xét ví dụ sau để duy trì một Book
thự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 merge
trong 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 merge
khác với tham chiếu tách rời mà chúng ta đã truyền cho merge
phương thức.
Bây giờ, mặc dù bạn nên sử dụng JPA merge
khi sao chép trạng thái thực thể tách rời, phần bổ sung SELECT
có 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 update
khi 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 persist
phương pháp JPA . Để sao chép trạng thái thực thể tách rời, merge
nên được ưu tiên. Các update
phương pháp rất hữu ích cho chỉ nhiệm vụ xử lý hàng loạt. Các save
và saveOrUpdate
chỉ là bí danh update
và có lẽ bạn không nên sử dụng chúng.
Một số nhà phát triển gọi save
ngay 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 .