Які відмінності між різними методами збереження в сплячому режимі?


199

Hibernate має кілька методів, які, так чи інакше, беруть ваш об’єкт і заносять його в базу даних. У чому полягають відмінності між ними, коли їх використовувати, і чому не існує лише одного розумного методу, який знає, коли використовувати?

Методи, які я визначив поки що:

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

Відповіді:


117

Ось моє розуміння методів. В основному вони базуються на API, хоча я не використовую все це на практиці.

saveOrUpdate Виклики або зберегти, або оновити залежно від деяких перевірок. Наприклад, якщо не існує ідентифікатора, зберігається виклик збереження. Інакше оновлення викликається.

зберегти Персист. Призначить ідентифікатор, якщо його не існує. Якщо це так, він по суті робить оновлення. Повертає згенерований ідентифікатор об'єкта.

update Спроби зберегти сутність за допомогою наявного ідентифікатора. Якщо жодного ідентифікатора не існує, я вважаю, що викид викинуто.

saveOrUpdateCopy Це застаріле і більше не слід використовувати. Натомість є ...

злиття. Ось тут мої знання починають стихати. Тут важливим є різниця між тимчасовими, відстороненими та стійкими сутностями. Для отримання додаткової інформації про стан об'єкта, подивіться тут . За допомогою збереження та оновлення ви маєте справу зі стійкими об'єктами. Вони пов'язані із сесією, тому сплячий режим знає, що змінилося. Але коли у вас є перехідний об'єкт, сеанс не бере участь. У цих випадках вам потрібно скористатися об'єднанням для оновлень та зберегти для збереження.

зберігаються Як було сказано вище, це використовується на перехідних об'єктах. Він не повертає створений ідентифікатор.


22
Я хотів би прийняти це як відповідь, але одне все ще незрозуміле: оскільки Save () відновлюється при оновленні (), якщо такий елемент існує, чим він відрізняється від saveOrUpdate () на практиці?
Генрік Пол

Де зазначено, що збереження буде працювати на відокремлених екземплярах?
jrudolph

2
Якщо ваш опис злиття / зберігається важливим лише для перехідних об'єктів, то це має сенс і відповідає тому, як ми використовуємо сплячий режим. Також зауважте, що злиття часто має обмеження продуктивності порівняно з оновленням, оскільки, здається, це робить додатковий пошук для певної перевірки цілісності.
Мартін Дейл Лінес

1
Відповідь jrudolph нижче є більш точною.
азероль

2
Дана сплячка, напевно, знає, в якому стані знаходиться об’єкт, чому ми повинні робити це вручну під час написання програми. Повинен бути лише один метод збереження.
майстерсіло

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()         
                                                                             
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝

updateперехідний об'єкт добре, я не отримав винятку.
GMsoF

Що я знаю, ми не можемо втримати перехідного жодним чином. Я думаю, різниця може бути між відстороненими та стійкими. Будь ласка, виправте мене.
Рам

тут є багато помилок ... наприклад 1) ´save () ´ не повертає „доданий об’єкт“, він повертає „id´; 2) "персист () "не гарантується встановленням", "і не" зберігає об'єкт в БД "; ...
Євген Лабун

67
  • Подивіться на Форумі сплячки для пояснення тонких відмінностей між збереженням та збереженням. Схоже, різниця полягає в тому, коли в кінцевому рахунку виконується оператор INSERT. Оскільки save не повертає ідентифікатор, оператор INSERT повинен бути виконаний миттєво незалежно від стану транзакції (що, як правило, погано). Persist не виконуватиме жодних операторів за межами поточної операції лише для призначення ідентифікатора. Зберегти / зберегти обидві роботи над тимчасовими екземплярами , тобто екземпляри, яким ще не призначений ідентифікатор, і як такі не зберігаються в БД.

  • Оновлення та об'єднання працюють як на відокремлених екземплярах , тобто на екземплярах, які мають відповідний запис у БД, але наразі не приєднані до (або керуються) Сесією. Різниця між ними полягає в тому, що відбувається з екземпляром, який передається функції. оновлення намагається повторно приєднати примірник, це означає, що зараз не повинно бути жодного іншого екземпляра постійної сутності, приєднаної до Сесії, інакше буде викинуто виняток. злиття , однак, просто копіює всі значення в стійкий екземпляр у сесії (який буде завантажено, якщо він наразі не завантажений). Об'єкт введення не змінюється. Тож злиття є більш загальним, ніж оновлення, але може використовувати більше ресурсів.


