Sự khác biệt giữa các phương thức tiết kiệm khác nhau trong Hibernate là gì?


199

Hibernate có một số phương thức, bằng cách này hay cách khác, lấy đối tượng của bạn và đưa nó vào cơ sở dữ liệu. Sự khác biệt giữa chúng là gì, khi nào nên sử dụng cái nào và tại sao không có một phương pháp thông minh nào biết khi nào nên sử dụng cái gì?

Các phương pháp mà tôi đã xác định cho đến nay là:

  • save()
  • update()
  • saveOrUpdate()
  • saveOrUpdateCopy()
  • merge()
  • persist()

Câu trả lời:


117

Đây là sự hiểu biết của tôi về các phương pháp. Chủ yếu chúng dựa trên API mặc dù tôi không sử dụng tất cả những thứ này trong thực tế.

saveOrUpdate Gọi hoặc lưu hoặc cập nhật tùy thuộc vào một số kiểm tra. Ví dụ, nếu không có định danh tồn tại, lưu được gọi. Nếu không thì cập nhật được gọi.

lưu vẫn tồn tại một thực thể. Sẽ chỉ định một định danh nếu không tồn tại. Nếu có, về cơ bản là thực hiện cập nhật. Trả về ID được tạo của thực thể.

cập nhật Nỗ lực duy trì thực thể bằng cách sử dụng một định danh hiện có. Nếu không có định danh tồn tại, tôi tin rằng một ngoại lệ được ném ra.

saveOrUpdateCopy Điều này không được dùng nữa và không còn được sử dụng. Thay vào đó là ...

hợp nhất Bây giờ đây là nơi kiến ​​thức của tôi bắt đầu chùn bước. Điều quan trọng ở đây là sự khác biệt giữa các thực thể thoáng qua, tách rời và liên tục. Để biết thêm thông tin về các trạng thái đối tượng, hãy xem ở đây . Với lưu và cập nhật, bạn đang xử lý các đối tượng liên tục. Chúng được liên kết với Phiên để Hibernate biết những gì đã thay đổi. Nhưng khi bạn có một đối tượng thoáng qua, không có phiên nào liên quan. Trong những trường hợp này, bạn cần sử dụng hợp nhất để cập nhật và tiếp tục lưu.

vẫn như đã đề cập ở trên, điều này được sử dụng trên các đối tượng thoáng qua. Nó không trả về ID được tạo.


22
Tôi muốn chấp nhận câu trả lời này, nhưng có một điều vẫn chưa rõ ràng: vì save () rơi vào update (), nếu mục đó tồn tại, nó khác với saveOrUpdate () như thế nào trong thực tế?
Henrik Paul

Nó được chỉ định ở đâu, lưu đó sẽ hoạt động trên các trường hợp tách rời?
jrudolph

2
Nếu mô tả của bạn về hợp nhất / tồn tại chỉ quan trọng đối với các đối tượng thoáng qua thì điều này có ý nghĩa rất lớn và phù hợp với cách chúng ta sử dụng ngủ đông. Cũng lưu ý rằng việc hợp nhất thường có các giới hạn hiệu suất so với bản cập nhật vì nó dường như thực hiện tìm nạp thêm để kiểm tra tính toàn vẹn của một số loại.
Martin Dale Lyness

1
Câu trả lời của jrudolph dưới đây chính xác hơn.
azerole

2
Do ngủ đông có thể biết trạng thái của một đối tượng, tại sao chúng ta phải làm điều này bằng tay khi viết chương trình. Chỉ nên có một phương pháp lưu.
masterxilo

116
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
    METHOD                TRANSIENT                      DETACHED            
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id if doesn't         sets new id even if object   
    save()         exist, persists to db,        already has it, persists    
                  returns attached object     to DB, returns attached object 
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id on object                    throws             
   persist()       persists object to DB            PersistenceException     
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
   update()              Exception                persists and reattaches    
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                copy the state of object in      copy the state of obj in    
    merge()        DB, doesn't attach it,    ║      DB, doesn't attach it,    
                  returns attached object         returns attached object    
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
saveOrUpdate()║           as save()                       as update()         
                                                                             
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝

updatemột đối tượng thoáng qua là tốt, tôi đã không nhận được một ngoại lệ.
GMsoF

Những gì tôi biết, chúng ta không thể duy trì thoáng qua theo một trong hai cách. Tôi nghĩ rằng sự khác biệt có thể là giữa tách rời và liên tục. Xin hãy sửa cho tôi.
Ram

