يوم جيد للجميع!
حسنًا ، تكون نهاية الشهر مكثفة دائمًا ، ولا يتبقى سوى يوم واحد فقط حتى بداية المرحلة الثانية من دورة
"Developer on Spring Framework" ، وهي دورة رائعة ومثيرة للاهتمام يدرسها
يوري الغاضبة على قدم المساواة (حيث يدعوه بعض الطلاب إلى مستوى المتطلبات في DZ) ، لذلك دعونا ننظر إلى مادة أخرى أعددناها لك.
دعنا نذهب.
مقدمةمعظم الوقت ، لا يولي المطورون أهمية لإدارة المعاملات. نتيجةً لذلك ، يجب إعادة كتابة معظم الكود لاحقًا ، أو يقوم المطور بتطبيق إدارة المعاملات دون معرفة كيفية عمله فعليًا أو الجوانب التي يجب استخدامها على وجه التحديد في حالتهم.
يتمثل أحد الجوانب المهمة في إدارة المعاملات في تحديد الحدود الصحيحة للمعاملة متى يجب أن تبدأ المعاملة ومتى تنتهي ، ومتى يجب إضافة البيانات إلى قاعدة البيانات ومتى يجب ضخها مرة أخرى (في حالة وجود استثناء).

الجانب الأكثر أهمية للمطورين هو فهم أفضل السبل لتنفيذ إدارة المعاملات في التطبيق. لذلك دعونا ننظر إلى الخيارات المختلفة.
أساليب إدارة المعاملاتيمكن إدارة المعاملات بالطرق التالية:
1. برنامج التحكم عن طريق كتابة التعليمات البرمجية المخصصةهذه طريقة قديمة لإدارة المعاملات.
EntityManagerFactory factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME"); EntityManager entityManager = entityManagerFactory.createEntityManager(); Transaction transaction = entityManager.getTransaction() try { transaction.begin(); someBusinessCode(); transaction.commit(); } catch(Exception ex) { transaction.rollback(); throw ex; }
الايجابيات :
- حدود المعاملة واضحة في الكود.
سلبيات :
- فمن تكرار وعرضة للخطأ.
- أي خطأ يمكن أن يكون لها تأثير كبير جدا.
- تحتاج إلى كتابة الكثير من القوالب ، أيضًا ، إذا كنت تريد استدعاء طريقة أخرى من هذه الطريقة ، فأنت بحاجة إلى التحكم فيها مرة أخرى من الرمز.
2. استخدام الربيع لإدارة المعاملاتالربيع يدعم نوعين من إدارة المعاملات
1. إدارة المعاملات البرمجية : يجب إدارة المعاملات من خلال البرمجة. هذه الطريقة مرنة بما فيه الكفاية ، ولكن يصعب الحفاظ عليها.
2. إدارة المعاملات التعريفي : يمكنك فصل إدارة المعاملات عن منطق الأعمال. يمكنك استخدام التعليقات التوضيحية فقط في التكوين المستند إلى XML لإدارة المعاملات.
نوصي بشدة باستخدام المعاملات التعريفية. إذا كنت تريد معرفة الأسباب ، فاقرأ ، وإلا انتقل مباشرة إلى قسم إدارة المعاملات التعريفي إذا كنت تريد تنفيذ هذا الخيار.الآن دعونا نلقي نظرة على كل نهج بالتفصيل.
2.1. إدارة المعاملات البرنامجية:يوفر إطار Spring أداتين لإدارة المعاملات البرنامجية.
أ. باستخدام
TransactionTemplate
(موصى به من قبل فريق Spring):
دعونا نلقي نظرة على كيفية تطبيق هذا النوع باستخدام رمز المثال أدناه (مأخوذ من وثائق Spring مع بعض التغييرات)
لاحظ أن مقتطفات الشفرة مأخوذة من مستندات Spring.ملف سياق XML:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="serviceImpl" class="com.service.ServiceImpl"> <constructor-arg ref="transactionManager"/> </bean>
فئة
Service
:
public class ServiceImpl implements Service { private final TransactionTemplate transactionTemplate;
إذا لم تكن هناك قيمة إرجاع ، فاستخدم فئة
TransactionCallbackWithoutResult
المريحة مع فئة مجهولة ، كما هو موضح أدناه:
transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2(); } });
- مثيلات فئة
TransactionTemplate
آمنة في سلسلة الرسائل ، لذلك لا يتم دعم جميع حالات الحوار. TransactionTemplate
الرغم من ذلك ، تدعم مثيلات TransactionTemplate
حالة التكوين ، لذلك إذا احتاج الفصل إلى استخدام TransactionTemplate مع إعدادات مختلفة (على سبيل المثال ، مستوى عزل مختلف) ، فأنت بحاجة إلى إنشاء مثيلين مختلفين من TransactionTemplate ، على الرغم من أن بعض الفئات قد تستخدم نفس مثيل TransactionTemplate.
ب. باستخدام تطبيق
PlatformTransactionManager
مباشرة:
دعنا ننظر إلى هذا الخيار في الكود مرة أخرى.
<!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> public class ServiceImpl implements Service { private PlatformTransactionManager transactionManager; public void setTransactionManager( PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } DefaultTransactionDefinition def = new DefaultTransactionDefinition();
الآن ، قبل الانتقال إلى الطريقة التالية لإدارة المعاملات ، دعونا نرى كيفية تحديد أي نوع من إدارة المعاملات للاختيار.
الاختيار بين
إدارة المعاملات البرنامجية والإعلانية :
- تعد إدارة المعاملات البرمجية خيارًا جيدًا فقط إذا كان لديك عدد صغير من عمليات المعاملات. (في معظم الحالات ، هذه ليست معاملات.)
- لا يمكن تعيين اسم المعاملة إلا صراحةً في "إدارة معاملات البرامج".
- يجب استخدام إدارة المعاملات البرنامجية عندما تريد التحكم بشكل صريح في إدارة المعاملات.
- من ناحية أخرى ، إذا كان التطبيق الخاص بك يحتوي على العديد من عمليات المعاملات ، فإن الأمر يستحق استخدام إدارة الإعلانات.
- الإدارة التصريحية لا تسمح لك بإدارة المعاملات في منطق الأعمال وليس من الصعب تكوينها.
2.2. المعاملات التعريفية (تُستخدم عادةً في جميع سيناريوهات أي تطبيق ويب تقريبًا)الخطوة 1 : تحديد مدير المعاملات في ملف سياق XML لتطبيق الربيع الخاص بك.
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/> <tx:annotation-driven transaction-manager="txManager"/>
الخطوة 2 : تمكين دعم التعليقات التوضيحية عن طريق إضافة إدخال في ملف xml السياق لتطبيق الربيع الخاص بك.
أو أضف
@EnableTransactionManagement
إلى ملف التكوين الخاص بك ، كما هو موضح أدناه:
@Configuration @EnableTransactionManagement public class AppConfig { ... }
يوصي Spring @Transactional
التعليقات التوضيحية لفئات محددة فقط (وطرق فئات محددة) مع تعليق توضيحي @Transactional
مقارنة @Transactional
التعليق التوضيحي.السبب في ذلك هو أنك وضعت التعليق التوضيحي على مستوى الواجهة ، وإذا كنت تستخدم فئات الوكيل (
proxy-target-class = «true»
) أو الجانب المتشابك (
mode = «aspectj»
) ، فلن يتم التعرف على معلمات المعاملة بواسطة البنية الأساسية للوكيل و plexuses ، على سبيل المثال لن يتم تطبيق سلوك المعاملات.
الخطوة 3 : إضافة تعليق توضيحي على
@Transactional
إلى الفئة (طريقة الفئة) أو الواجهة (طريقة الواجهة).
<tx:annotation-driven proxy-target-class="true">
التكوين الافتراضي:
proxy-target-class="false"
- يمكن وضع
@Transactional
قبل تعريف واجهة أو طريقة واجهة أو تعريف فئة أو طريقة فئة عامة. - إذا كنت تريد أن تحتوي بعض أساليب الفصل (التي تحمل علامة توضيحية
@Transactional
) على إعدادات سمة مختلفة ، مثل مستوى العزل أو مستوى الانتشار ، ضع التعليق التوضيحي في مستوى الطريقة لتجاوز إعدادات سمة مستوى الفئة. - في وضع الوكيل (الذي يتم تعيينه افتراضيًا) ، يمكن اعتراض المكالمات "الخارجية" فقط التي تمر عبر الوكيل. هذا يعني أن "المكالمة المستقلة" ، على سبيل المثال ، الأسلوب في الهدف الذي يستدعي بعض الطرق الأخرى للهدف ، لن يؤدي إلى معاملة فعلية في وقت التشغيل حتى إذا تم وضع علامة على الطريقة التي تم
@Transactional
مع @Transactional
.
الآن دعونا
@Transactional
الفرق بين
@Transactional
التعليقات التوضيحية
@Transactional
@Transactional (isolation=Isolation.READ_COMMITTED)
- الافتراضي هو
Isolation.DEFAULT
- في معظم الحالات ، سوف تستخدم الإعدادات الافتراضية حتى يكون لديك متطلبات خاصة.
- يخبر مدير المعاملة (
tx
) بأنه يجب استخدام مستوى العزل التالي في tx
الحالي. يجب تثبيته عند النقطة التي يبدأ منها tx
، لأنه لا يمكننا تغيير مستوى العزل بعد بدء tx.
DEFAULT : استخدم مستوى العزل الافتراضي في قاعدة البيانات الأساسية.
READ_COMMITTED (قراءة البيانات الثابتة): ثابت يشير إلى أن القراءة القذرة تم منعها ؛ قد تحدث القراءة غير المتكررة والقراءة الوهمية.
READ_UNCOMMITTED (قراءة البيانات غير الملتزم بها): يشير مستوى العزل هذا إلى أن المعاملة يمكنها قراءة البيانات التي لم يتم حذفها بعد بواسطة المعاملات الأخرى.
REPEATABLE_READ (قراءة التكرار): ثابت يشير إلى أن القراءة القذرة والقراءة غير القابلة للتكرار يتم منعها ؛ قد تظهر القراءة الوهمية.
SERIALIZABLE : دائم ، مشيرا إلى أن القراءة القذرة ، والقراءة غير القابلة للتكرار ، والقراءة الوهمية يتم منعها.
ماذا تعني هذه المصطلحات: القراءة "القذرة" ، القراءة الوهمية أو القراءة المتكررة؟
- قراءة القذرة : المعاملات A يكتب. وفي الوقت نفسه ، تقرأ المعاملة "B" نفس السجل حتى يتم الانتهاء من المعاملة A. في وقت لاحق ، تقرر المعاملة A التراجع ، والآن لدينا تغييرات على المعاملة B غير متوافقة. هذه قراءة قذرة. عملت المعاملة B على مستوى العزل READ_UNCOMMITTED ، بحيث يمكنها قراءة التغييرات التي تم إجراؤها بواسطة المعاملة A قبل إتمام المعاملة.
- قراءة غير قابلة للتكرار : المعاملة "A" يقرأ بعض السجلات. ثم تكتب المعاملة "B" هذا السجل وتلتزم به. في وقت لاحق ، تقوم المعاملة A بقراءة نفس السجل مرة أخرى وقد تتلقى قيمًا مختلفة ، نظرًا لأن المعاملة B أجرت تغييرات على هذا السجل وارتكبت تلك القيم. هذه قراءة غير متكررة.
- قراءة فانتوم : المعاملة "أ" يقرأ سلسلة من السجلات. وفي الوقت نفسه ، تدرج المعاملة "B" سجلاً جديدًا في نفس صف المعاملة A. لاحقًا ، تقوم المعاملة A بقراءة نفس النطاق مرة أخرى وتتلقى أيضًا السجل الذي أدخلته المعاملة B ، وهذه قراءة وهمية: المعاملة استرجعت سلسلة من السجلات عدة مرات من قاعدة البيانات وتلقى مجموعات نتائج مختلفة (تحتوي على سجلات الوهمية).
@Transactional(timeout=60)
الافتراضي هو المهلة الافتراضية لنظام المعاملات الأساسي.
يبلغ مدير tx عن طول الفترة الزمنية للانتظار حتى يصبح tx خاملاً قبل تقرير ما إذا كان يجب استرجاع المعاملات غير المستجيبة.
@Transactional(propagation=Propagation.REQUIRED)
إذا لم يتم تحديد ذلك ، فإن سلوك الانتشار الافتراضي
REQUIRED
.
الخيارات الأخرى هي
REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER
و
NESTED
.
مطلوبيشير إلى أن الطريقة المستهدفة لا يمكن أن تعمل بدون tx نشط. إذا كان tx يعمل بالفعل قبل استدعاء هذه الطريقة ، فسوف يستمر في نفس tx ، أو سيبدأ tx الجديد بعد وقت قصير من الاتصال بهذه الطريقة.
REQUIRES_NEW- يشير إلى أنه يجب تشغيل tx جديد في كل مرة يتم استدعاء الأسلوب الهدف. إذا كان tx قيد التشغيل بالفعل ، فسيتم إيقافه مؤقتًا قبل بدء تشغيل تطبيق جديد.
الانتداب- يشير إلى أن الأسلوب الهدف يتطلب tx نشط. إذا لم يستمر tx ، فلن يفشل ، مما يؤدي إلى استثناء.
يدعم- يشير إلى أنه يمكن تنفيذ الطريقة المستهدفة بشكل مستقل عن tx. إذا كان tx يعمل ، فسوف يشارك في نفس tx. إذا تم تنفيذه بدون tx ، فسيظل تنفيذه إذا لم تكن هناك أخطاء.
- تعتبر الطرق التي تسترجع البيانات هي أفضل المرشحين لهذا الخيار.
غير مدعوم- يشير إلى أن الطريقة المستهدفة لا تتطلب نشر سياق المعاملة.
- بشكل أساسي ، تعتبر تلك الطرق التي يتم تنفيذها في معاملة ، والتي تنفذ عمليات باستخدام ذاكرة الوصول العشوائي ، أفضل المرشحين لهذا الخيار.
ابدا- يشير إلى أن الطريقة المستهدفة ستلقي استثناءًا إذا تم تنفيذها في عملية معاملات.
- لا يتم استخدام هذا الخيار في معظم الحالات في المشروعات.
@Transactional (rollbackFor=Exception.class)
القيمة الافتراضية:
rollbackFor=RunTimeException.class
في فصل الربيع ، تقوم جميع فئات واجهة برمجة التطبيقات (API) برمي RuntimeException ، مما يعني أنه في حالة فشل أي طريقة ، تقوم الحاوية دائمًا بإعادة المعاملة الحالية.
المشكلة هي فقط مع الاستثناءات المحددة. وبالتالي ، يمكن استخدام هذه المعلمة لاستعادة معاملة بشكل
Checked Exception
حدوث
Checked Exception
.
@Transactional (noRollbackFor=IllegalStateException.class)
يشير إلى أن الاستعادة يجب ألا تحدث إذا كانت الطريقة المستهدفة تثير هذا الاستثناء.
الآن ، الخطوة الأخيرة ولكن الأكثر أهمية في إدارة المعاملات هي نشر تعليق توضيحي على
@Transactiona
. في معظم الحالات ، ينشأ الارتباك في المكان الذي يجب أن يوجد فيه التعليق التوضيحي: على مستوى الخدمة أو على مستوى DAO؟
@Transactional
: مستوى الخدمة أو DAO؟الخدمة هي أفضل مكان لوضع
@Transactional
، يجب أن يحتوي مستوى الخدمة على سلوك حالة الاستخدام على مستوى التفاصيل لتفاعل المستخدم ، والذي يذهب منطقيا في الصفقة.
هناك العديد من تطبيقات CRUD التي ليس لديها منطق عمل كبير له مستوى خدمة يقوم ببساطة بنقل البيانات بين وحدات التحكم وكائنات الوصول إلى البيانات ، وهو أمر غير مفيد. في هذه الحالات ، يمكننا وضع تعليق توضيحي للمعاملة على مستوى DAO.
لذلك ، في الممارسة العملية ، يمكنك وضعها في أي مكان ، الأمر متروك لك.
بالإضافة إلى ذلك ، إذا وضعت
@Transactional
في طبقة DAO وإذا كان سيتم إعادة استخدام طبقة DAO الخاصة بك بواسطة خدمات مختلفة ، فسيكون من الصعب وضعها في طبقة DAO ، حيث قد يكون للخدمات المختلفة متطلبات مختلفة.
إذا كان مستوى الخدمة الخاص بك يسترجع الكائنات باستخدام Hibernate ، ودعونا نقول إن لديك التهيئات البطيئة في تعريف كائن المجال ، فأنت بحاجة إلى فتح المعاملة على مستوى الخدمة ، وإلا فسوف تواجه LazyInitializationException التي ألقاها ORM.
فكر في مثال آخر حيث يمكن لمستوى الخدمة الاتصال بطريقتين مختلفتين من DAO لتنفيذ عمليات قاعدة البيانات. في حالة فشل عملية DAO الأولى الخاصة بك ، قد يتم نقل الاثنين الآخرين ، وسوف تنهي حالة عدم الاتساق في قاعدة البيانات. يمكن للتعليق على مستوى الخدمة أن ينقذك من مثل هذه الحالات.
آمل أن يكون هذا المقال ساعدك.
النهاية
من المثير للاهتمام دائمًا أن ترى تعليقاتك أو أسئلتك.