оператор вставлення все ще не відбувається, якщо у вас є власний генератор id
kommradHomer

Тож у випадку відірваного об'єкта об'єднання призведе до вибору, тоді як оновлення не буде?
Габ

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.Скажіть, будь ласка, як може вставитись поза сеансом і чому це погано?
Ерран Морад

Застереження: Я давно не використовував сплячку. Проблема IMO полягає в наступному: підпис і договір save () вимагають, щоб зберегти повертає ідентифікатор нового об'єкта. Залежно від обраної вами стратегії генерації id, ідентифікатор генерується БД, коли значення INSERTредагується. Отже, у цих випадках ви не можете повернути ідентифікатор прямо зараз, не створивши його, і генерувати його, вам потрібно запустити INSERT зараз . Оскільки давно запущена транзакція виконується не зараз, а лише на фіксації, єдиний спосіб виконати INSERTтепер - це запустити її поза tx.
jrudolph

12

Це посилання добре пояснює:

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

У всіх нас є ті проблеми, з якими ми стикаємось досить рідко, що коли ми їх знову бачимо, ми знаємо, що це вирішили, але не можемо згадати, як.

NonUniqueObjectException, кинутий при використанні Session.saveOrUpdate () в режимі Hibernate - один із моїх. Я додаю нову функціональність до складної програми. Всі мої одиничні тести працюють чудово. Потім під час тестування інтерфейсу користувача, намагаючись зберегти об'єкт, я починаю отримувати виняток із повідомленням "інший об'єкт із тим самим значенням ідентифікатора вже був пов’язаний із сеансом". Ось декілька прикладів коду з програми Java Persistence with 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();

Щоб зрозуміти причину цього винятку, важливо зрозуміти відокремлені об'єкти і те, що відбувається, коли ви викликаєте saveOrUpdate () (або просто оновлення ()) на відокремленому об'єкті.

Коли ми закриваємо індивідуальну сплячку, стійкі об'єкти, з якими ми працюємо, відриваються. Це означає, що дані все ще зберігаються в пам’яті програми, але Hibernate більше не відповідає за відстеження змін об’єктів.

Якщо потім ми модифікуємо наш відокремлений об'єкт і хочемо оновити його, нам доведеться повторно приєднати об'єкт. Під час цього процесу повторного приєднання Hibernate перевірить, чи немає інших копій того ж об’єкта. Якщо він знайде його, він повинен сказати нам, що не знає, що таке "справжня" копія. Можливо, були внесені інші зміни до тих інших примірників, які, як ми очікуємо, буде збережено, але Hibernate про них не знає, оскільки тоді не керував ними.

Замість того, щоб зберегти можливі погані дані, Hibernate повідомляє нам про проблему через NonUniqueObjectException.

То що нам робити? У Hibernate 3 у нас є merge () (у Hibernate 2 використовуйте saveOrUpdateCopy ()). Цей метод змусить 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));
        Item item3 = session2.merge(item); // Success!
        tx2.commit();
        session2.close();

Важливо зазначити, що злиття повертає посилання на нещодавно оновлену версію екземпляра. Це не повторне приєднання пункту до сесії. Якщо ви перевірите, наприклад, рівність (item == item3), то виявите, що в цьому випадку воно повертається помилково. Ви, ймовірно, захочете працювати з item3 з цього моменту вперед.

