Viblo CTF
+1

Hibernate: save, persist, update, merge, saveOrUpdate

1. Introduction

Bài viết này sẽ thảo luận về sự khác biệt giữa một số phương thức của Session interface: save, persist, update, merge, saveOrUpdate.
Trong bài viết đôi khi bạn gặp mình viết là "phiên", đôi khi là "session" thì bạn hãy cứ hiểu nó là một. "Instance" hay "thể hiện" là một.

2. Session as a Persistence Context Implementation

Session interface có một số phương thức liên quan đến việc lưu dữ liệu vào cơ sở dữ liệu: persist, save, update, merge, saveOrUpdate. Để hiểu sự khác biệt giữa các phương thức này, trước tiên hãy thảo luận về mục đích của Session và sự khác biệt giữa các trạng thái của các thực thể (entity) liên quan đến Session.

2.1. Managing Entity Instances

Ngoài object-relational mapping, một trong những vấn đề mà Hibernate có ý định giải quyết là vấn đề quản lý các thực thể (entity) trong thời gian chạy. Khái niệm “persistence context” là giải pháp của Hibernate cho vấn đề này. Persistence context có thể được coi như một container hoặc một cache cấp đầu tiên cho tất cả các đối tượng mà bạn đã nạp hoặc lưu vào cơ sở dữ liệu trong một phiên. Phân chia phiên là một giao dịch hợp lý, logic nghiệp vụ sẽ quyết định ranh giới các phiên. Khi làm việc với cơ sở dữ liệu thông qua một persistence context và tất cả các instance (thể hiện) được gắn vào ngữ cảnh này, thì đó phải luôn là một instance duy nhất cho mỗi bản ghi cơ sở dữ liệu mà ta đã tương tác trong phiên.
Trong Hibernate, persistence context được thể hiện bằng một instance của org.hibernate.Session. Đối với JPA, nó là javax.persistence.EntityManager.

2.2. States of Entity Instances

Bất kỳ thể hiện của thực thể nào trong ứng dụng đều xuất hiện ở một trong ba trạng thái chính liên quan đến Session persistence context:

  • transient : trường hợp tạo mới một đối tượng từ một Entity, đối tượng đó có tình trạng là Transient. Hibernate không biết về sự tồn tại của nó. Nó nằm ngoài sự quản lý của Hibernate. Trường hợp này không được gắn liền với một phiên; trường hợp này không có hàng tương ứng trong cơ sở dữ liệu; nó thường chỉ là một đối tượng mới mà bạn đã tạo để lưu vào cơ sở dữ liệu.
  • persistent : trường hợp này được liên kết với một đối tượng Session duy nhất; khi flushing Session đến cơ sở dữ liệu, thực thể này được đảm bảo có một bản ghi phù hợp tương ứng trong cơ sở dữ liệu.
  • detached : sau khi Session gọi evict(..) hoặc clear() để đuổi các đối tượng có trạng thái persistent (bền vững) ra khỏi sự quản lý của Hibernate, giờ các đối tượng này sẽ có trạng thái mới là detected (bị tách ra). Nếu nó không được đính (attached) trở lại, nó sẽ bị bộ gom rác của Java quét đi theo cơ chế thông thường.

Khi thể hiện của thực thể ở trạng thái persistent, tất cả các thay đổi mà bạn thực hiện đối với cá thể này sẽ được áp dụng cho các bản ghi và các trường cơ sở dữ liệu tương ứng khi flush session. Trạng thái persistent có thể coi là "online", còn detached có thể coi là "offline" và không được theo dõi để ghi nhận các thay đổi xuống database.
Điều này có nghĩa rằng khi ta thay đổi các thuộc tính của một đối tượng đang ở trạng thái persistent, ta không cần phải gọi save, update hay bất kỳ phương thức nào khác để cập nhật những thay đổi xuống database. Tất cả những gì ta cần làm là commit transaction đó, hoặc flush hoặc close session đó.

2.3. Conformity to JPA Specification

Hibernate là implementation thành công nhất đối với Java ORM.

3. Differences Between the Operations

Cần hiểu ngay từ đầu rằng tất cả các phương thức (persist, save, update, merge, saveOrUpdate) không ngay lập tức dẫn đến các câu lệnh SQL UPDATE hoặc INSERT tương ứng. Việc lưu dữ liệu thực tế vào cơ sở dữ liệu xảy ra khi commit transaction hoặc flush session.
Các phương thức được đề cập về cơ bản quản lý trạng thái của các thể hiện của thực thể bằng cách chuyển chúng giữa các trạng thái khác nhau dọc theo vòng đời.
Ví dụ ta có một thực thể như sau :

@Entity
public class Person {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String name;
 
    // ... getters and setters
 
}    
3.1. Persist

Phương thức persist được thiết kế để thêm một thể hiện mới của thực thể vào persistent context, tức là chuyển một thể hiện từ trạng thái transient sang trạng thái persistent.
Ta thường gọi nó khi ta muốn thêm một bản ghi vào cơ sở dữ liệu (tồn tại một cá thể thực thể):

Person person = new Person();
person.setName("John");
session.persist(person);

Điều gì xảy ra sau khi phương thức persist được gọi? Đối tượng Person đã chuyển từ trạng thái transient sang trạng thái persistent. Đối tượng bây giờ trong persistence context , nhưng chưa được lưu vào cơ sở dữ liệu. Việc tạo ra các câu lệnh INSERT sẽ chỉ xảy ra khi commit transaction, flush hoặc close session.
Lưu ý rằng phương thức persist có kiểu trả về void. Nó hoạt động trên đối tượng được truyền "tại chỗ", thay đổi trạng thái của chính nó và tham chiếu đến đối tượng tồn tại thực tế.