có nhiều lỗi ở đây ... ví dụ 1) etssave () ọ không trả về một "đối tượng đính kèm", nó trả về ọidid; 2) ´perspersist () ạc không được bảo đảm để thiết lập ´idid, nó cũng không "duy trì đối tượng với DB"; ...
Eugen Labun

67
  • Xem Diễn đàn Hibernate để được giải thích về sự khác biệt tinh tế giữa kiên trì và tiết kiệm. Có vẻ như sự khác biệt là thời gian cuối cùng câu lệnh INSERT được thực thi. Vì lưu không trả về mã định danh, nên lệnh INSERT phải được thực thi ngay lập tức bất kể trạng thái của giao dịch (thường là một điều xấu). Kiên trì sẽ không thực hiện bất kỳ câu lệnh nào bên ngoài giao dịch hiện đang chạy chỉ để gán mã định danh. Lưu / Duy trì cả hai hoạt động trên các phiên bản tạm thời , tức là các trường hợp chưa được nhận dạng và chưa được lưu trong DB.

  • Cập nhậtHợp nhất cả hai đều hoạt động trên các phiên bản tách rời , tức là các phiên bản có mục tương ứng trong DB nhưng hiện không được đính kèm (hoặc được quản lý bởi) Phiên. Sự khác biệt giữa chúng là những gì xảy ra với thể hiện được truyền cho hàm. Cập nhật cố gắng gắn lại phiên bản, điều đó có nghĩa là không phải có phiên bản khác của thực thể liên tục được đính kèm vào Phiên ngay bây giờ, nếu không sẽ có ngoại lệ. tuy nhiên, hợp nhất , chỉ sao chép tất cả các giá trị vào một phiên bản liên tục trong Phiên (sẽ được tải nếu nó hiện không được tải). Đối tượng đầu vào không thay đổi. Vì vậy, hợp nhất là tổng quát hơn cập nhật, nhưng có thể sử dụng nhiều tài nguyên hơn.


câu lệnh chèn vẫn không xảy ra nếu bạn có trình tạo Id của riêng mình
kommradHomer

Vì vậy, trong trường hợp đối tượng tách ra, hợp nhất sẽ kích hoạt một lựa chọn trong khi cập nhật sẽ không?
Gab

1
save() - If an INSERT has to be executed to get the identifier, then this INSERT happens immediately, no matter if you are inside or outside of a transaction. This is problematic in a long-running conversation with an extended Session/persistence context.Bạn có thể vui lòng cho tôi biết làm thế nào một chèn có thể xảy ra bên ngoài một phiên và tại sao nó là xấu?
Erran Morad

Tuyên bố miễn trừ trách nhiệm: Tôi đã không sử dụng chế độ ngủ đông trong một thời gian dài. IMO vấn đề là đây: chữ ký và hợp đồng lưu () yêu cầu lưu đó trả về một định danh cho đối tượng mới. Tùy thuộc vào chiến lược tạo id mà bạn chọn, mã định danh được tạo bởi DB khi giá trị được INSERTed. Do đó, trong những trường hợp đó, bạn không thể trả lại mã định danh ngay bây giờ mà không tạo ra nó và để tạo nó, bạn phải chạy INSERT ngay bây giờ . Vì, một giao dịch dài hạn không chạy ngay bây giờ mà chỉ dựa trên cam kết, cách duy nhất để thực hiện INSERTngay bây giờ là chạy nó bên ngoài tx.
jrudolph

12

Liên kết này giải thích theo cách tốt:

http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/

Tất cả chúng ta đều có những vấn đề mà chúng ta gặp phải không thường xuyên đến mức khi chúng ta gặp lại chúng, chúng ta biết rằng chúng ta đã giải quyết vấn đề này, nhưng không thể nhớ làm thế nào.

NonUniqueObjectException được ném khi sử dụng Session.saveOrUpdate () trong Hibernate là một trong số tôi. Tôi sẽ thêm chức năng mới vào một ứng dụng phức tạp. Tất cả các bài kiểm tra đơn vị của tôi hoạt động tốt. Sau đó, khi kiểm tra giao diện người dùng, cố gắng lưu một đối tượng, tôi bắt đầu nhận được một ngoại lệ với thông báo, một đối tượng khác có cùng giá trị định danh đã được liên kết với phiên. Đây là một số mã ví dụ từ Java Persistence với Hibernate.

            Session session = sessionFactory1.openSession();
            Transaction tx = session.beginTransaction();
            Item item = (Item) session.get(Item.class, new Long(1234));
            tx.commit();
            session.close(); // end of first session, item is detached

            item.getId(); // The database identity is "1234"
            item.setDescription("my new description");
            Session session2 = sessionFactory.openSession();
            Transaction tx2 = session2.beginTransaction();
            Item item2 = (Item) session2.get(Item.class, new Long(1234));
            session2.update(item); // Throws NonUniqueObjectException
            tx2.commit();
            session2.close();