Також важливо відзначити, що API API API PersistentManager (JPA) не має поняття відокремлених та повторно закріплених об'єктів і використовує EntityManager.persist () та EntityManager.merge ().

Я взагалі виявив, що при використанні Hibernate, saveOrUpdate () зазвичай достатній для моїх потреб. Зазвичай мені потрібно використовувати злиття лише тоді, коли у мене є об'єкти, які можуть мати посилання на об'єкти одного типу. Зовсім недавно причина винятку була в коді, що підтверджує, що посилання не було рекурсивним. Я завантажував той самий об’єкт у свій сеанс у рамках перевірки, викликаючи помилку.

Де ви стикалися з цією проблемою? Чи працювало злиття для вас чи вам потрібно було інше рішення? Ви віддаєте перевагу завжди використовувати злиття, або вважаєте за краще використовувати його тільки в міру необхідності для конкретних випадків


Посилання на статтю про webarchive, оскільки оригінал недоступний: web.archive.org/web/20160521091122/http://www.stevideter.com:80/…
Євген Лабун

5

Насправді різниця між сплячим режимом save()та persist()методами залежить від класу генераторів, який ми використовуємо.

Якщо наш клас генератора призначений, різниці між методами save()і persist() немає. Оскільки генератор "призначений" означає, що ми, як програміст, мусимо надати значення основного ключа для збереження в праві бази даних [Сподіваюся, ви знаєте цю концепцію генераторів] У випадку іншого, ніж призначений клас генератора, припустімо, чи ім'я нашого класу генератора - це збільшення hibernate it self призначить значення ідентифікатора первинного ключа праворуч бази даних (окрім призначеного генератора; hibernate використовується лише для піклування про значення ідентифікатора первинного ключа) пам'ятайте], тому в цьому випадку, якщо ми зателефонуємо save()чи persist()методом, він вставить запис у база даних зазвичай. Але чуйте, save()метод може повернути те значення первинного ключа, яке генерується в сплячому режимі, і ми можемо бачити це

long s = session.save(k);

У цьому ж випадку persist()ніколи не повернеться жодному значенню для клієнта.


5

Я знайшов хороший приклад, що показує відмінності між усіма методами збереження в сплячому режимі:

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

Коротше кажучи, за вищенаведеним посиланням:

зберегти ()

  • Ми можемо використовувати цей метод поза транзакцією. Якщо ми використовуємо це без транзакцій, і ми каскадуємо між сутностями, тоді зберігається лише первинний об'єкт, якщо ми не очистимо сеанс.
  • Отже, якщо є інші об’єкти, відображені з основного об'єкта, вони зберігаються під час здійснення транзакції або коли ми промиваємо сеанс.

зберігатись ()

  • Це аналогічно використанню save () в транзакції, тому він безпечний і піклується про будь-які каскадні об'єкти.

saveOrUpdate ()

  • Можна використовувати з транзакцією або без неї, і так само, як зберегти (), якщо вона використовується без транзакції, відображені сутні об'єкти не будуть збережені, якщо ми не відмиємо сеанс.

  • Результати вставки або оновлення запитів на основі наданих даних. Якщо дані є в базі даних, виконується запит на оновлення.

update ()

  • Оновлення в сплячому режимі слід використовувати там, коли ми знаємо, що ми лише оновлюємо інформацію про сутність. Ця операція додає об'єкт сутності до стійкого контексту, а подальші зміни відслідковуються та зберігаються при здійсненні транзакції.
  • Отже, навіть після виклику оновлення, якщо ми встановимо будь-які значення в об'єкті, вони будуть оновлені, коли транзакція здійснюється.

злиття ()

  • Hibernate злиття може використовуватися для оновлення існуючих значень, однак цей метод створює копію з переданого об'єкта сутності та повертає її. Повернений об'єкт є частиною стійкого контексту і відслідковується для будь-яких змін, пройдений об'єкт не відстежується. Це головна відмінність з об'єднанням () від усіх інших методів.

