رائع الشرح نسخة في JPA

مقدمة


لذلك دعونا نبدأ! ماذا يعني التعليق التوضيحي للنسخة في JPA؟

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

ما هي المشاكل التي يمكن أن تنشأ؟


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

باختصار عن قراراتهم


  1. قراءة غير ملتزم بها - تم حلها باستخدام التعليق التوضيحي للنسخة في JPA (هذه هي المقالة بالضبط)
  2. اقرأ ملتزم - يسمح بقراءة التغييرات الملتزمة فقط
  3. قراءة مكررة - قليلا أكثر تعقيدا هنا. تعاملنا "لا يرى" تغييرات في البيانات التي قرأتها سابقًا ، ولا يمكن للمعاملات الأخرى تغيير البيانات التي تدخل في معاملاتنا.
  4. SERIALIZABLE - تنفيذ المعاملة التسلسلية

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

الإصدار


الإصدار يحل المشكلة مع التحديثات المفقودة . كيف بالضبط ، الآن سنرى.

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

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

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

في الحالات المتشائمة ، يتم فرض القفل مباشرة قبل تعديل البيانات المزعوم على جميع الخطوط التي من المفترض أن يؤثر هذا التعديل عليها.

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

LockModeType أو كيفية تعيين قفل


يمكن ضبط القفل عن طريق استدعاء طريقة مظهر EntityManager.

entityManager.lock(myObject, LockModeType.OPTIMISTIC); 

يعرف LockModeType استراتيجية تأمين.

يمكن أن يكون LockModeType من 6 أنواع (2 منها متفائل و 3 متشائم ):

  1. لا شيء - لا قفل
  2. OPTIMISTIC
  3. OPTIMISTIC_FORCE_INCREMENT
  4. PESSIMISTIC_READ
  5. PESSIMISTIC_WRITE
  6. PESSIMISTIC_FORCE_INCREMENT

إنشاء الكيان لدينا
 import lombok.Getter; import lombok.Setter; import javax.persistence.*; @EntityListeners(OperationListenerForMyEntity.class) @Entity public class MyEntity{ @Version private long version; @Id @GeneratedValue @Getter @Setter private Integer id; @Getter @Setter private String value; @Override public String toString() { return "MyEntity{" + "id=" + id + ", version=" + version + ", value='" + value + '\'' + '}'; } } 


لنقم بإنشاء فصل حيث سيتم تطبيق جميع أساليب رد الاتصال.
 import javax.persistence.*; public class OperationListenerForMyEntity { @PostLoad public void postLoad(MyEntity obj) { System.out.println("Loaded operation: " + obj); } @PrePersist public void prePersist(MyEntity obj) { System.out.println("Pre-Persistiting operation: " + obj); } @PostPersist public void postPersist(MyEntity obj) { System.out.println("Post-Persist operation: " + obj); } @PreRemove public void preRemove(MyEntity obj) { System.out.println("Pre-Removing operation: " + obj); } @PostRemove public void postRemove(MyEntity obj) { System.out.println("Post-Remove operation: " + obj); } @PreUpdate public void preUpdate(MyEntity obj) { System.out.println("Pre-Updating operation: " + obj); } @PostUpdate public void postUpdate(MyEntity obj) { System.out.println("Post-Update operation: " + obj); } } 


Main.java
 import javax.persistence.*; import java.util.concurrent.*; //        ,   . public class Main { //  , ..  EntityManagerFactory  ,     . private static EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("ru.easyjava.data.jpa.hibernate"); public static void main(String[] args) { //  10 (  ,       ). ExecutorService es = Executors.newFixedThreadPool(10); try { //  persistFill()   - . persistFill(); for(int i=0; i<10; i++){ int finalI = i; es.execute(() -> { //      updateEntity(finalI) ,  java       .    java -  ,      id,       , id    ,       (  ,      persistFill(),  id       500). updateEntity(finalI); }); } es.shutdown(); try { es.awaitTermination(10, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } finally { entityManagerFactory.close(); } } //         . private static void updateEntity(int index) { //  EntityManager  ,     ,    . EntityManager em = entityManagerFactory.createEntityManager(); MyEntity myEntity = null; try { em.getTransaction().begin(); //        1. myEntity = em.find(MyEntity.class, 1); //   sout,       "" . System.out.println("load = "+index); //       (  LockModeType.*). em.lock(myEntity, LockModeType.OPTIMISTIC); //   Value,  ,        . myEntity.setValue("WoW_" + index); em.getTransaction().commit(); em.close(); System.out.println("--Greeter updated : " + myEntity +" __--__ "+ index); }catch(RollbackException ex){ System.out.println(", =" + myEntity); } } public static void persistFill() { MyEntity myEntity = new MyEntity(); myEntity.setValue("JPA"); EntityManager em = entityManagerFactory.createEntityManager(); em.getTransaction().begin(); em.persist(myEntity); em.getTransaction().commit(); em.close(); } } 


تشغيل أولاً مع أسلوب updateEntity علق
 Pre-Persistiting operation: MyEntity{id=null, version=0, value='JPA'} Post-Persist operation: MyEntity{id=531, version=0, value='JPA'}  .  id   find   . 


LockModeType.OPTIMISTIC

هذه كتلة متفائلة ، حسنًا ، هذا منطقي جدًا. كما كتبت أعلاه ، تتم مقارنة قيمة حقل الإصدار ؛ إذا كان مختلفًا ، يتم إلقاء خطأ. تحقق من ذلك.