Để hiểu nguyên nhân của ngoại lệ này, điều quan trọng là phải hiểu các đối tượng tách rời và điều gì xảy ra khi bạn gọi saveOrUpdate () (hoặc chỉ cập nhật ()) trên một đối tượng tách rời.

Khi chúng ta đóng một Phiên Hibernate riêng lẻ, các đối tượng liên tục mà chúng ta đang làm việc bị tách ra. Điều này có nghĩa là dữ liệu vẫn còn trong bộ nhớ của ứng dụng, nhưng Hibernate không còn chịu trách nhiệm theo dõi các thay đổi đối với các đối tượng.

Nếu sau đó chúng tôi sửa đổi đối tượng tách ra và muốn cập nhật nó, chúng tôi phải gắn lại đối tượng. Trong quá trình gắn lại đó, Hibernate sẽ kiểm tra xem liệu có bất kỳ bản sao nào khác của cùng một đối tượng không. Nếu nó tìm thấy bất kỳ thứ gì, nó phải nói với chúng tôi rằng nó không biết bản sao thực sự của thế nào nữa. Có lẽ những thay đổi khác đã được thực hiện đối với những bản sao khác mà chúng tôi dự kiến ​​sẽ được lưu, nhưng Hibernate không biết về chúng, vì nó không quản lý chúng vào thời điểm đó.

Thay vì lưu dữ liệu xấu, Hibernate cho chúng tôi biết về sự cố thông qua NonUniqueObjectException.

Vì vậy, chúng ta phải làm gì? Trong Hibernate 3, chúng ta có merge () (trong Hibernate 2, sử dụng saveOrUpdateCopy ()). Phương pháp này sẽ buộc Hibernate sao chép mọi thay đổi từ các phiên bản tách rời khác sang thể hiện bạn muốn lưu và do đó hợp nhất tất cả các thay đổi trong bộ nhớ trước khi lưu.

        Session session = sessionFactory1.openSession();
        Transaction tx = session.beginTransaction();
        Item item = (Item) session.get(Item.class, new Long(1234));
        tx.commit();
        session.close(); // end of first session, item is detached

        item.getId(); // The database identity is "1234"
        item.setDescription("my new description");
        Session session2 = sessionFactory.openSession();
        Transaction tx2 = session2.beginTransaction();
        Item item2 = (Item) session2.get(Item.class, new Long(1234));
        Item item3 = session2.merge(item); // Success!
        tx2.commit();
        session2.close();

Điều quan trọng cần lưu ý là hợp nhất trả về một tham chiếu đến phiên bản mới được cập nhật của thể hiện. Đây không phải là mục kết nối lại với Phiên. Nếu bạn kiểm tra tính bằng nhau (item == item3), bạn sẽ thấy nó trả về false trong trường hợp này. Bạn có thể sẽ muốn làm việc với item3 từ thời điểm này trở đi.

Cũng cần lưu ý rằng API liên tục Java (JPA) không có khái niệm về các đối tượng tách rời và được gắn lại và sử dụng EntityManager.persist () và EntityManager.merge ().

Nói chung, tôi đã thấy rằng khi sử dụng Hibernate, saveOrUpdate () thường đủ cho nhu cầu của tôi. Tôi thường chỉ cần sử dụng hợp nhất khi tôi có các đối tượng có thể có các tham chiếu đến các đối tượng cùng loại. Gần đây nhất, nguyên nhân của ngoại lệ là trong mã xác thực rằng tham chiếu không được đệ quy. Tôi đã tải cùng một đối tượng vào phiên của mình như là một phần của xác thực, gây ra lỗi.

Bạn đã gặp phải vấn đề này ở đâu? Đã hợp nhất làm việc cho bạn hoặc bạn cần một giải pháp khác? Bạn có thích luôn luôn sử dụng hợp nhất, hoặc chỉ thích sử dụng nó khi cần thiết cho các trường hợp cụ thể


Liên kết đến bài viết về webarchive, kể từ khi ban đầu là không có sẵn: web.archive.org/web/20160521091122/http://www.stevideter.com:80/...
Eugen Labun

5

Trên thực tế, sự khác biệt giữa ngủ đông save()persist()phương thức phụ thuộc vào lớp trình tạo mà chúng ta đang sử dụng.