Також для практичних прикладів усього цього, будь ласка, зверніться до посилання, яке я згадав вище, в ньому наведено приклади для всіх цих різних методів.


3

Як я пояснив у цій статті , ви повинні віддавати перевагу методам JPA більшість часу та завданням updateпакетної обробки.

Спільна спільна особа або сплячий суб'єкт може перебувати в одному з наступних чотирьох штатів:

  • Тимчасовий (новий)
  • Керований (стійкий)
  • Окремі
  • Видалено (видалено)

Перехід від одного стану до іншого здійснюється методами EntityManager або Session.

Наприклад, JPA EntityManagerнадає такі методи переходу стану сутності.

введіть тут опис зображення

У Hibernate Sessionреалізує всі JPA EntityManagerметоди і надає деякі додаткові методи суб'єкт перехідного стану , як save, saveOrUpdateі update.

введіть тут опис зображення

Персист

Щоб змінити стан суб'єкта господарювання з Тимчасового (Нового) на Керованого (Персистентного), ми можемо використовувати persistметод, запропонований JPA, EntityManagerякий також успадковується Гібернатом Session.

persistМетод викликає , PersistEventякий обробляється DefaultPersistEventListenerПрослуховувач подій глибокого сну.

Тому при виконанні наступного тестового випадку:

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 генерує такі оператори SQL:

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
)

Зауважте, що idприсвоюється до приєднання Bookсутності до поточного контексту постійності. Це потрібно, оскільки керовані об'єкти зберігаються в Mapструктурі, де ключ формується за типом сутності та його ідентифікатором, а значення - посиланням на сутність. Це причина, через яку JPA EntityManagerі Hibernate Sessionвідомі як кеш першого рівня.

Під час виклику persistсутність приєднується лише до поточно працюючого контексту постійності, і INSERT можна відкласти до виклику flush.

Єдиним винятком є генератор IDENTITY, який спрацьовує INSERT відразу, оскільки це єдиний спосіб отримати ідентифікатор сутності. З цієї причини Hibernate не може додати вставки для об'єктів, що використовують генератор IDENTITY. Детальніше про цю тему ознайомтеся з цією статтею .

Зберегти

Метод, специфічний для saveспячки, передує JPA, і він доступний з початку проекту "Зимовий режим".

saveМетод викликає , SaveOrUpdateEventякий обробляється DefaultSaveOrUpdateEventListenerПрослуховувач подій глибокого сну. Тому saveспосіб еквівалентний методам updateі saveOrUpdate.

Щоб побачити, як saveпрацює метод, розгляньте наступний тестовий випадок:

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
    );
});

Під час запуску тестового випадку вище, Hibernate генерує такі оператори SQL:

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
)

Як бачите, результат ідентичний persistвиклику методу. Однак, на відміну від цього persist, saveметод повертає ідентифікатор сутності.

Більш детально ознайомтеся з цією статтею .

Оновлення

Метод, специфічний для updateспячки, призначений для обходу брудного механізму перевірки та примушування оновлення суб'єкта господарювання під час потоку.

updateМетод викликає , SaveOrUpdateEventякий обробляється DefaultSaveOrUpdateEventListenerПрослуховувач подій глибокого сну. Тому updateспосіб еквівалентний методам saveі saveOrUpdate.

Щоб побачити, як updateпрацює метод, розглянемо наступний приклад, який зберігає Bookоб'єкт в одній транзакції, потім він модифікує його, коли сутність знаходиться у відокремленому стані, і він змушує SQL UPDATE використовувати updateвиклик методу.

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");
});

Виконуючи тестовий випадок вище, Hibernate генерує такі оператори SQL:

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

Зауважте, що UPDATEфункція виконується під час спалаху контексту стійкості безпосередньо перед фіксацією, і саме тому Updating the Book entityповідомлення спочатку реєструється.

