مقدمة
لذلك دعونا نبدأ! ماذا يعني
التعليق التوضيحي للنسخة في JPA؟
باختصار ، هي المسؤولة عن الحجب في JPA. يحل هذا التعليق التوضيحي إحدى المشكلات التي قد تنشأ نتيجة للمعاملات الموازية.
ما هي المشاكل التي يمكن أن تنشأ؟
- يمكن أن تحدث التحديثات المفقودة في الحالات التي تحاول فيها معاملتان تعملان بالتوازي تحديث نفس البيانات.
- تحدث القراءات القذرة عندما ترى معاملة التغييرات التي لم يتم حفظها بعد بواسطة معاملة أخرى. في مثل هذه الحالات ، قد تحدث مشكلة بسبب التراجع عن المعاملة الثانية ، ولكن تمت قراءة البيانات بالفعل أولاً.
- تحدث القراءات غير القابلة للتكرار عندما تتلقى المعاملة الأولى البيانات ، وأجرت المعاملة الثانية تغييرات عليها والتزمت بها حتى نهاية المعاملة الأولى. بمعنى آخر ، عندما يكون نفس طلب الحصول على نفس المعاملة ، داخل نفس المعاملة ، يؤدي إلى عرض نتائج مختلفة.
- تعتبر القراءة الوهمية مشكلة شبيهة بتكرار القراءات ، باستثناء أنه يتم إرجاع عدد مختلف من الأسطر.
باختصار عن قراراتهم
- قراءة غير ملتزم بها - تم حلها باستخدام التعليق التوضيحي للنسخة في JPA (هذه هي المقالة بالضبط)
- اقرأ ملتزم - يسمح بقراءة التغييرات الملتزمة فقط
- قراءة مكررة - قليلا أكثر تعقيدا هنا. تعاملنا "لا يرى" تغييرات في البيانات التي قرأتها سابقًا ، ولا يمكن للمعاملات الأخرى تغيير البيانات التي تدخل في معاملاتنا.
- SERIALIZABLE - تنفيذ المعاملة التسلسلية
تغطي كل فقرة لاحقة جميع الفقرات السابقة ، وبعبارة أخرى ، يمكنها أن تحل محل الحلول المشار إليها سابقًا. وبالتالي ، فإن SERIALIZABLE لديه أعلى مستوى للعزل ، وقراءة READ غير ملتزم بها.
الإصدار
الإصدار يحل المشكلة مع
التحديثات المفقودة . كيف بالضبط ، الآن سنرى.
قبل الانتقال إلى الكود ، تجدر الإشارة إلى أن هناك نوعين من الأقفال:
متفائل ومتشائم . الفرق هو أن الأول يسترشد بالمواقف التي تحاول فيها العديد من المعاملات تغيير حقل واحد في نفس الوقت ، نادرًا ما يحدث ، بينما يسترشد الآخرون بالموقف المعاكس. وفقًا لهذا ، هناك اختلاف في منطق تنفيذها.
في الأقفال
المتفائلة ، عند الالتزام بقاعدة البيانات ، تتم مقارنة قيمة الحقل الذي تم وضع علامة عليه كإصدار في وقت استلام البيانات وفي الوقت الحالي. إذا كان قد تغير ، فهناك بعض المعاملات الأخرى قبلنا وتمكنت من تغيير البيانات ، ثم في هذه الحالة ، ستلقي معاملتنا خطأ ، وتحتاج إلى إعادة تشغيلها.
عند استخدام الأقفال
المتفائلة ، يتم ضمان مستوى أعلى من القدرة التنافسية عند الوصول إلى قاعدة البيانات ، ولكن في هذه الحالة يجب عليك تكرار المعاملات التي لم يكن لديها الوقت لإجراء تغييرات قبل الآخرين.
في الحالات
المتشائمة ، يتم فرض القفل مباشرة قبل تعديل البيانات المزعوم على جميع الخطوط التي من المفترض أن يؤثر هذا التعديل عليها.
وعند استخدام الأقفال
المتشائمة ، يتم ضمان عدم وجود تناقضات أثناء تنفيذ المعاملة ، وذلك بسبب وضع الباقي في وضع الاستعداد (لكنه يستغرق وقتًا) ، ونتيجة لذلك ، يخفض مستوى المنافسة.
LockModeType أو كيفية تعيين قفل
يمكن ضبط القفل عن طريق استدعاء طريقة مظهر EntityManager.
entityManager.lock(myObject, LockModeType.OPTIMISTIC);
يعرف LockModeType استراتيجية تأمين.
يمكن أن يكون LockModeType من 6 أنواع (2 منها
متفائل و 3
متشائم ):
- لا شيء - لا قفل
- OPTIMISTIC
- OPTIMISTIC_FORCE_INCREMENT
- PESSIMISTIC_READ
- PESSIMISTIC_WRITE
- 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.