Nếu lớp trình tạo của chúng ta được gán, thì không có sự khác biệt giữa save()persist() phương thức. Bởi vì 'được chỉ định' của trình tạo, với tư cách là một lập trình viên, chúng ta cần cung cấp giá trị khóa chính để lưu trong cơ sở dữ liệu ngay [Hy vọng bạn biết khái niệm trình tạo này] Trong trường hợp khác với lớp trình tạo được gán, giả sử nếu tên lớp trình tạo của chúng ta là Tăng Hibernate nó sẽ tự gán giá trị id khóa chính vào cơ sở dữ liệu ngay [ngoài trình tạo được gán, hibernate chỉ được sử dụng để chăm sóc giá trị id khóa chính], vì vậy trong trường hợp này nếu chúng ta gọi save()hoặc persist()phương thức thì nó sẽ chèn bản ghi vào cơ sở dữ liệu bình thường Nhưng nghe nói, save()phương thức có thể trả về giá trị id khóa chính được tạo bởi hibernate và chúng ta có thể thấy nó bằng cách

long s = session.save(k);

Trong trường hợp tương tự, persist()sẽ không bao giờ trả lại bất kỳ giá trị nào cho khách hàng.


5

Tôi đã tìm thấy một ví dụ hay cho thấy sự khác biệt giữa tất cả các phương thức lưu hibernate:

http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example

Tóm lại, theo liên kết trên:

tiết kiệm()

  • Chúng ta có thể gọi phương thức này bên ngoài một giao dịch. Nếu chúng tôi sử dụng điều này mà không có giao dịch và chúng tôi có xếp tầng giữa các thực thể, thì chỉ có thực thể chính được lưu trừ khi chúng tôi xóa phiên.
  • Vì vậy, nếu có các đối tượng khác được ánh xạ từ đối tượng chính, chúng sẽ được lưu tại thời điểm thực hiện giao dịch hoặc khi chúng ta xóa phiên.

kiên trì ()

  • Nó tương tự như sử dụng save () trong giao dịch, vì vậy nó an toàn và chăm sóc mọi đối tượng xếp tầng.

saveOrUpdate ()

  • Có thể được sử dụng có hoặc không có giao dịch, và giống như save (), nếu nó được sử dụng mà không có giao dịch, các thực thể được ánh xạ sẽ không được lưu un; chúng ta sẽ xóa phiên.

  • Kết quả vào chèn hoặc cập nhật truy vấn dựa trên dữ liệu được cung cấp. Nếu dữ liệu có trong cơ sở dữ liệu, truy vấn cập nhật được thực hiện.

cập nhật ()

  • Cập nhật Hibernate nên được sử dụng khi chúng tôi biết rằng chúng tôi chỉ cập nhật thông tin thực thể. Hoạt động này thêm đối tượng thực thể vào bối cảnh liên tục và các thay đổi tiếp theo được theo dõi và lưu lại khi giao dịch được thực hiện.
  • Do đó ngay cả sau khi gọi cập nhật, nếu chúng tôi đặt bất kỳ giá trị nào trong thực thể, chúng sẽ được cập nhật khi giao dịch được thực hiện.

hợp nhất ()

  • Hợp nhất Hibernate có thể được sử dụng để cập nhật các giá trị hiện có, tuy nhiên phương thức này tạo một bản sao từ đối tượng thực thể được truyền và trả về nó. Đối tượng trả về là một phần của bối cảnh liên tục và được theo dõi cho bất kỳ thay đổi nào, đối tượng đã qua không được theo dõi. Đây là sự khác biệt chính với merge () từ tất cả các phương thức khác.

Ngoài ra đối với các ví dụ thực tế của tất cả những điều này, vui lòng tham khảo liên kết tôi đã đề cập ở trên, nó hiển thị các ví dụ cho tất cả các phương pháp khác nhau này.


3

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.

nhập mô tả hình ảnh ở đâ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, saveOrUpdateupdate.

nhập mô tả hình ảnh ở đây

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 updatesaveOrUpdatephươ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 savesaveOrUpdatephươ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 saveupdate.

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 savesaveOrUpdatephươ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, updatesaveOrUpdatelà 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 savesaveOrUpdatechỉ 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 .


2

Xin lưu ý rằng nếu bạn gọi một bản cập nhật trên một đối tượng tách ra, sẽ luôn có một bản cập nhật được thực hiện trong cơ sở dữ liệu cho dù bạn có thay đổi đối tượng hay không. Nếu đó không phải là những gì bạn muốn, bạn nên sử dụng Session.lock () với LockMode.None.

Bạn chỉ nên gọi cập nhật nếu đối tượng bị thay đổi ngoài phạm vi phiên hiện tại của bạn (khi ở chế độ tách rời).


1