Використання, @SelectBeforeUpdateщоб уникнути зайвих оновлень

Тепер ОНОВЛЕННЯ завжди буде виконано, навіть якщо сутність не була змінена під час перебування в окремому стані. Щоб цього не допустити, ви можете використовувати @SelectBeforeUpdateанотацію Hibernate, яка спричинить отримання SELECTтвердження, loaded stateяке потім використовується брудним механізмом перевірки.

Отже, якщо ми коментуємо Bookсутність @SelectBeforeUpdateанотацією:

@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {

    //Code omitted for brevity
}

І виконати такий тестовий випадок:

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 виконує такі оператори SQL:

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

Зауважте, що цього разу не UPDATEвиконується, оскільки механізм брудної перевірки в сплячому режимі виявив, що сутність не була змінена.

SaveOrUpdate

Специфічний для сплячки saveOrUpdateметод - лише псевдонім для saveта update.

saveOrUpdateМетод викликає , SaveOrUpdateEventякий обробляється DefaultSaveOrUpdateEventListenerПрослуховувач подій глибокого сну. Тому updateспосіб еквівалентний методам saveі saveOrUpdate.

Тепер ви можете використовувати, saveOrUpdateколи хочете зберегти сутність або змусити форму, UPDATEяк це проілюстровано наступним прикладом.

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);
});

Остерігайся NonUniqueObjectException

Одна проблема, яка може виникнути з save, updateі saveOrUpdateполягає в тому, якщо контекст стійкості вже містить посилання на сутність з тим же ідентифікатором і того ж типу, що і в наступному прикладі:

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
    );
}

Тепер, виконуючи тестовий випадок вище, Hibernate збирається кинути знак a, NonUniqueObjectExceptionоскільки другий EntityManagerвже містить aBook сутність з тим самим ідентифікатором, що і той, до якого ми передаємо update, а Контекст стійкості не може містити двох представлень того самого об'єкта.

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)

Злиття

Щоб уникнути цього NonUniqueObjectException, потрібно використовувати mergeметод, запропонований JPAEntityManager та успадкований також сплячим Session.

Як пояснено в цій статті , mergeвитягує новий знімок об'єкта з бази даних, якщо в контексті постійності не знайдено посилання на об'єкт, і він копіює стан відокремленої сутності, переданий наmerge методу.

mergeМетод викликає , MergeEventякий обробляєтьсяDefaultMergeEventListenerПрослуховувач подій глибокого сну.

Щоб побачити, як mergeпрацює метод, розглянемо наступний приклад, який зберігає Bookсуб'єкт господарювання в одній транзакції, потім він модифікує його, поки суб'єкт знаходиться в відірваному стані, і передає від'єднану сутність mergeв послідовність послідовності послідовності.

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);
});

Під час запуску тестового випадку, Hibernate виконав такі оператори SQL:

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

Зауважте, що повернення сутності, що повертається, mergeвідрізняється від відокремленої, яку ми передали mergeметоду.

Тепер, хоча вам слід віддати перевагу використанню JPA mergeпід час копіювання окремого стану сутності, додаткове SELECTможе бути проблематичним при виконанні завдання пакетної обробки.

З цієї причини вам слід віддати перевагу використанню update коли ви впевнені, що до поточного контексту персистенції вже не додано посилання на сутність та що відокремлена сутність була змінена.

Детальніше про цю тему ознайомтеся з цією статтею .

Висновок

Щоб зберегти сутність, слід скористатися persistметодом JPA . Щоб скопіювати відокремлений стан сутності,merge слід віддати перевагу. updateМетод корисний тільки для завдань пакетної обробки. saveІ saveOrUpdateпросто псевдонімиupdate , і ви не повинні , ймовірно , використовувати їх на всіх.

