EntityManager.merge()
може вставляти нові об’єкти та оновлювати існуючі.
Навіщо хотіти використовувати persist()
(які можуть створювати лише нові об’єкти)?
EntityManager.merge()
може вставляти нові об’єкти та оновлювати існуючі.
Навіщо хотіти використовувати persist()
(які можуть створювати лише нові об’єкти)?
Відповіді:
Будь-який спосіб додасть сутність до PersistentContext, різниця полягає в тому, що ви робите з сутністю згодом.
Persist приймає екземпляр сутності, додає його до контексту і робить цей екземпляр керованим (тобто майбутні оновлення сутності будуть відстежуватися).
Злиття повертає керований екземпляр, до якого була об'єднана держава. Це повертає щось, що існує в PersistenceContext, або створює новий екземпляр вашої сутності. У будь-якому випадку він скопіює стан із наданої сутності та поверне керовану копію. Екземпляр, який ви передасте, не буде керований (будь-які внесені вами зміни не будуть частиною транзакції - якщо ви знову не зателефонуєте злити). Ви можете скористатися поверненим екземпляром (керованим).
Можливо, допоможе приклад коду.
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)
Сценарій 1 і 3 є приблизно еквівалентними, але є деякі ситуації, коли ви хочете використовувати сценарій 2.
merge
, повна копія об'єкта, перш ніж керувати ним, уражає продуктивність?
@GeneratedId
можу я це отримати у сценарії 2?
Персист і злиття мають дві різні цілі (вони взагалі не є альтернативою).
(відредаговано для розширення інформації про відмінності)
зберігаються:
злиття:
зберегти () ефективність:
перманентна () семантика:
Приклад:
{
AnyEntity newEntity;
AnyEntity nonAttachedEntity;
AnyEntity attachedEntity;
// Create a new entity and persist it
newEntity = new AnyEntity();
em.persist(newEntity);
// Save 1 to the database at next flush
newEntity.setValue(1);
// Create a new entity with the same Id than the persisted one.
AnyEntity nonAttachedEntity = new AnyEntity();
nonAttachedEntity.setId(newEntity.getId());
// Save 2 to the database at next flush instead of 1!!!
nonAttachedEntity.setValue(2);
attachedEntity = em.merge(nonAttachedEntity);
// This condition returns true
// merge has found the already attached object (newEntity) and returns it.
if(attachedEntity==newEntity) {
System.out.print("They are the same object!");
}
// Set 3 to value
attachedEntity.setValue(3);
// Really, now both are the same object. Prints 3
System.out.println(newEntity.getValue());
// Modify the un attached object has no effect to the entity manager
// nor to the other objects
nonAttachedEntity.setValue(42);
}
Таким чином існує лише 1 доданий об'єкт для будь-якого реєстру в адміністраторі сутностей.
merge () для об'єкта з ідентифікатором - це щось на зразок:
AnyEntity myMerge(AnyEntity entityToSave) {
AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
if(attached==null) {
attached = new AnyEntity();
em.persist(attached);
}
BeanUtils.copyProperties(attached, entityToSave);
return attached;
}
Хоча якщо підключення до MySQL merge () може бути настільки ж ефективним, як і persist (), використовуючи виклик INSERT з опцією ON DUPLICATE KEY UPDATE, JPA є програмуванням дуже високого рівня, і ви не можете припустити, що це буде повсюдно.
em.persist(x)
з x = em.merge(x)
?
merge()
також може кинутиEntityExistsException
RuntimeException
, але це не згадується в Javadoc.
Якщо ви використовуєте призначений генератор, використання об'єднання замість збереження може призвести до надмірного оператора SQL , що впливає на продуктивність.
Крім того, виклик злиття для керованих об'єктів також є помилкою, оскільки керованими сутностями автоматично керує Hibernate, а їх стан синхронізується із записом бази даних брудним механізмом перевірки при змиванні контенту збереження .
Щоб зрозуміти, як все це працює, спершу слід знати, що Hibernate змінює мислення розробника з операторів SQL на переходи стану сутності .
Після того, як суб'єктом активно керує Hibernate, всі зміни будуть автоматично передані до бази даних.
Монітор сплячого режиму в даний час приєднаних осіб. Але для того, щоб суб'єкт господарювання отримав управління, він повинен знаходитись у правильній державі.
Щоб краще зрозуміти переходи стану JPA, ви можете візуалізувати таку схему:
Або якщо ви використовуєте специфічний API Hibernate:
Як показано на наведених діаграмах, сутність може перебувати в одному з наступних чотирьох станів:
Нове (тимчасове)
Новостворений об’єкт, який ніколи не асоціювався зі сплячим режимом Session
(ака Persistence Context
) і не відображається в жодному рядку таблиці баз даних, вважається в новому (перехідному) стані.
Щоб зберегтись, нам потрібно або явно викликати EntityManager#persist
метод, або скористатися механізмом транзитивної стійкості.
Постійний (керований)
Послідовна сутність була пов’язана з рядком таблиці баз даних, і нею керує поточний контекст постійності. Будь-які зміни, внесені до такої сутності, будуть виявлені та розповсюджені до бази даних (під час часу сесії). У режимі Hibernate нам більше не потрібно виконувати заяви INSERT / UPDATE / DELETE. Hibernate використовує стиль роботи транзакційного запису, і зміни синхронізуються в останній відповідальний момент, під час поточного Session
потоку.
Окремі
Після закриття поточного контексту постійності всі раніше керовані об'єкти стають відокремленими. Послідовні зміни більше не відстежуються, і автоматична синхронізація бази даних не відбуватиметься.
Щоб приєднати відокремлену сутність до активної сплячої сесії, ви можете вибрати один із наступних варіантів:
Повторне з'єднання
Hibernate (але не JPA 2.1) підтримує повторне з'єднання за допомогою методу оновлення Session #. Гібернатна сесія може асоціювати лише один об'єкт Entity для заданого рядка бази даних. Це пояснюється тим, що контекст стійкості діє як кеш пам'яті (кеш першого рівня), і лише одне значення (сутність) пов'язане з заданим ключем (тип сутності та ідентифікатор бази даних). Суб'єкт можна повторно приєднати, лише якщо немає жодного іншого об'єкта JVM (відповідного тому ж рядку бази даних), який уже пов'язаний з поточним сеансом сплячого режиму.
Злиття
Злиття збирається скопіювати відокремлений стан об'єкта (джерело) в керований екземпляр об'єкта (призначення). Якщо об'єднання, що зливається, не має еквівалента в поточній сесії, його буде вилучено з бази даних. Екземпляр від'єднаного об'єкта продовжить залишатися від'єднаним навіть після операції злиття.
Вилучено
Хоча JPA вимагає, щоб лише керованим об'єктам було дозволено видаляти, Hibernate також може видалити відокремлені об'єкти (але лише за допомогою виклику методу видалення сесії #). Видалене об'єкт призначено лише для видалення, а фактичний оператор DELETE бази даних буде виконуватися під час часу сесії.
Я помітив, що коли я використовував em.merge
, я отримував SELECT
заяву для кожного INSERT
, навіть коли не було поля, яке JPA створює для мене - поле первинного ключа - це UUID, який я встановив сам. Я перейшов em.persist(myEntityObject)
і отримав лише INSERT
заяви тоді.
merge()
. У мене була база даних PostgreSQL зі складним виглядом : перегляд агрегованих даних з кількох таблиць (таблиці мали однакову структуру, але різні назви). Так JPA намагався зробити merge()
, але насправді JPA спершу зробив SELECT
(база даних завдяки налаштуванням перегляду могла повернути кілька записів з однаковим первинним ключем з різних таблиць!), Потім JPA (Hibernate - це впровадження) не вдалося: є кілька записів з одним ключем ( org.hibernate.HibernateException: More than one row with the given identifier was found
). У моєму випадку persist()
мені допомогли.
Специфікація JPA говорить про наступне persist()
.
Якщо X є відокремленим об'єктом, він
EntityExistsException
може бути кинутий, коли викликається тривала операція,EntityExistsException
або іншаPersistenceException
може бути кинута під час потоку або введення часу.
Тому використання persist()
було б придатним, коли об'єкт не повинен бути відокремленим об'єктом. Можливо, ви хочете, щоб код кинув, PersistenceException
щоб він швидко вийшов з ладу.
Хоча специфікація незрозуміла , persist()
може встановити @GeneratedValue
@Id
об'єкт. merge()
однак повинен мати об'єкт із @Id
вже створеним.
merge()
однак повинен мати об'єкт із @Id
вже створеним . " Щоразу, коли EntityManager не знайде значення для поля ідентифікатора об'єкта, воно зберігається (вставляється) в БД.
Ще кілька подробиць про злиття, які допоможуть вам використовувати об'єднання над збереженням:
Повернення керованого екземпляра, відмінного від початкового об'єкта, є важливою частиною процесу злиття. Якщо екземпляр суб'єкта господарювання з тим самим ідентифікатором вже існує в контексті збереження, провайдер перезаписать його стан у стан об'єднання, яке об'єднується, але керована версія, що існувала, вже повинна бути повернена клієнту, щоб він міг бути б / в. Якщо постачальник не оновив екземпляр Співробітника в контексті постійності, будь-які посилання на цей екземпляр стануть невідповідними новому стану, в якому об'єднано.
Коли Merge () викликається новим об'єктом, він поводиться аналогічно операції persist (). Це додає сутність до контексту стійкості, але замість того, щоб додати оригінальний екземпляр сутності, він створює нову копію та керує цим екземпляром. Копія, що створюється операцією merge (), зберігається так, ніби на неї було викликано метод persist ().
За наявності зв’язків операція merge () спробує оновити керовану сутність, щоб вказати на керовані версії сутностей, на які посилається відокремлена сутність. Якщо суб'єкт господарювання має відношення до об'єкта, який не має стійкої ідентичності, результат операції злиття не визначений. Деякі провайдери можуть дозволити керованій копії вказувати на непостійний об'єкт, тоді як інші можуть негайно кинути виняток. Операція злиття () може бути необов'язково каскадована в цих випадках, щоб запобігти виникненню виключення. Пізніше в цьому розділі ми розглянемо каскадування операції злиття (). Якщо об'єднання об'єднано вказує на видалену сутність, буде викинуто виняток IllegalArgumentException.
Відносини ледачих навантажень є особливим випадком в операції злиття. Якщо відносини з ледачим завантаженням не були запущені на об'єкт до його від'єднання, це відношення буде ігноровано при об'єднанні сутності. Якщо зв'язок був запущений під час керування, а потім встановлений на нуль, поки сутність від'єднана, керована версія сутності також очистить відносини під час злиття ".
Вся вищенаведена інформація була взята з "Pro JPA 2 Освоєння Java ™ Persistent API" Майком Кітом та Мерріком Шнікаріолом. Глава 6. Відділення та злиття секцій. Ця книга насправді є другою книгою, присвяченою авторами проекту JPA. Ця нова книга має багато нової інформації, ніж колишня. Я дійсно рекомендував прочитати цю книгу для тих, хто буде серйозно пов’язаний із JPA. Мені шкода, що анонімно опублікував свою першу відповідь.
Є ще кілька відмінностей між merge
і persist
(я перелічу ще раз ті, які вже розміщені тут):
D1. merge
не робить передану суть керованою, а повертає інший керований екземпляр. persist
з іншого боку зробить передану особу керованою:
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
D2. Якщо ви видалите сутність, а потім вирішите зберегти об'єкт назад, ви можете зробити це лише з persist (), тому що merge
буде кинути IllegalArgumentException
.
D3. Якщо ви вирішили піклуватися про свої ідентифікатори вручну (наприклад, використовуючи UUID), тоді merge
операція запустить наступні SELECT
запити, щоб шукати існуючі об'єкти з цим ідентифікатором, хоча persist
ці запити можуть не потребувати.
D4. Бувають випадки, коли ви просто не довіряєте коду, який викликає ваш код, і для того, щоб переконатися, що дані не оновлюються, а, скоріше, вставлені, ви повинні використовувати persist
.
Я отримував винятки у форматі lazyLoading, оскільки я намагався отримати доступ до колекції з ледачим завантаженням, яка знаходилася в сесії.
Що я б робив, це було в окремому запиті, вилучити об'єкт із сеансу, а потім спробувати отримати доступ до колекції на моїй сторінці jsp, що було проблематично.
Щоб полегшити це, я оновив ту саму суть у своєму контролері і передав її на свій jsp, хоча я уявляю, що коли я повторно зберігав сеанс, він також буде доступний, але SessionScope
не кидає а LazyLoadingException
, модифікація прикладу 2:
Наступне працювало для мене:
// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"
//access e from jsp and it will work dandy!!
Це пояснення я знайшов у документі Hibernate просвітлюючи, оскільки вони містять випадок використання:
Використання та семантика злиття () видається заплутаною для нових користувачів. По-перше, до тих пір, поки ви не намагаєтеся використовувати стан об'єктів, завантажених в один менеджер об'єктів, в інший новий менеджер сутності, вам взагалі не потрібно використовувати merge () . Деякі цілі програми ніколи не використовуватимуть цей метод.
Зазвичай merge () використовується в наступному сценарії:
- Додаток завантажує об'єкт у перший менеджер сутності
- об'єкт передається до шару презентації
- деякі об’єкти вносяться до модифікацій
- об'єкт передається назад до рівня бізнес-логіки
- додаток зберігає ці зміни, викликаючи merge () у другому менеджері сутності
Ось точний семантичний злиття ():
- якщо є керований екземпляр з тим самим ідентифікатором, який в даний час пов'язаний з контекстом постійності, скопіюйте стан даного об'єкта на керований екземпляр
- якщо в даний час немає керованого екземпляра, пов'язаного з контекстом стійкості, спробуйте завантажити його з бази даних або створити новий керований екземпляр
- керований екземпляр повертається
- даний екземпляр не стає асоційованим із контекстом стійкості, він залишається відокремленим і зазвичай відкидається
Від: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
Переглядаючи відповіді, деякі деталі відсутні стосовно "Cascade" та генерації ідентифікаторів. Дивіться питання
Також варто згадати, що ви можете мати окремі Cascade
примітки для об'єднання та збереження: Cascade.MERGE
і Cascade.PERSIST
які будуть оброблятися відповідно до використовуваного методу.
Специфікація - твій друг;)
JPA - це безперечно велике спрощення в області корпоративних додатків, побудованих на платформі Java. Як розробник, який повинен був впоратися з тонкощами старих бобів сутності в J2EE, я бачу включення JPA до специфікацій Java EE як великий стрибок уперед. Однак, заглиблюючись у деталі JPA, я знаходжу речі не такі прості. У цій статті я розглядаю порівняння методів злиття та збереження EntityManager, поведінка яких перекривається може спричинити плутанину не тільки у новачків. Крім того, я пропоную узагальнення, яке розглядає обидва методи як окремі випадки комбінування більш загального методу.
Суттєві утворення
На відміну від методу злиття, метод персистування є досить простим та інтуїтивним. Найпоширеніший сценарій використання персистуючого методу можна підсумувати наступним чином:
"Новостворений екземпляр класу сутності передається методу" Персист ". Після повернення цього методу управління об'єктом керується та планується для вставки в базу даних. Це може статися під час або до того, як транзакція буде здійснена, або коли буде викликаний метод флеш. Якщо суб'єкт господарювання посилається на іншу сутність через зв'язок, позначений каскадною стратегією PERSIST, ця процедура також застосовується до неї. "
У специфікаціях детальніше йдеться про деталі, проте запам'ятовування їх не є вирішальним, оскільки ці деталі охоплюють лише більш-менш екзотичні ситуації.
Об'єднання об'єднань
У порівнянні зі стійкими, опис поведінки злиття не так просто. Немає головного сценарію, як це у випадку збереження, і програміст повинен запам'ятати всі сценарії, щоб написати правильний код. Мені здається, що розробники проекту JPA хотіли створити якийсь метод, основним завданням якого буде поводження з відокремленими об'єктами (як протилежний персистентному методу, який стосується насамперед новостворених структур). Основним завданням методу злиття є перенесення стану з некерована сутність (передається як аргумент) своєму керованому колезі в контексті стійкості. Це завдання, однак, ділиться далі на кілька сценаріїв, які погіршують зрозумілість загальної поведінки методу.
Замість повторення абзаців зі специфікації JPA я підготував блок-схему, яка схематично зображує поведінку методу злиття:
Отже, коли я повинен використовувати наполегливі та коли злиття?
зберігаються
злиття
Сценарій X:
Таблиця: Spitter (One), Table: Spittles (багато) (Spittles є власником відносин з FK: spitter_id)
Цей сценарій призводить до економії: Spitter і обидва Spittles як би належать одному Spitter.
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.addSpittle(spittle3); // <--persist
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Сценарій Y:
Це врятує Spitter, врятує 2 Spittles. Але вони не будуть посилатися на того ж Spitter!
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Ще одне зауваження:
merge()
буде піклуватися про автоматично згенерований ідентифікатор (перевірений на IDENTITY
та SEQUENCE
), коли запис із таким ідентифікатором вже існує у вашій таблиці. У такому випадку merge()
спробують оновити запис. Якщо, однак, ідентифікатор відсутній або не відповідає жодним існуючим записам, merge()
він повністю ігнорує його і попросить db виділити новий. Іноді це джерело безлічі помилок. Не використовуйте merge()
для присвоєння ідентифікатора для нового запису.
persist()
з іншого боку, ніколи не дозволить вам навіть передати ідентифікатор. Це негайно вийде з ладу. У моєму випадку це:
Викликано: org.hibernate.PersistentObjectException: відокремлений об'єкт передається для збереження
hibernate-jpa javadoc має підказку:
Кидає : javax.persistence.EntityExistsException - якщо сутність вже існує. (Якщо сутність вже існує, EntityExistsException може бути кинуто, коли викликається збережена операція, або EntityExistsException або інший PersistentException може бути кинутий під час флеш-трансляції або фіксації часу.)
persist()
не скаржиться, що має ідентифікатор, він скаржиться лише тоді, коли щось із таким самим ідентифікатором вже є в базі даних.
Можливо, ви завітали сюди за порадами щодо того, коли використовувати персист і коли використовувати злиття . Я думаю, що це залежить від ситуації: наскільки ймовірно, що вам потрібно створити новий запис і наскільки важко отримати збережені дані.
Припустимо, ви можете використовувати природний ключ / ідентифікатор.
Дані потрібно зберігати, але раз у раз існує запис і вимагається оновлення. У цьому випадку ви можете спробувати зберегти, і якщо він кидає EntityExistsException, ви перегляньте його та об'єднайте дані:
спробуйте {entitManager.persist (сутність)}
catch (виняток EntityExistsException) {/ * вилучення та об'єднання * /}
Постійні дані потрібно оновлювати, але час від часу для цих даних ще немає записів. У такому випадку ви перегляньте його та зробіть збереження, якщо організація відсутня:
entitet = entitetManager.find (ключ);
if (entitet == null) {entitManager.persist (сутність); }
else {/ * злиття * /}
Якщо у вас немає природного ключа / ідентифікатора, вам буде складніше зрозуміти, чи існує сутність чи ні, або як її шукати.
Злиття також може бути вирішено двома способами:
persist (сутність) слід використовувати з абсолютно новими об'єктами, щоб додати їх до БД (якщо сутність вже існує в БД, буде кинути EntityExistsException).
слід об'єднати об'єднання (сутність), щоб повернути сутність в контекст стійкості, якщо сутність була від'єднана та змінена.
Можливо, зберігається генерує оператор INSERT sql та об'єднує оператор UPDATE sql (але я не впевнений).