ما هي الاختلافات بين طرق الحفظ المختلفة في السبات؟

StackOverflow https://stackoverflow.com/questions/161224

  •  03-07-2019
  •  | 
  •  

سؤال

يحتوي Hibernate على مجموعة من الأساليب التي تأخذ الكائن الخاص بك، بطريقة أو بأخرى، وتضعه في قاعدة البيانات.ما هي الاختلافات بينهما، ومتى يجب استخدامها، ولماذا لا توجد طريقة ذكية واحدة فقط تعرف متى تستخدم ماذا؟

الطرق التي حددتها حتى الآن هي:

  • save()
  • update()
  • saveOrUpdate()
  • saveOrUpdateCopy()
  • merge()
  • persist()
هل كانت مفيدة؟

المحلول

هذا هو فهمي للأساليب.وتستند هذه أساسا على واجهة برمجة التطبيقات على الرغم من أنني لا أستخدم كل هذه الأشياء في الممارسة العملية.

saveOrUpdateيتم حفظ المكالمات أو تحديثها بناءً على بعض عمليات التحقق.على سبيل المثالفي حالة عدم وجود معرف، يتم استدعاء الحفظ.خلاف ذلك يتم استدعاء التحديث.

يحفظيستمر الكيان.سيتم تعيين معرف إذا لم يكن موجودًا.إذا حدث ذلك، فهو يقوم بالتحديث بشكل أساسي.إرجاع المعرف الذي تم إنشاؤه للكيان.

تحديثمحاولات لاستمرار الكيان باستخدام معرف موجود.في حالة عدم وجود معرف، أعتقد أنه سيتم طرح استثناء.

saveOrUpdateCopyتم إهمال هذا ولا ينبغي استخدامه بعد الآن.بدلا من ذلك هناك...

دمجالآن هذا هو المكان الذي بدأت فيه معرفتي بالتعثر.الشيء المهم هنا هو الفرق بين الكيانات العابرة والمنفصلة والمستمرة.لمزيد من المعلومات حول حالات الكائن، نلقي نظرة هنا.مع الحفظ والتحديث، أنت تتعامل مع الكائنات المستمرة.وهي مرتبطة بجلسة حتى يعرف السبات ما الذي تغير.ولكن عندما يكون لديك كائن عابر، فلا توجد جلسة معنية.في هذه الحالات، تحتاج إلى استخدام الدمج للتحديثات والاستمرار للحفظ.

ثابركما ذكر أعلاه، يتم استخدام هذا على الكائنات العابرة.ولا يقوم بإرجاع المعرف الذي تم إنشاؤه.

نصائح أخرى

╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
║    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()         ║
║              ║                               ║                                ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
  • انظر منتدى السبات للحصول على شرح للاختلافات الدقيقة بين الاستمرار والحفظ.يبدو أن الاختلاف هو الوقت الذي يتم فيه تنفيذ عبارة INSERT في النهاية.منذ يحفظ إذا قام بإرجاع المعرف، فيجب تنفيذ عبارة INSERT على الفور بغض النظر عن حالة المعاملة (وهو أمر سيئ بشكل عام). ثابر لن يتم تنفيذ أي بيانات خارج المعاملة الجارية حاليًا فقط لتعيين المعرف.حفظ/استمرار العمل على حد سواء حالات عابرة, ، أي المثيلات التي لم يتم تعيين معرف لها بعد وبالتالي لا يتم حفظها في قاعدة البيانات.

  • تحديث و دمج كلاهما يعمل على حالات منفصلة, ، أي المثيلات التي لها إدخال مطابق في قاعدة البيانات ولكنها غير مرتبطة حاليًا بجلسة (أو تتم إدارتها بواسطة).الفرق بينهما هو ما يحدث للمثيل الذي تم تمريره إلى الوظيفة. تحديث يحاول إعادة ربط المثيل، وهذا يعني أنه قد لا يكون هناك مثيل آخر للكيان المستمر المرتبط بالجلسة الآن وإلا فسيتم طرح استثناء. دمج, ومع ذلك، يقوم فقط بنسخ جميع القيم إلى مثيل مستمر في الجلسة (والذي سيتم تحميله إذا لم يتم تحميله حاليًا).لم يتم تغيير كائن الإدخال.لذا دمج هو أكثر عمومية من تحديث, ، ولكن قد تستخدم المزيد من الموارد.

وهذا الرابط يشرح بطريقة جيدة :

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