Деякі розробники телефонують save навіть тоді, коли суттю вже керовано, але це помилка і викликає зайву подію, оскільки для керованих об'єктів UPDATE автоматично обробляється під час змивання контексту Persistent.

Більш детально ознайомтеся з цією статтею .


2

Пам’ятайте, що якщо ви зателефонуєте на оновлення на відокремлений об’єкт, у базі даних завжди буде зроблено оновлення, змінили ви об’єкт чи ні. Якщо це не те, що ви хочете, ви повинні використовувати Session.lock () з LockMode.None.

Вам слід викликати оновлення лише в тому випадку, якщо об'єкт було змінено поза межами вашого поточного сеансу (коли він знаходиться в режимі відключення).


1

Жодна з наведених відповідей не є правильною. Усі ці методи просто схожі, але на практиці роблять абсолютно різні речі. Важко давати короткі коментарі. Краще надати посилання на повну документацію про ці методи: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html


11
Скажіть, будь ласка, чому наступні відповіді невірні.
Ерран Морад

0

Жодна з наведених відповідей не є повною. Хоча відповідь Лева Теобальда виглядає найближчою відповіддю.

Основним моментом є те, як сплячий стан поводиться з державами сутностей і як він поводиться з ними, коли відбувається зміна стану. Все повинно бути розцінено і щодо змивів та викидів, які, схоже, усі повністю ігнорували.

НІКОЛИ НЕ ВИКОРИСТОВУЙТЕ МЕТОД ЗАБЕЗПЕЧЕННЯ ВІДБОРУ. ЗАБУДУЙТЕ, ЩО ВІН ВІДПОВІСТЬ!

Персист

Як всі пояснили, Персист в основному переходить суб'єкт господарювання з "Тимчасового" стану в "Керований". На цьому етапі слама або фіксація можуть створити оператор вставки. Але суб'єкт господарювання все ще залишатиметься в "керованому" стані. Це не змінюється змиванням.

У цей момент, якщо ви знову "наполегливі", змін не буде. І більше не буде жодних заощаджень, якщо ми намагатимемося зберегти збережену сутність.

Весело починається, коли ми намагаємось виселити сутність.

Виселення - це особлива функція Hibernate, яка переведе сутність з "Керованого" на "Окреме". Ми не можемо викликати зберігання на відокремленій сутності. Якщо ми це зробимо, Hibernate створює виняток, і вся транзакція повертається назад на комісію.

Злиття проти оновлення

Це 2 цікаві функції, які роблять різні речі, коли їх обробляють по-різному. Вони обидва намагаються перейти сутність із стану "Окремо" у стан "Керований". Але робити це інакше.

Зрозумійте факт, що "Detached" означає вид "офлайн" держави. а керований означає стан "Інтернет".

Дотримуйтесь коду нижче:

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();

Коли ти це робиш? Як ви думаєте, що станеться? Якщо ви сказали, що це призведе до винятку, то ви правильні. Це призведе до винятку, оскільки злиття працювало на об'єкт сутності, який є відокремленим станом. Але це не змінює стан об'єкта.

За кадром злиття підніме обраний запит і в основному повертає копію сутності, яка знаходиться у приєднаному стані. Дотримуйтесь коду нижче:

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();

Вищевказаний зразок працює, тому що злиття принесло нову сутність у контекст, який перебуває у постійному стані.

При застосуванні з оновленням це добре працює, оскільки оновлення насправді не приносить копії сутності, як об'єднання.

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();

У той же час у відстеженні налагодження ми бачимо, що оновлення не підвищило SQL-запит вибору на зразок злиття.

видалити

У наведеному вище прикладі я використовував delete без розмови про видалення. Видалення в основному перейде сутність із керованого стану у стан "видалення". І коли рум'янець чи зафіксований, видадуть команду delete для зберігання.

Однак можна повернути сутність у стан "керованого" з "вилученого" за допомогою методу "збереження".

Сподіваюсь, що вищезгадане пояснення прояснило будь-які сумніви.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.