Persist và Merge trong JPA – Hibernate làm việc như thế nào?

Khi sử dụng JPA, quá trình chuyển đổi trạng thái của thực thể(entity) sẽ được JPA – Hibermate chuyển đổi tự động sang các câu lệnh SQL. Trong bài viết này chúng ta sẽ cùng tìm kiểu về persistmerge trong JPA – Hibernate cho phép tạo mới và cập nhật dự liệu xuống database. 

Hàm Persist trong JPA – Hibernate

Persist dùng để chuyển một entity từ trạng thái transitent sang persistence(persistence entity) được quản lý bởi Hibernate. Vì vậy persist chỉ được sử dụng với transitent entity (một thực thể mới không có quan hệ với một dòng dữ liệu nào dưới database).

Ví dụ chúng ta thực thi đoạn code sau:

Post post = new Post();
post.setTitle("High-Performance Java Persistence");
 
entityManager.persist(post);
LOGGER.info("The post entity identifier is {}", post.getId());
 
LOGGER.info("Flush Persistence Context");
entityManager.flush();

Hibernate sẽ chuyển Post vào Persistence Context. Một câu lệnh INSERT sẽ được thực thi ngay lúc đó hoặc chờ đến thời điểm flush-time.

flush-time là thời điểm mà JPA-Hibernate đồng bộ hoá các thay đổi của các entity xuống database. JPA và Hibernate hỗ trợ một số chiến lược để xác định flush-time. Ví dụ trong Hibernate và JPA sử dụng mặc định flush-before-query, bởi hầu hết các câu truy vấn đều phải kết nối đến database, đây cũng là thời điểm tốt để thực thi các thay đổi trên entity xuống database.

Persist với Identity

Nếu entity đang sử dụng IDENTITY generator.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

Thì SQL INSERT sẽ thực thi ngay lập tức, và Hibernate sẽ về kết quả sau

INSERT INTO post (id, title)
VALUES (DEFAULT, 'High-Performance Java Persistence')
 
-- The Post entity identifier is 1
 
-- Flush Persistence Context

Note:

  • Khi một entity được persist, Hibernate sẽ thêm nó vào Persistence Context đây là nơi quản lý các persist entity thông qua một Map với key là khoá chính của entity và Entity-Class-Type. Ví dụ với Post thì key sẽ là Post class và thuộc tính ID.
  • Đối với khoá chính sử dụng IDENTITY generator thì cách duy nhất để xác định giá trị cho khoá chính là thực thi SQL INSERT. Vì vậy SQL INSERT được thực thi ngay lập tức khi chúng ta gọi persist method mà không phải chờ đến flush-time.
  • Bởi lý do trên mà Hibernate vô hiệu hoá tính năng JDBC batch insert cho các entity sử dụng Identity generator.

Persist với Sequence

Khi sử dụng SEQUENCE generator, SQL INSERT có thể chờ cho đến flush-time. Và hibermate có thể tối ưu hoá hiệu xuất bằng cách áp dụng JDBC batch insert. 

CALL NEXT VALUE FOR 'hibernate_sequence'
 
-- The post entity identifier is 1
 
-- Flush Persistence Context
 
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence', 1)

Hàm merge trong JPA – Hibernate

Merge dùng để chuyển một entity từ trạng thái detached (detached entity) sang persistence và cập nhật lại những thay đổi trên đó so với dòng dữ liệu tương ứng dưới database. Vì thế Merge method chỉ nên sử dụng với detached entity.

Giả sử chúng ta thực đoạn code sau trong một phiên của EntityManager.

Post post = doInJPA(entityManager -> {
    Post _post = new Post();
    _post.setTitle("High-Performance Java Persistence");
 
    entityManager.persist(_post);
    return _post;
});

Sau khi thực thi đoạn mã trên, một Post entity được thêm xuống database và entityManager bị đóng lại, lúc này Post entity sẽ chuyển sang trạng thái detached. Hibermate sẽ không ghi nhận bất kỳ thay đổi nào trên nó. Nếu chúng ta thực thi một số thay đổi và muốn nó được đồng bộ xuống database, việc cần thiết là thêm nó vào một Persistence Context mới.

post.setTitle("High-Performance Java Persistence Rocks!");
 
doInJPA(entityManager -> {
    LOGGER.info("Merging the Post entity");
    Post post_ = entityManager.merge(post);
});

Khi đoạn code trên thực hiện, hibernate sẽ trả về kết quả như sau

-- Merging the Post entity
 
SELECT p.id AS id1_0_0_ ,
       p.title AS title2_0_0_
FROM   post p
WHERE  p.id = 1
 
UPDATE post
SET title='High-Performance Java Persistence Rocks!'
WHERE id=1

Đầu tiên Hibernate sẽ thực thi câu SELECT trước để lấy trạng thái mới nhất từ database, tiếp đến nó sẽ sao chép các trạng thái từ detached entity vào entity vừa được lấy lên từ database.

Đối với các entity sử dụng IDENTITYSEQUENCE generator, bạn có thể sử dụng merge để persist một entity.

Xem ví dụ dưới đây khi Post entity sử dụng khoá chính được giá trị thủ công

@Id
private Long id;

Nếu chúng ta sử dụng merge thay vì persist method

doInJPA(entityManager -> {
    Post post = new Post();
    post.setId(1L);
    post.setTitle("High-Performance Java Persistence");
 
    entityManager.merge(post);
});

Hibernate sẽ thực thi câu lệnh SELECT xuống database để đảm bảo không có dòng dữ liệu nào tương ứng với Post entity.

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM   post p
WHERE  p.id = 1
 
INSERT INTO post (title, id)
VALUES ('High-Performance Java Persistence', 1)

Chúng ta có thể khắc vấn đề này bằng cách thêm một thuộc tính @Version cho entity, đây cũng là cách tốt để đảm bảo các dữ liệu không bị mất qua các lần cập nhật.

@Version
private Long version;

Lưu ý hãy sử dụng wrapper class để Hibernate có thể kiểm tra null cho thuộc tính @Version thay vì sử dụng kiểu dữ liệu nguyên thuỷ.

Một ví dụ áp dụng quy tắc trên trong Spring Data bên trong code triển khai của save() method

@Transactional
public <S extends T> S save(S entity) {
 
    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

Hàm entityInformation.isNew(entity) sẽ trả về true nếu khóa chính notnull, thì lúc này save() method sẽ gọi đến merge thay vì persist như chúng ta mong muốn. Trong trường hợp này chúng ta sẽ giảm được một câu lệnh SELECT.

Sai lầm khi sử dụng save method trong Spring

Đoạn code sau mô tả việc chúng ta muốn lấy một Post entity từ database lên và cập nhật lại title.

@Transactional
public void savePostTitle(Long postId, String title) {
    Post post = postRepository.findOne(postId);
    post.setTitle(title);
    postRepository.save(post);
}

Tuy nhiên trong trường hợp này, save method được sử dụng gần như vô nghĩa. Nếu chúng ta xoá nó ra Hibermate vẫn sẽ thực thi một câu lệnh UPDATE xuống database bởi vì entity này đang được Hibermate quản lý, mọi thay đổi trên nó đều được đồng bộ xuống database miền là EntityManager hiện tại đang chạy.

Tóm lược

Trong khi sử dụng save method trong spring khá tiện lợi, thì bạn không nên gọi merge method cho các entity vừa được khởi tạo hoặc đã được quản lý bởi hibernate. Các entity mới nên được gọi với persist và merge cho các deteach entity muốn cập nhật lại các thay đổi.

Nguồn tham khảo

https://vladmihalcea.com/jpa-persist-and-merge/

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x