لدينا جميعًا تلك المشكلات التي نواجهها بشكل غير متكرر لدرجة أنه عندما نراها مرة أخرى، نعلم أننا قمنا بحلها، ولكن لا يمكننا أن نتذكر كيف.

يعد NonUniqueObjectException الذي تم طرحه عند استخدام Session.saveOrUpdate() في Hibernate واحدًا من أعمالي.سأضيف وظائف جديدة إلى تطبيق معقد.جميع اختبارات وحدتي تعمل بشكل جيد.ثم عند اختبار واجهة المستخدم ، في محاولة لإنقاذ كائن ما ، أبدأ في الحصول على استثناء من خلال الرسالة "كائن مختلف ذي قيمة المعرف نفسه مرتبط بالفعل بالجلسة". إليك بعض الكود على مثال من ثبات Java مع السبات.

            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 بالمشكلة عبر NonUniqueObjectException.

اذن، ماذا علينا ان نفعل؟في Hibernate 3، لدينا دمج () (في Hibernate 2، استخدم saveOrUpdateCopy()).ستجبر هذه الطريقة السبات على نسخ أي تغييرات من المثيلات المنفصلة الأخرى إلى المثيل الذي تريد حفظه، وبالتالي دمج جميع التغييرات في الذاكرة قبل الحفظ.

        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 من هذه النقطة فصاعدًا.

من المهم أيضًا ملاحظة أن Java Persistence API (JPA) لا تحتوي على مفهوم الكائنات المنفصلة والمعاد توصيلها، وتستخدم EntityManager.persist() وEntityManager.merge().

لقد وجدت بشكل عام أنه عند استخدام Hibernate، يكون saveOrUpdate() عادةً كافيًا لاحتياجاتي.عادةً ما أحتاج فقط إلى استخدام الدمج عندما يكون لدي كائنات يمكن أن تحتوي على مراجع لكائنات من نفس النوع.في الآونة الأخيرة، كان سبب الاستثناء في الكود الذي يؤكد أن المرجع لم يكن متكررًا.كنت أقوم بتحميل نفس الكائن في جلستي كجزء من التحقق من الصحة، مما تسبب في حدوث الخطأ.

أين واجهت هذه المشكلة؟هل نجح الدمج معك أم أنك بحاجة إلى حل آخر؟هل تفضل استخدام الدمج دائمًا أم تفضل استخدامه فقط حسب الحاجة لحالات محددة

في الواقع الفرق بين السبات save() و persist() تعتمد الطرق على فئة المولد التي نستخدمها.

إذا تم تعيين فئة المولد لدينا، فلا يوجد فرق بين save() و persist() طُرق.نظرًا لأن المولد "المعين" يعني ، كمبرمج ، نحتاج إلى إعطاء القيمة الرئيسية الأساسية للحفظ في قاعدة البيانات الصحي ستبذل kibernate It Self قيمة معرف المفتاح الأساسي في قاعدة البيانات الصحيحة [بخلاف المولد المعين ، تستخدم السبات فقط لرعاية قيمة معرف المفتاح الأساسي تذكر] ، لذلك في هذه الحالة إذا اتصلنا save() أو persist() الطريقة التي سيقوم بإدراج السجل في قاعدة البيانات بشكل طبيعي ولكن اسمع الشيء هو ، save() يمكن للأسلوب إرجاع قيمة معرف المفتاح الأساسي التي تم إنشاؤها بواسطة السبات ويمكننا رؤيتها من خلالها

long s = session.save(k);

وفي هذه الحالة نفسها، persist() لن يعيد أي قيمة إلى العميل أبدًا.

لقد وجدت مثالًا جيدًا يوضح الاختلافات بين جميع طرق حفظ السبات:

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

باختصار، بحسب الرابط أعلاه:

يحفظ()

  • يمكننا استدعاء هذه الطريقة خارج المعاملة.إذا استخدمنا هذا بدون معاملة وكان لدينا تتالي بين الكيانات، فسيتم حفظ الكيان الأساسي فقط ما لم نقوم بمسح الجلسة.
  • لذلك، إذا كانت هناك كائنات أخرى تم تعيينها من الكائن الأساسي، فسيتم حفظها في وقت تنفيذ المعاملة أو عندما نقوم بمسح الجلسة.

ثابر()

  • إنه مشابه لاستخدام save() في المعاملة، لذا فهو آمن ويعتني بأي كائنات متتالية.