Không có câu trả lời nào sau đây là đúng. Tất cả các phương pháp này dường như giống nhau, nhưng trong thực tế làm những điều hoàn toàn khác nhau. Thật khó để đưa ra ý kiến ​​ngắn. Tốt hơn là cung cấp một liên kết đến tài liệu đầy đủ về các phương pháp này: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html


11
Xin vui lòng cho chúng tôi biết lý do tại sao các câu trả lời sau là không đúng.
Erran Morad

0

Không có câu trả lời nào ở trên là hoàn chỉnh. Mặc dù câu trả lời của Leo Theobald trông gần nhất.

Điểm cơ bản là cách thức ngủ đông xử lý các trạng thái của các thực thể và cách nó xử lý chúng khi có thay đổi trạng thái. Tất cả mọi thứ phải được nhìn thấy liên quan đến tuôn ra và cam kết, điều mà mọi người dường như đã bỏ qua hoàn toàn.

KHÔNG BAO GIỜ SỬ DỤNG PHƯƠNG PHÁP TIẾT KIỆM CỦA HIBERNATE. HÃY ĐỂ R ITNG NÓ NGAY LẬP TỨC TRONG HIBERNATE!

Kiên trì

Như mọi người đã giải thích, về cơ bản, Persist chuyển một thực thể từ trạng thái "Tạm thời" sang trạng thái "Được quản lý". Tại thời điểm này, một slush hoặc một cam kết có thể tạo ra một câu lệnh chèn. Nhưng thực thể vẫn sẽ ở trạng thái "Được quản lý". Điều đó không thay đổi với tuôn ra.

Tại thời điểm này, nếu bạn "Kiên trì" một lần nữa, sẽ không có thay đổi. Và sẽ không còn tiết kiệm nữa nếu chúng ta cố gắng duy trì một thực thể bền bỉ.

Cuộc vui bắt đầu khi chúng ta cố gắng đuổi thực thể.

Một evict là một chức năng đặc biệt của Hibernate sẽ chuyển thực thể từ "Managed" sang "Detached". Chúng ta không thể gọi một người kiên trì trên một thực thể tách rời. Nếu chúng ta làm điều đó, thì Hibernate sẽ tạo ra một ngoại lệ và toàn bộ giao dịch sẽ được khôi phục theo cam kết.

Hợp nhất và cập nhật

Đây là 2 chức năng thú vị làm các công cụ khác nhau khi xử lý theo những cách khác nhau. Cả hai đều cố gắng chuyển thực thể từ trạng thái "Tách rời" sang trạng thái "Được quản lý". Nhưng làm nó khác đi.

Hiểu một thực tế rằng Detached có nghĩa là loại trạng thái "ngoại tuyến". và được quản lý có nghĩa là trạng thái "Trực tuyến".

Quan sát mã dưới đây:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.merge(entity);

    ses1.delete(entity);

    tx1.commit();

Khi bạn làm điều này? Bạn nghĩ điều gì sẽ xảy ra? Nếu bạn nói điều này sẽ đưa ra ngoại lệ, thì bạn đã đúng. Điều này sẽ đưa ra ngoại lệ vì, hợp nhất đã hoạt động trên đối tượng thực thể, trạng thái tách rời. Nhưng nó không làm thay đổi trạng thái của đối tượng.

Đằng sau cảnh, hợp nhất sẽ đưa ra một truy vấn chọn và về cơ bản trả về một bản sao của thực thể ở trạng thái đính kèm. Quan sát mã dưới đây:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();
    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    HibEntity copied = (HibEntity)ses1.merge(entity);
    ses1.delete(copied);

    tx1.commit();

Mẫu trên hoạt động vì hợp nhất đã đưa một thực thể mới vào bối cảnh ở trạng thái bền bỉ.

Khi được áp dụng với Cập nhật, hoạt động tương tự cũng tốt vì cập nhật không thực sự mang lại một bản sao của thực thể như hợp nhất.

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.update(entity);

    ses1.delete(entity);

    tx1.commit();

Đồng thời trong theo dõi gỡ lỗi, chúng ta có thể thấy rằng Cập nhật không đưa ra truy vấn SQL chọn giống như hợp nhất.

xóa bỏ

Trong ví dụ trên tôi đã sử dụng xóa mà không nói về xóa. Xóa về cơ bản sẽ chuyển thực thể từ trạng thái được quản lý sang trạng thái "bị xóa". Và khi được xóa hoặc cam kết sẽ đưa ra lệnh xóa để lưu trữ.

Tuy nhiên, có thể đưa thực thể trở lại trạng thái "được quản lý" từ trạng thái "bị loại bỏ" bằng phương thức kiên trì.

Hy vọng giải thích trên làm rõ bất kỳ nghi ngờ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.