النتائج:
 Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 3 Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 2 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_2'} Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_3'} Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 9 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_9'} Loaded operation: MyEntity{id=531, version=0, value='JPA'} load = 1 Pre-Updating operation: MyEntity{id=531, version=0, value='WoW_1'} Post-Update operation: MyEntity{id=531, version=1, value='WoW_1'} --Greeter updated : MyEntity{id=531, version=1, value='WoW_1'} __--__ 1 , =MyEntity{id=531, version=0, value='WoW_2'} , =MyEntity{id=531, version=0, value='WoW_3'} Loaded operation: MyEntity{id=531, version=1, value='WoW_1'} load = 4 Pre-Updating operation: MyEntity{id=531, version=1, value='WoW_4'} Post-Update operation: MyEntity{id=531, version=2, value='WoW_4'} --Greeter updated : MyEntity{id=531, version=2, value='WoW_4'} __--__ 4 , =MyEntity{id=531, version=0, value='WoW_9'} Loaded operation: MyEntity{id=531, version=2, value='WoW_4'} load = 0 Pre-Updating operation: MyEntity{id=531, version=2, value='WoW_0'} Post-Update operation: MyEntity{id=531, version=3, value='WoW_0'} --Greeter updated : MyEntity{id=531, version=3, value='WoW_0'} __--__ 0 Loaded operation: MyEntity{id=531, version=3, value='WoW_0'} load = 6 Pre-Updating operation: MyEntity{id=531, version=3, value='WoW_6'} Post-Update operation: MyEntity{id=531, version=4, value='WoW_6'} Loaded operation: MyEntity{id=531, version=4, value='WoW_6'} load = 5 Pre-Updating operation: MyEntity{id=531, version=4, value='WoW_5'} Post-Update operation: MyEntity{id=531, version=5, value='WoW_5'} --Greeter updated : MyEntity{id=531, version=4, value='WoW_6'} __--__ 6 --Greeter updated : MyEntity{id=531, version=5, value='WoW_5'} __--__ 5 Loaded operation: MyEntity{id=531, version=5, value='WoW_5'} load = 7 Pre-Updating operation: MyEntity{id=531, version=5, value='WoW_7'} Post-Update operation: MyEntity{id=531, version=6, value='WoW_7'} Loaded operation: MyEntity{id=531, version=5, value='WoW_5'} load = 8 Pre-Updating operation: MyEntity{id=531, version=5, value='WoW_8'} --Greeter updated : MyEntity{id=531, version=6, value='WoW_7'} __--__ 7 , =MyEntity{id=531, version=5, value='WoW_8'} 


الملاحظات: كما ترون من النتائج ، كانت التدفقات 3 و 2 و 9 و 1 أول من يتم تحميله ، وتم استدعاء أساليب رد الاتصال قبل التحديث لها. كان أول مؤشر ترابط تم استدعاء أسلوب Post-Update به هو 1 ، كما ترون من النتائج ، تم بالفعل تغيير الحقل الذي يحمل علامة التعليق التوضيحي للنسخة (زاد بنسبة 1). وفقا لذلك ، ألقى جميع المواضيع المتبقية 2 ، 3 ، 9 استثناء. و هكذا. والنتيجة هي القيمة = WoW_7 ، الإصدار = 6. في الواقع ، كان آخر ما بعد التحديث في الموضوع 7 مع الإصدار = 6.

LockModeType.OPTIMISTIC_FORCE_INCREMENT

إنه يعمل وفقًا لنفس الخوارزمية مثل LockModeType.OPTIMISTIC ، إلا أنه بعد الالتزام يتم زيادة قيمة حقل الإصدار بالقوة بمقدار 1. ونتيجة لذلك ، بعد كل التزام ، سيزداد الحقل بمقدار 2 (الزيادة التي يمكن رؤيتها في زيادة القسري بعد التحديث +) . سؤال لماذا؟ إذا كنا لا نزال نرغب في "استحضار" هذه البيانات بعد الالتزام ، ونحن لسنا بحاجة إلى معاملات من طرف ثالث يمكنها الفصل بين الالتزام الأول وإغلاق معاملتنا.

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

LockModeType.PESSIMISTIC_READ و LockModeType.PESSIMISTIC_WRITE و LockModeType.PESSIMISTIC_FORCE_INCREMENT

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

LockModeType.PESSIMISTIC_READ - قفل قراءة متشائم .
LockModeType.PESSIMISTIC_WRITE - قفل متشائم للكتابة (وقراءة).
LockModeType.PESSIMISTIC_FORCE_INCREMENT - قفل متشائم للكتابة (وقراءة) مع زيادة قسرية في حقل الإصدار.

نتيجة لهذه الأقفال ، قد يحدث انتظار طويل للقفل ، الأمر الذي قد يؤدي بدوره إلى حدوث خطأ.

النتيجة على LockModeType.PESSIMISTIC_READ (لم يتم تقديمها بالكامل):
 load = 0 Pre-Updating operation: MyEntity{id=549, version=5, value='WoW_0'} Post-Update operation: MyEntity{id=549, version=6, value='WoW_0'} Loaded operation: MyEntity{id=549, version=6, value='WoW_0'} load = 8 Pre-Updating operation: MyEntity{id=549, version=6, value='WoW_8'} Loaded operation: MyEntity{id=549, version=6, value='WoW_0'} load = 4 Pre-Updating operation: MyEntity{id=549, version=6, value='WoW_4'} ... ERROR: :   :  22760    ExclusiveLock  " (0,66)  287733   271341";   20876.  20876    ShareLock  " 8812";   22760. 


نتيجة لذلك ، قام كل من الخيوط 4 و 8 بحظر بعضها البعض ، مما أدى إلى تعارض غير قابل للحل. قبل ذلك ، لم يتدخل أحد في الموضوع 0. موقف مماثل مع جميع المواضيع تصل إلى 0.

Source: https://habr.com/ru/post/ar434836/


All Articles