حفظأوتحديث ()

  • يمكن استخدامها مع المعاملة أو بدونها، تمامًا مثل save()، إذا تم استخدامها بدون المعاملة، فلن يتم حفظ الكيانات المعينة إلا إذا قمنا بمسح الجلسة.

  • النتائج في إدراج أو تحديث الاستعلامات بناءً على البيانات المقدمة.إذا كانت البيانات موجودة في قاعدة البيانات، فسيتم تنفيذ استعلام التحديث.

تحديث()

  • يجب استخدام تحديث السبات حيث نعلم أننا نقوم فقط بتحديث معلومات الكيان.تضيف هذه العملية كائن الكيان إلى السياق المستمر ويتم تعقب المزيد من التغييرات وحفظها عند تنفيذ المعاملة.
  • وبالتالي، حتى بعد استدعاء التحديث، إذا قمنا بتعيين أي قيم في الكيان، فسيتم تحديثها عند تنفيذ المعاملة.

دمج()

  • يمكن استخدام دمج السبات لتحديث القيم الموجودة، ولكن هذه الطريقة تقوم بإنشاء نسخة من كائن الكيان الذي تم تمريره وإعادته.الكائن الذي تم إرجاعه هو جزء من سياق مستمر ويتم تعقبه بحثًا عن أي تغييرات، ولا يتم تعقب الكائن الذي تم تمريره.هذا هو الفرق الرئيسي بين merge()‎ وبين جميع الطرق الأخرى.

وللحصول على أمثلة عملية لكل هذه الطرق، يرجى الرجوع إلى الرابط الذي ذكرته أعلاه، فهو يعرض أمثلة لكل هذه الطرق المختلفة.

انتبه إلى أنه إذا قمت باستدعاء تحديث على كائن منفصل، فسيكون هناك دائمًا تحديث يتم إجراؤه في قاعدة البيانات سواء قمت بتغيير الكائن أم لا.إذا لم يكن هذا هو ما تريده فيجب عليك استخدام Session.lock() مع LockMode.None.

يجب عليك استدعاء التحديث فقط إذا تم تغيير الكائن خارج نطاق جلستك الحالية (عندما تكون في الوضع المنفصل).

كما شرحت في هذا المقال, ، يجب أن تفضل أساليب JPA في معظم الأوقات، و update لمهام معالجة الدفعات.

يمكن أن يكون كيان JPA أو Hibernate في إحدى الحالات الأربع التالية:

  • عابر (جديد)
  • مُدارة (مستمرة)
  • منفصل
  • تمت الإزالة (محذوفة)

يتم الانتقال من حالة إلى أخرى عبر EntityManager أو أساليب الجلسة.

على سبيل المثال، JPA EntityManager يوفر طرق انتقال حالة الكيان التالية.

enter image description here

السبات Session تنفذ جميع JPA EntityManager الأساليب وتوفر بعض أساليب انتقال حالة الكيان الإضافية مثل save, saveOrUpdate و update.

enter image description here

ثابر

لتغيير حالة الكيان من عابر (جديد) إلى مُدار (مستمر)، يمكننا استخدام 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()
    );
});

يقوم السبات بإنشاء عبارات 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 والسبات Session تُعرف باسم ذاكرة التخزين المؤقت من المستوى الأول.

عند الاتصال persist, ، يتم إرفاق الكيان فقط بسياق الاستمرارية قيد التشغيل حاليًا، ويمكن تأجيل INSERT حتى flush يسمى.

الاستثناء الوحيد هو مولد الهوية مما يؤدي إلى تشغيل INSERT على الفور لأن هذه هي الطريقة الوحيدة للحصول على معرف الكيان.لهذا السبب، لا يمكن لـ Hibernate إدراج عمليات الإدخال المجمعة للكيانات التي تستخدم منشئ IDENTITY.لمزيد من التفاصيل حول هذا الموضوع، راجع هذا المقال.

يحفظ

السبات الخاص save تسبق هذه الطريقة JPA وهي متاحة منذ بداية مشروع Hibernate.

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

عند تشغيل حالة الاختبار أعلاه، يقوم السبات بإنشاء عبارات 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 استدعاء الطريقة.

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

عند تنفيذ حالة الاختبار أعلاه، يقوم السبات بإنشاء عبارات 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 التعليق التوضيحي للإسبات والذي سيؤدي إلى تشغيل 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);
});

