توضح هذه المقالة إدارة المعاملات في الربيع مع الأمثلة. شاركها مع الجميع لتكون مرجعا لك. التحليل المحدد هو كما يلي:
مقدمة الأعمال:
تعد إدارة المعاملات تقنية أساسية في تطوير تطبيقات المؤسسات لضمان سلامة البيانات واتساقها
المعاملة عبارة عن سلسلة من الإجراءات التي يتم التعامل معها كوحدة عمل واحدة. إما أن تنجح كل هذه الإجراءات، أو لا ينجح أي منها
أربع سمات رئيسية للمعاملات (ACID)
① الذرية: تتكون العملية الذرية في غرفة المعاملات من سلسلة من الإجراءات. تضمن ذرية المعاملات أن تكون الإجراءات إما مكتملة بالكامل أو ليس لها أي تأثير على الإطلاق ② الاتساق: بمجرد اكتمال جميع إجراءات المعاملة، يتم الالتزام بالمعاملة. البيانات والموارد في حالة متسقة تلبي قواعد العمل ③ العزل: قد يكون هناك العديد من المعاملات التي تعالج نفس البيانات في نفس الوقت، لذلك يجب عزل كل شيء عن المعاملات الأخرى لمنع تلف البيانات ④ المتانة: بمجرد اكتمال المعاملة فلا ينبغي أن تتأثر نتائجه مهما حدثت أخطاء في النظام. عادة، تتم كتابة نتائج المعاملة إلى التخزين المستمر
إدارة المعاملات في الربيع
باعتباره إطارًا لتطبيقات المؤسسات، يحدد Spring طبقة تجريد أعلى واجهات برمجة التطبيقات المختلفة لإدارة المعاملات. لا يحتاج مطورو التطبيقات إلى فهم واجهة برمجة التطبيقات الأساسية لإدارة المعاملات لاستخدام آلية إدارة المعاملات في Spring.
يدعم Spring كلاً من إدارة المعاملات البرمجية وإدارة المعاملات التصريحية.
إدارة المعاملات البرمجية: قم بتضمين رمز إدارة المعاملات في أساليب العمل للتحكم في تقديم المعاملات والتراجع عنها في المعاملات البرمجية، يجب تضمين رمز إدارة المعاملات الإضافي في كل عملية تجارية.
إدارة المعاملات التصريحية: أفضل في الاستخدام من إدارة المعاملات الآلية في معظم الحالات. فهو يفصل رمز إدارة المعاملات عن أساليب العمل وينفذ إدارة المعاملات بطريقة تعريفية. يمكن تقسيم إدارة المعاملات، باعتبارها اهتمامًا شاملاً، إلى وحدات من خلال أساليب AOP. يدعم Spring إدارة المعاملات التصريحية من خلال إطار عمل Spring AOP.
خصائص انتشار معاملات الربيع:
عندما يتم استدعاء أسلوب معاملة بواسطة أسلوب معاملة آخر، يجب عليك تحديد كيفية نشر المعاملة. على سبيل المثال: قد تستمر الطريقة في العمل في المعاملة الحالية، أو قد تبدأ معاملة جديدة وتعمل في المعاملة الخاصة بها.
يمكن تحديد سلوك نشر المعاملة بواسطة سمة النشر. يحدد الربيع 7 سلوكيات للتواصل:
سلوك التواصل | معنى |
PROPAGATION_MANDATORY | يشير إلى أنه يجب تشغيل الطريقة داخل المعاملة، إذا لم تكن المعاملة الحالية موجودة، فسيتم طرح استثناء. |
PROPAGATION_NESTED | يشير إلى أنه في حالة وجود معاملة حاليًا، فسيتم تشغيل هذه الطريقة في معاملة متداخلة. يمكن تنفيذ المعاملات المتداخلة أو التراجع عنها بشكل مستقل عن المعاملة الحالية. إذا كانت المعاملة الحالية غير موجودة، فسيكون السلوك هو نفسه PROPAGATION_REQUIRED. لاحظ أن دعم كل شركة مصنعة لسلوك النشر هذا يختلف. يمكنك الرجوع إلى وثائق مديري الموارد لديك للتأكد مما إذا كانوا يدعمون المعاملات المتداخلة. |
PROPAGATION_NEVER | يشير إلى أنه لا ينبغي تشغيل الطريقة الحالية في سياق المعاملة. إذا كانت هناك معاملة قيد التشغيل حاليًا، فسيتم طرح استثناء |
PROPAGATION_NOT_SUPPORTED | يشير إلى أنه لا ينبغي تشغيل هذه الطريقة ضمن المعاملة. في حالة وجود معاملة حالية، سيتم تعليقها أثناء تشغيل هذه الطريقة. إذا كنت تستخدم JTATransactionManager، فستحتاج إلى الوصول إلى TransactionManager |
PROPAGATION_REQUIRED | يشير إلى أنه يجب تشغيل الطريقة الحالية ضمن المعاملة. إذا كانت المعاملة الحالية موجودة، فسيتم تشغيل الطريقة في تلك المعاملة. وإلا سيتم بدء معاملة جديدة |
PROPAGATION_REQUIRED_NEW | يشير إلى أن الطريقة الحالية يجب أن تعمل في معاملتها الخاصة. سيتم بدء معاملة جديدة. في حالة وجود معاملة حالية، سيتم تعليقها أثناء تنفيذ هذه الطريقة. إذا كنت تستخدم JTATransactionManager، فستحتاج إلى الوصول إلى TransactionManager |
PROPAGATION_SUPPORTS | يشير إلى أن الطريقة الحالية لا تتطلب سياق معاملة، ولكن إذا كانت هناك معاملة حالية، فسيتم تشغيل الطريقة في هذه المعاملة |
من بينها PROPAGATION_REQUIRED هي سمة النشر الافتراضية
المشاكل الناجمة عن المعاملات المتزامنة
يمكن أن تنشأ العديد من المشكلات غير المتوقعة عند تنفيذ معاملات متعددة في نفس التطبيق أو في تطبيقات مختلفة بشكل متزامن على نفس مجموعة البيانات.
يمكن تقسيم المشاكل الناجمة عن المعاملات المتزامنة إلى الفئات الثلاث التالية:
① القراءة القذرة: تحدث القراءة القذرة عندما تقرأ إحدى المعاملات البيانات التي تمت إعادة كتابتها بواسطة معاملة أخرى ولكن لم يتم الالتزام بها بعد. إذا تم التراجع عن إعادة الكتابة لاحقًا، فستكون البيانات التي تم الحصول عليها من خلال المعاملة الأولى غير صالحة.
② قراءة غير قابلة للتكرار: تحدث قراءة غير قابلة للتكرار عندما تنفذ المعاملة نفس الاستعلام مرتين أو أكثر، ولكنها تحصل على بيانات مختلفة في كل مرة. يحدث هذا عادةً بسبب قيام معاملة متزامنة أخرى بتحديث البيانات بين الاستعلامات
③ القراءة الوهمية: القراءة الوهمية تشبه القراءة غير المتكررة. يحدث ذلك عندما تقرأ معاملة واحدة (T1) بضعة صفوف من البيانات، ثم تقوم معاملة متزامنة أخرى (T2) بإدراج بعض البيانات. في الاستعلامات اللاحقة، ستجد المعاملة الأولى (T1) أن هناك بعض السجلات الإضافية التي لم تكن موجودة في الأصل.
مثال التعليمات البرمجية
الأول هو جدول قاعدة البيانات:
بما في ذلك الكتاب (isbn، book_name، السعر)، الحساب (اسم المستخدم، الرصيد)، book_stock (isbn، المخزون)
ثم الطبقات المستخدمة:
BookShopDao
انسخ رمز الكود كما يلي:
الحزمة com.yl.spring.tx؛
الواجهة العامة BookShopDao {
// احصل على سعر وحدة الكتاب بناءً على رقم الكتاب
public int findBookPriceByIsbn(String isbn);
// قم بتحديث مخزون الكتاب بحيث يكون المخزون المطابق لرقم الكتاب هو -1
تحديث الفراغ العام BookStock(String isbn);
// تحديث رصيد حساب المستخدم: اجعل رصيد اسم المستخدم هو السعر
تحديث الفراغ العامUserAccount(اسم مستخدم السلسلة، سعر int);
}
BookShopDaoImpl
انسخ رمز الكود كما يلي:
الحزمة com.yl.spring.tx؛
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
الطبقة العامة BookShopDaoImpl تنفذ BookShopDao {
@Autowired
JdbcTemplate الخاص JdbcTemplate؛
@تجاوز
الباحث العام findBookPriceByIsbn(سلسلة isbn) {
String sql = "اختر السعر من الكتاب WHERE isbn = ؟";
return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
}
@تجاوز
تحديث الفراغ العام BookStock(سلسلة isbn) {
//تحقق مما إذا كان مخزون الكتب كافيًا، وإذا لم يكن كذلك، فقم بطرح استثناء
String sql2 = "اختر المخزون من book_stock WHERE isbn = ؟";
int Stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
إذا (المخزون == 0) {
رمي BookStockException الجديد("المخزون غير كاف!");
}
String sql = "UPDATE book_stock SET Stock = Stock - 1 WHERE isbn = ؟";
JdbcTemplate.update(sql, isbn);
}
@تجاوز
تحديث الفراغ العامUserAccount(اسم مستخدم السلسلة، سعر int) {
//تحقق مما إذا كان الرصيد غير كافٍ، وإذا لم يكن كذلك، فقم بطرح استثناء
String sql2 = "اختر الرصيد من الحساب حيث اسم المستخدم =؟";
int Balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
إذا (الرصيد <السعر) {
طرح UserAccountException جديد("الرصيد غير كاف!");
}
String sql = "تحديث رصيد مجموعة الحساب = الرصيد - ؟ أين اسم المستخدم =؟";
JdbcTemplate.update(sql,price,username);
}
}
خدمة متجر الكتب
انسخ الكود كما يلي: package com.yl.spring.tx;
الواجهة العامة BookShopService {
شراء باطل عام (اسم مستخدم السلسلة، سلسلة isbn)؛
}
BookShopServiceImpl
انسخ رمز الكود كما يلي:
الحزمة com.yl.spring.tx؛
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
الطبقة العامة BookShopServiceImpl تنفذ BookShopService {
@Autowired
BookShopDao الخاص bookShopDao؛
/**
* 1. أضف التعليقات التوضيحية للمعاملة
* استخدم النشر لتحديد سلوك النشر للمعاملة، أي كيفية استخدام المعاملة عندما يتم استدعاء طريقة المعاملة الحالية بواسطة طريقة معاملة أخرى.
* القيمة الافتراضية مطلوبة، مما يعني استخدام المعاملة التي تستدعي الطريقة
* REQUIRES_NEW: استخدم المعاملة الخاصة بك، وسيتم تعليق معاملة طريقة المعاملة المطلوبة.
*
* 2. استخدم العزل لتحديد مستوى عزل المعاملة. القيمة الأكثر استخدامًا هي READ_COMMITTED.
* 3. افتراضيًا، تقوم معاملة Spring التصريحية باستعادة جميع استثناءات وقت التشغيل، والتي يمكن أيضًا تعيينها من خلال الخصائص المقابلة. عادةً ما تكون القيمة الافتراضية كافية.
* 4. استخدم القراءة فقط لتحديد ما إذا كانت المعاملة للقراءة فقط. يشير إلى أن هذه المعاملة تقرأ البيانات فقط ولكنها لا تقوم بتحديث البيانات، مما يمكن أن يساعد محرك قاعدة البيانات على تحسين المعاملة. إذا كانت هذه الطريقة حقًا تقرأ قاعدة البيانات فقط، فيجب تعيين readOnly=true.
* 5. استخدم المهلة لتحديد الوقت الذي يمكن أن تستغرقه المعاملة قبل فرض التراجع.
*/
@Transactional(propagation=Propagation.REQUIRES_NEW،
العزلة=العزلة.READ_COMMITTED،
noRollbackFor={UserAccountException.class},
قراءة فقط = صحيح، المهلة = 3)
@تجاوز
شراء باطل عام (اسم مستخدم السلسلة، سلسلة isbn) {
//1. احصل على سعر الوحدة للكتاب
int السعر = bookShopDao.findBookPriceByIsbn(isbn);
//2. تحديث مخزون الكتب
bookShopDao.updateBookStock(isbn);
//3. تحديث رصيد المستخدم
bookShopDao.updateUserAccount(اسم المستخدم, السعر);;
}
}
BookStockException
انسخ رمز الكود كما يلي:
الحزمة com.yl.spring.tx؛
الطبقة العامة BookStockException تمتد RuntimeException {
/**
*
*/
serialVersionUID النهائي الطويل الخاص الثابت = 1L؛
عام BookStockException() {
ممتاز()؛
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
BookStockException العام (سلسلة arg0، Throwable arg1، منطقية arg2،
الوسيطة المنطقية3) {
super(arg0, arg1, arg2, arg3);
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
عام BookStockException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
عام BookStockException(سلسلة arg0) {
سوبر(arg0);
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
عام BookStockException(Throwable arg0) {
سوبر(arg0);
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
}
UserAccountException
انسخ رمز الكود كما يلي:
الحزمة com.yl.spring.tx؛
الطبقة العامة UserAccountException تمتد RuntimeException {
/**
*
*/
serialVersionUID النهائي الطويل الخاص الثابت = 1L؛
عام UserAccountException() {
ممتاز()؛
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
UserAccountException العام (سلسلة arg0، Throwable arg1، منطقية arg2،
الوسيطة المنطقية3) {
super(arg0, arg1, arg2, arg3);
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
عام UserAccountException(String arg0, Throwable arg1) {
super(arg0, arg1);
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
عام UserAccountException(سلسلة arg0) {
سوبر(arg0);
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
عام UserAccountException(Throwable arg0) {
سوبر(arg0);
// TODO كعب المنشئ الذي تم إنشاؤه تلقائيًا
}
}
أمين الصندوق
انسخ رمز الكود كما يلي:
الحزمة com.yl.spring.tx؛
import java.util.List;
واجهة عامة أمين الصندوق {
الخروج الفراغي العام (اسم مستخدم السلسلة، List<String>isbns)؛
}
CashierImpl. يقوم كل من CashierImpl.checkout وbookShopService.purchase باختبار سلوك نشر المعاملة بشكل مشترك
انسخ رمز الكود كما يلي:
الحزمة com.yl.spring.tx؛
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("أمين الصندوق")
الطبقة العامة CashierImpl تنفذ Cashier {
@Autowired
خدمة BookShopService الخاصة bookShopService؛
@المعاملات
@تجاوز
الخروج باطل عام (اسم مستخدم السلسلة، قائمة <سلسلة> إيسبنس) {
ل(سلسلة isbn : isbns) {
bookShopService.purchase(username, isbn);
}
}
}
فئة الاختبار:
انسخ رمز الكود كما يلي:
الحزمة com.yl.spring.tx؛
استيراد java.util.Arrays؛
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
الطبقة العامة SpringTransitionTest {
Private ApplicationContext ctx = null;
Private BookShopDao bookShopDao = null;
Private BookShopService bookShopService = null;
أمين الصندوق الخاص Cashier = null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
bookShopDao = ctx.getBean(BookShopDao.class);
bookShopService = ctx.getBean(BookShopService.class);
أمين الصندوق = ctx.getBean(Cashier.class);
}
@امتحان
اختبار الفراغ العامBookShopDaoFindPriceByIsbn() {
System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
}
@امتحان
اختبار الفراغ العامBookShopDaoUpdateBookStock(){
bookShopDao.updateBookStock("1001");
}
@امتحان
اختبار الفراغ العامBookShopDaoUpdateUserAccount(){
bookShopDao.updateUserAccount("AA", 100);
}
@امتحان
اختبار الفراغ العامBookShopService () {
bookShopService.purchase("AA", "1001");
}
@امتحان
اختبار الفراغ العامTransactionPropagation(){
Cashier.checkout("AA"، Arrays.asList("1001"، "1002"));
}
}
آمل أن تكون هذه المقالة مفيدة لتصميم برمجة الربيع للجميع.