3.2. Save

Phương thức save là original Hibernate method, nó không phù hợp với đặc tả JPA. Mục đích của nó về cơ bản giống như persist, nhưng implementation details của nó thì khác. Phương thức này sẽ tạo ra một định danh, đảm bảo rằng nó sẽ return the Serializable của định danh này.

Person person = new Person();
person.setName("John");
Long id = (Long) session.save(person);

Hiệu quả của việc lưu một persisted instance là giống như với persist. Sự khác biệt xuất hiện khi ta cố gắng lưu một detached instance:

Person person = new Person();
person.setName("John");
Long id1 = (Long) session.save(person);
 
session.evict(person);
Long id2 = (Long) session.save(person);

Biến id2 khác với biến id1. Khi save trên một detached instance sẽ tạo ra một persistent instance mới và gán nó cho một định danh mới. Kết quả là bị duplicate bản ghi trong database khi commit hoặc flush.

3.3. Merge

Mục đích chính của phương thức merge là update một persistent entity instance với các giá trị mới từ một detached entity instance.
Ví dụ, giả sử ta có RESTful interface với một phương thức để lấy một đối tượng bằng id của nó và một phương thức update để update đối tượng này sau đó. Một đối tượng đi qua serialization/deserialization như vậy sẽ xuất hiện ở trạng thái detached.
Sau khi deserializing this entity instance, ta cần có một persistent entity instance từ một persistent context và cập nhật các thuộc tính của nó với các giá trị mới từ detached instance này. Vì vậy, phương thức merge thực hiện chính xác điều đó:

  • tìm một entity instance bằng id lấy từ đối tượng được truyền (hoặc một existing entity instance từ persistence context được lấy ra, hoặc một new instance được load ra từ database);
  • sao chép các trường từ đối tượng được truyền vào instance vừa tìm được;
  • returns instance mới đã được update. Trong ví dụ sau, ta evict(detach) thực thể đã lưu khỏi context, thay đổi name field, và sau đó merge với detached entity.
Person person = new Person(); 
person.setName("John"); 
session.save(person);
 
session.evict(person);
person.setName("Mary");
 
Person mergedPerson = (Person) session.merge(person);

Lưu ý rằng phương thức merge trả về một đối tượng - nó là đối tượng mergeedPerson được load vào persistent context và được update, không phải đối tượng person mà ta đã truyền làm đối số. Đó là hai đối tượng khác nhau, và đối tượng person thường cần phải được loại bỏ sau đó
Như với phương thức persist, phương thức merge được chỉ định bởi JSR-220 để có một số ngữ nghĩa nhất định mà bạn có thể dựa vào:

  • nếu thực thể là detached, nó được sao chép trên một existing persistent entity.
  • nếu thực thể là transient, nó được sao chép trên một newly created persistent entity.
  • hoạt động cascades cho tất cả các mối quan hệ : cascade = MERGE hoặc cascade = ALL.
3.4. Update

Như với persist và save, phương thức update là một “original” Hibernate method đã tồn tại trước khi phương thức merge được thêm vào. Ngữ nghĩa của nó khác nhau ở một số điểm chính:

  • nó hoạt động khi đối tượng được truyền (kiểu trả về của nó là void); phương thức update chuyển đổi đối tượng được truyền từ trạng thái detached thành trạng thái persistent;
  • phương pháp này ném một ngoại lệ nếu ta truyền vào nó một transient entity.

Trong ví dụ sau, ta lưu đối tượng, sau đó evict(detach) nó khỏi context, sau đó thay đổi name và thực hiện update. Lưu ý rằng ta không đưa kết quả của hoạt động update vào một biến riêng biệt, vì việc update thực hiện trên chính đối tượng person đó. Về cơ bản, ta đang gắn lại existing entity instance vào persistence context - một điều gì đó mà đặc tả JPA không cho phép chúng tôi thực hiện.

Person person = new Person();
person.setName("John");
session.save(person);
session.evict(person);
 
person.setName("Mary");
session.update(person);

Cố gắng thực hiện update trên một transient instance sẽ dẫn đến một ngoại lệ. Điều sau đây sẽ không hoạt động:

Person person = new Person();
person.setName("John");
session.update(person); // PersistenceException!
3.5. SaveOrUpdate

Phương thức này chỉ xuất hiện trong Hibernate API. Tương tự như update, nó cũng có thể được sử dụng cho các trường hợp gắn lại.
Trên thực tế, lớp DefaultUpdateEventListener bên trong xử lý phương thức update là một lớp con của DefaultSaveOrUpdateListener, chỉ ghi đè một số chức năng. Sự khác biệt chính của phương thức saveOrUpdate là nó không ném ngoại lệ khi được áp dụng cho một transient instance; it makes this transient instance persistent. Đoạn mã sau sẽ persist một thực thể Person mới được tạo:

Person person = new Person();
person.setName("John");
session.saveOrUpdate(person);

Ta có thể nghĩ rằng phương pháp này là một công cụ phổ biến để làm cho một đối tượng persistent bất kể trạng thái của nó là transient hay detached.

4. What to Use?

Nếu không có bất kỳ yêu cầu đặc biệt nào, theo quy tắc chung, ta nên tuân thủ các phương thức persist và merge, vì chúng được chuẩn hóa và được đảm bảo để tuân theo đặc tả JPA.
Chúng cũng linh hoạt trong trường hợp ta quyết định chuyển sang một persistence provider khác, nhưng đôi khi chúng có vẻ không hữu ích như các phương thức Hibernate “gốc”, save, update và saveOrUpdate.


All Rights Reserved