ينفذ السبات عبارات 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 الأسلوب هو مجرد اسم مستعار ل 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 بإلقاء ملف NonUniqueObjectException لأن الثاني EntityManager يحتوي بالفعل على أ Book كيان له نفس المعرف الذي نمرر إليه 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 الطريقة التي تقدمها JPA EntityManager ويرثها السبات 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 عندما تكون متأكدًا من عدم وجود مرجع كيان مرتبط بالفعل بسياق الثبات قيد التشغيل حاليًا وأنه تم تعديل الكيان المنفصل.

لمزيد من التفاصيل حول هذا الموضوع، راجع هذا المقال.

خاتمة

للاستمرار في كيان ما، يجب عليك استخدام JPA persist طريقة.لنسخ حالة الكيان المنفصل، merge ينبغي أن يكون المفضل.ال update الطريقة مفيدة لمهام معالجة الدُفعات فقط.ال save و saveOrUpdate هي مجرد أسماء مستعارة ل update وربما لا ينبغي عليك استخدامها على الإطلاق.

بعض المطورين يدعون save حتى عندما يكون الكيان مُدارًا بالفعل، ولكن هذا خطأ ويؤدي إلى حدث متكرر لأنه، بالنسبة للكيانات المُدارة، تتم معالجة التحديث تلقائيًا في وقت تدفق سياق الثبات.

لمزيد من التفاصيل، تحقق من هذا المقال.

لا شيء من الإجابات التالية صحيحة.تبدو كل هذه الأساليب متشابهة، لكنها في الواقع تفعل أشياء مختلفة تمامًا.ومن الصعب إعطاء تعليقات قصيرة.من الأفضل تقديم رابط للوثائق الكاملة حول هذه الطرق:http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html

لا شيء من الإجابات المذكورة أعلاه كاملة.على الرغم من أن إجابة ليو ثيوبالد تبدو أقرب إجابة.

النقطة الأساسية هي كيفية تعامل السبات مع حالات الكيانات وكيفية التعامل معها عند حدوث تغيير في الحالة.يجب أن يُنظر إلى كل شيء فيما يتعلق بالتدفقات والالتزامات أيضًا، والتي يبدو أن الجميع تجاهلها تمامًا.

لا تستخدم أبدًا طريقة الحفظ الخاصة بالإسبات.ننسى أنه موجود حتى في السبات!

ثابر

كما أوضح الجميع، يقوم Persist بشكل أساسي بنقل الكيان من الحالة "العابرة" إلى الحالة "المدارة".عند هذه النقطة، يمكن للسلاش أو الالتزام إنشاء عبارة إدراج.لكن الكيان سيظل في حالة "مُدارة".هذا لا يتغير مع التدفق.

في هذه المرحلة، إذا "استمريت" مرة أخرى، فلن يكون هناك أي تغيير.ولن يكون هناك المزيد من عمليات الحفظ إذا حاولنا الحفاظ على كيان مستمر.

تبدأ المتعة عندما نحاول طرد الكيان.

الإخلاء هو وظيفة خاصة للإسبات والتي ستنقل الكيان من "مُدار" إلى "منفصل".لا يمكننا استدعاء استمرار على كيان منفصل.إذا فعلنا ذلك، فسيقوم السبات بإصدار استثناء وسيتم التراجع عن المعاملة بأكملها عند الالتزام.

دمج مقابل التحديث

هاتان وظيفتان مثيرتان للاهتمام تقومان بأشياء مختلفة عند التعامل معها بطرق مختلفة.يحاول كلاهما نقل الكيان من الحالة "المنفصلة" إلى الحالة "المدارة".لكن القيام بذلك بشكل مختلف.

افهم حقيقة أن المنفصل يعني نوعًا من الحالة "غير المتصلة بالإنترنت".وتدار وسائل "على الإنترنت" الدولة.

لاحظ الكود أدناه:

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 لتحديد مثل الدمج.

يمسح

في المثال أعلاه استخدمت الحذف دون الحديث عن الحذف.سيؤدي الحذف بشكل أساسي إلى نقل الكيان من الحالة المُدارة إلى الحالة "المُزالة".وعند المسح أو الالتزام سيصدر أمر حذف للتخزين.

ومع ذلك، فمن الممكن إعادة الكيان إلى الحالة "المُدارة" من الحالة "المُزالة" باستخدام طريقة الاستمرار.

نأمل أن يوضح التفسير أعلاه أي شكوك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top