यदि आपने कभी Django डेटाबेस लेनदेन प्रबंधन के लिए अधिक समय समर्पित किया है, तो आप जानते हैं कि यह कितना भ्रमित कर सकता है। अतीत में, प्रलेखन काफी गहराई प्रदान करता था, लेकिन समझ केवल निर्माण और प्रयोग के माध्यम से आती थी।
commit_on_success
जैसे सज्जाकारों के साथ काम करने के लिए ढेर सारे डेकोरेटर थे। , commit_manually
, commit_unless_managed
, rollback_unless_managed
, enter_transaction_management
, leave_transaction_management
, कुछ के नाम बताएं। सौभाग्य से, Django 1.6 के साथ यह सब दरवाजे से बाहर हो जाता है। आपको वास्तव में अब केवल कुछ कार्यों के बारे में जानने की जरूरत है। और हम केवल एक सेकंड में उन तक पहुंच जाएंगे। सबसे पहले, हम इन विषयों पर ध्यान देंगे:
- लेन-देन प्रबंधन क्या है?
- Django 1.6 से पहले के लेनदेन प्रबंधन में क्या गलत है?
इसमें कूदने से पहले:
- Django 1.6 में लेनदेन प्रबंधन के बारे में क्या सही है?
और फिर एक विस्तृत उदाहरण के साथ काम करना:
- पट्टी उदाहरण
- लेन-देन
- अनुशंसित तरीका
- डेकोरेटर का उपयोग करना
- प्रति HTTP अनुरोध लेनदेन
- सेव पॉइंट्स
- नेस्टेड लेन-देन
लेन-देन क्या है?
SQL-92 के अनुसार, "एक SQL-लेन-देन (कभी-कभी केवल "लेन-देन" कहा जाता है) SQL-कथनों के निष्पादन का एक क्रम है जो पुनर्प्राप्ति के संबंध में परमाणु है"। दूसरे शब्दों में, सभी SQL कथनों को एक साथ निष्पादित और प्रतिबद्ध किया जाता है। इसी तरह, जब वापस रोल किया जाता है, तो सभी कथन एक साथ वापस लुढ़क जाते हैं।
उदाहरण के लिए:
# START
note = Note(title="my first note", text="Yay!")
note = Note(title="my second note", text="Whee!")
address1.save()
address2.save()
# COMMIT
तो एक लेनदेन डेटाबेस में काम की एक इकाई है। और कार्य की उस एकल इकाई को एक प्रारंभ लेनदेन और फिर एक प्रतिबद्ध या एक स्पष्ट रोलबैक द्वारा सीमांकित किया जाता है।
Django 1.6 से पहले के लेनदेन प्रबंधन में क्या गलत है?
इस प्रश्न का पूरी तरह उत्तर देने के लिए, हमें पता होना चाहिए कि डेटाबेस, क्लाइंट लाइब्रेरी और Django के भीतर लेन-देन कैसे किया जाता है।
डेटाबेस
डेटाबेस में प्रत्येक कथन को लेन-देन में चलाना होता है, भले ही लेन-देन में केवल एक कथन शामिल हो।
अधिकांश डेटाबेस में AUTOCOMMIT
होता है सेटिंग, जिसे आमतौर पर डिफ़ॉल्ट के रूप में सही पर सेट किया जाता है। यह AUTOCOMMIT
प्रत्येक कथन को एक लेन-देन में लपेटता है जो कथन के सफल होने पर तुरंत प्रतिबद्ध हो जाता है। बेशक आप START_TRANSACTION
. जैसी किसी चीज़ को मैन्युअल रूप से कॉल कर सकते हैं जो अस्थायी रूप से AUTOCOMMIT
. को निलंबित कर देगा जब तक आप COMMIT_TRANSACTION
. पर कॉल नहीं करते या ROLLBACK
।
हालाँकि, यहाँ मुख्य बात यह है कि AUTOCOMMIT
सेटिंग प्रत्येक कथन के बाद एक अंतर्निहित प्रतिबद्धता लागू करती है ।
ग्राहक पुस्तकालय
इसके बाद पायथन है क्लाइंट लाइब्रेरी जैसे sqlite3 और mysqldb, जो पायथन प्रोग्राम को डेटाबेस के साथ इंटरफेस करने की अनुमति देते हैं। इस तरह के पुस्तकालय डेटाबेस तक पहुँचने और क्वेरी करने के लिए मानकों के एक सेट का पालन करते हैं। वह मानक, डीबी एपीआई 2.0, पीईपी 249 में वर्णित है। हालांकि यह कुछ हद तक शुष्क पढ़ने के लिए बना सकता है, एक महत्वपूर्ण बात यह है कि पीईपी 249 बताता है कि डेटाबेस AUTOCOMMIT
बंद होना चाहिए डिफ़ॉल्ट रूप से।
यह स्पष्ट रूप से डेटाबेस के भीतर क्या हो रहा है, इसके विपरीत है:
- SQL कथनों को हमेशा लेन-देन में चलाना होता है, जिसे डेटाबेस आमतौर पर
AUTOCOMMIT
के माध्यम से आपके लिए खोलता है । - हालांकि, पीईपी 249 के अनुसार, ऐसा नहीं होना चाहिए।
- क्लाइंट पुस्तकालयों को डेटाबेस के भीतर क्या होता है, यह प्रतिबिंबित करना चाहिए, लेकिन चूंकि उन्हें
AUTOCOMMIT
चालू करने की अनुमति नहीं है डिफ़ॉल्ट रूप से, वे डेटाबेस की तरह ही आपके SQL कथनों को लेन-देन में लपेट देते हैं।
ठीक। थोड़ी देर मेरे साथ रहो।
Django
Django दर्ज करें। Django लेनदेन प्रबंधन के बारे में भी कुछ कहना है। Django 1.5 और इससे पहले के संस्करण में, Django मूल रूप से एक खुले लेनदेन के साथ चलता था और जब आप डेटाबेस में डेटा लिखते थे तो उस लेनदेन को स्वत:प्रतिबद्ध करते थे। तो हर बार जब आप model.save()
. जैसा कुछ कॉल करते हैं या model.update()
, Django ने उपयुक्त SQL कथन उत्पन्न किए और लेन-देन किया।
इसके अलावा Django 1.5 और इससे पहले के संस्करण में, यह अनुशंसा की गई थी कि आप TransactionMiddleware
. का उपयोग करें HTTP अनुरोधों के लिए लेनदेन को बाध्य करने के लिए। प्रत्येक अनुरोध को एक लेनदेन दिया गया था। यदि प्रतिक्रिया बिना किसी अपवाद के वापस आती है, तो Django लेन-देन करेगा लेकिन यदि आपके व्यू फ़ंक्शन ने एक त्रुटि फेंक दी, ROLLBACK
कहा जाएगा। यह वास्तव में, AUTOCOMMIT
को बंद कर दिया गया है . यदि आप मानक, डेटाबेस स्तर ऑटोकॉमिट शैली लेनदेन प्रबंधन चाहते हैं, तो आपको लेनदेन का प्रबंधन स्वयं करना होगा - आमतौर पर आपके व्यू फ़ंक्शन जैसे @transaction.commit_manually
पर लेनदेन डेकोरेटर का उपयोग करके। , या @transaction.commit_on_success
।
सांस लें। या दो।
इसका क्या अर्थ है?
हाँ, वहाँ बहुत कुछ चल रहा है, और यह पता चला है कि अधिकांश डेवलपर्स केवल मानक डेटाबेस स्तर ऑटोकॉमिट्स चाहते हैं - जिसका अर्थ है कि लेन-देन पर्दे के पीछे रहते हैं, अपना काम करते हैं, जब तक आपको उन्हें मैन्युअल रूप से समायोजित करने की आवश्यकता नहीं होती है।
Django 1.6 में लेनदेन प्रबंधन के बारे में क्या सही है?
अब, Django 1.6 में आपका स्वागत है। हमारे द्वारा अभी-अभी बात की गई हर चीज़ को भूलने की पूरी कोशिश करें और बस याद रखें कि Django 1.6 में, आप डेटाबेस AUTOCOMMIT
का उपयोग करते हैं। और जरूरत पड़ने पर लेनदेन को मैन्युअल रूप से प्रबंधित करें। अनिवार्य रूप से, हमारे पास एक बहुत ही सरल मॉडल है जो मूल रूप से वही करता है जो डेटाबेस को पहले स्थान पर करने के लिए डिज़ाइन किया गया था।
पर्याप्त सिद्धांत। आइए कोड करें।
स्ट्राइप उदाहरण
यहां हमारे पास यह उदाहरण दृश्य फ़ंक्शन है जो एक उपयोगकर्ता को पंजीकृत करने और क्रेडिट कार्ड प्रसंस्करण के लिए स्ट्राइप को कॉल करने का काम करता है।
def register(request):
user = None
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
customer = Customer.create("subscription",
email = form.cleaned_data['email'],
description = form.cleaned_data['name'],
card = form.cleaned_data['stripe_token'],
plan="gold",
)
cd = form.cleaned_data
try:
user = User.create(cd['name'], cd['email'], cd['password'],
cd['last_4_digits'])
if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()
except IntegrityError:
form.addError(cd['email'] + ' is already a member')
else:
request.session['user'] = user.pk
return HttpResponseRedirect('/')
else:
form = UserForm()
return render_to_response(
'register.html',
{
'form': form,
'months': range(1, 12),
'publishable': settings.STRIPE_PUBLISHABLE,
'soon': soon(),
'user': user,
'years': range(2011, 2036),
},
context_instance=RequestContext(request)
)
यह दृश्य सबसे पहले Customer.create
calls को कॉल करता है जो वास्तव में क्रेडिट कार्ड प्रोसेसिंग को संभालने के लिए स्ट्राइप को कॉल करता है। फिर हम एक नया उपयोगकर्ता बनाते हैं। अगर हमें स्ट्राइप से कोई प्रतिक्रिया मिलती है तो हम नए बनाए गए ग्राहक को stripe_id
के साथ अपडेट करते हैं . यदि हमें कोई ग्राहक वापस नहीं मिलता है (पट्टी बंद है) तो हम UnpaidUsers
में एक प्रविष्टि जोड़ देंगे नए बनाए गए ग्राहकों के ईमेल के साथ तालिका, ताकि हम उन्हें बाद में अपने क्रेडिट कार्ड विवरण के लिए फिर से प्रयास करने के लिए कह सकें।
विचार यह है कि भले ही स्ट्राइप डाउन हो, उपयोगकर्ता अभी भी पंजीकरण कर सकता है और हमारी साइट का उपयोग करना शुरू कर सकता है। हम उनसे क्रेडिट कार्ड की जानकारी के लिए बाद में फिर से पूछेंगे।
<ब्लॉकक्वॉट>मैं समझता हूं कि यह एक छोटा सा उदाहरण हो सकता है, और अगर मुझे ऐसा करना होता तो मैं इस तरह की कार्यक्षमता को लागू नहीं करता, लेकिन इसका उद्देश्य लेनदेन को प्रदर्शित करना है।
आगे। लेन-देन के बारे में सोचते हुए, और यह ध्यान में रखते हुए कि डिफ़ॉल्ट रूप से Django 1.6 हमें AUTOCOMMIT
देता है हमारे डेटाबेस के लिए व्यवहार, आइए डेटाबेस से संबंधित कोड को थोड़ा और देखें।
cd = form.cleaned_data
try:
user = User.create(
cd['name'], cd['email'],
cd['password'], cd['last_4_digits'])
if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()
except IntegrityError:
# ...
क्या आप कोई समस्या देख सकते हैं? वैसे क्या होता है यदि UnpaidUsers(email=cd['email']).save()
लाइन विफल?
आपके पास सिस्टम में पंजीकृत एक उपयोगकर्ता होगा, जिसे लगता है कि सिस्टम ने उनके क्रेडिट कार्ड को सत्यापित कर लिया है, लेकिन वास्तव में उन्होंने कार्ड को सत्यापित नहीं किया है।
हम केवल दो परिणामों में से एक चाहते हैं:
- उपयोगकर्ता (डेटाबेस में) बनाया गया है और उसके पास एक
stripe_id
है । - उपयोगकर्ता बनाया गया है (डेटाबेस में) और उसके पास
stripe_id
नहीं है औरUnpaidUsers
. में एक संबद्ध पंक्ति एक ही ईमेल पते वाली तालिका उत्पन्न होती है।
जिसका अर्थ है कि हम चाहते हैं कि दो अलग-अलग डेटाबेस स्टेटमेंट या तो प्रतिबद्ध हों या दोनों रोलबैक हों। मामूली लेन-देन के लिए एक आदर्श मामला।
सबसे पहले, आइए कुछ परीक्षण लिखें ताकि यह सत्यापित किया जा सके कि चीजें उस तरह से व्यवहार करती हैं जिस तरह से हम चाहते हैं।
@mock.patch('payments.models.UnpaidUsers.save', side_effect = IntegrityError)
def test_registering_user_when_strip_is_down_all_or_nothing(self, save_mock):
#create the request used to test the view
self.request.session = {}
self.request.method='POST'
self.request.POST = {'email' : '[email protected]',
'name' : 'pyRock',
'stripe_token' : '...',
'last_4_digits' : '4242',
'password' : 'bad_password',
'ver_password' : 'bad_password',
}
#mock out stripe and ask it to throw a connection error
with mock.patch('stripe.Customer.create', side_effect =
socket.error("can't connect to stripe")) as stripe_mock:
#run the test
resp = register(self.request)
#assert there is no record in the database without stripe id.
users = User.objects.filter(email="[email protected]")
self.assertEquals(len(users), 0)
#check the associated table also didn't get updated
unpaid = UnpaidUsers.objects.filter(email="[email protected]")
self.assertEquals(len(unpaid), 0)
परीक्षण के शीर्ष पर डेकोरेटर एक नकली है जो एक 'ईमानदारी त्रुटि' फेंक देगा जब हम UnpaidUsers
को सहेजने का प्रयास करेंगे। टेबल।
यह इस प्रश्न का उत्तर देने के लिए है, "क्या होता है यदि UnpaidUsers(email=cd['email']).save()
लाइन फेल?" कोड का अगला बिट सिर्फ एक नकली सत्र बनाता है, उचित जानकारी के साथ हमें अपने पंजीकरण फ़ंक्शन की आवश्यकता होती है। और फिर with mock.patch
सिस्टम को यह विश्वास करने के लिए मजबूर करता है कि स्ट्राइप नीचे है ... अंत में हम परीक्षण के लिए तैयार हैं।
resp = register(self.request)
उपरोक्त लाइन सिर्फ हमारे रजिस्टर व्यू फंक्शन को मॉक रिक्वेस्ट में पास करती है। फिर हम केवल यह सुनिश्चित करने के लिए जाँच करते हैं कि तालिकाएँ अद्यतन नहीं हैं:
#assert there is no record in the database without stripe_id.
users = User.objects.filter(email="[email protected]")
self.assertEquals(len(users), 0)
#check the associated table also didn't get updated
unpaid = UnpaidUsers.objects.filter(email="[email protected]")
self.assertEquals(len(unpaid), 0)
इसलिए यदि हम परीक्षण चलाते हैं तो यह विफल हो जाना चाहिए:
======================================================================
FAIL: test_registering_user_when_strip_is_down_all_or_nothing (tests.payments.testViews.RegisterPageTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/j1z0/.virtualenvs/django_1.6/lib/python2.7/site-packages/mock.py", line 1201, in patched
return func(*args, **keywargs)
File "/Users/j1z0/Code/RealPython/mvp_for_Adv_Python_Web_Book/tests/payments/testViews.py", line 266, in test_registering_user_when_strip_is_down_all_or_nothing
self.assertEquals(len(users), 0)
AssertionError: 1 != 0
----------------------------------------------------------------------
अच्छा। कहने में अजीब लगता है लेकिन हम यही चाहते थे। याद रखें:हम यहां टीडीडी का अभ्यास कर रहे हैं। त्रुटि संदेश हमें बताता है कि उपयोगकर्ता वास्तव में डेटाबेस में संग्रहीत किया जा रहा है - जो वास्तव में हम नहीं चाहते हैं क्योंकि उन्होंने भुगतान नहीं किया है!
बचाव के लिए लेन-देन…
लेन-देन
वास्तव में Django 1.6 में लेनदेन बनाने के कई तरीके हैं।
आइए कुछ के माध्यम से चलते हैं।
अनुशंसित तरीका
Django 1.6 प्रलेखन के अनुसार:
<ब्लॉकक्वॉट>"Django डेटाबेस लेनदेन को नियंत्रित करने के लिए एक एकल API प्रदान करता है। [...] परमाणुता डेटाबेस लेनदेन की परिभाषित संपत्ति है। परमाणु हमें कोड का एक ब्लॉक बनाने की अनुमति देता है जिसके भीतर डेटाबेस पर परमाणुता की गारंटी होती है। यदि कोड का ब्लॉक सफलतापूर्वक पूरा हो गया है, तो परिवर्तन डेटाबेस के लिए प्रतिबद्ध हैं। यदि कोई अपवाद होता है, तो परिवर्तन वापस ले लिए जाते हैं।"
परमाणु का उपयोग डेकोरेटर या संदर्भ_प्रबंधक दोनों के रूप में किया जा सकता है। इसलिए यदि हम इसे संदर्भ प्रबंधक के रूप में उपयोग करते हैं, तो हमारे रजिस्टर फ़ंक्शन में कोड इस तरह दिखेगा:
from django.db import transaction
try:
with transaction.atomic():
user = User.create(
cd['name'], cd['email'],
cd['password'], cd['last_4_digits'])
if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()
except IntegrityError:
form.addError(cd['email'] + ' is already a member')
लाइन पर ध्यान दें with transaction.atomic()
के साथ . उस ब्लॉक के अंदर सभी कोड लेनदेन के अंदर निष्पादित किए जाएंगे। इसलिए यदि हम अपने परीक्षण फिर से चलाते हैं, तो वे सभी पास होने चाहिए! याद रखें कि लेन-देन कार्य की एक इकाई है, इसलिए जब UnpaidUsers
कॉल विफल।
डेकोरेटर का उपयोग करना
हम परमाणु को डेकोरेटर के रूप में जोड़ने का भी प्रयास कर सकते हैं।
@transaction.atomic():
def register(request):
# ...snip....
try:
user = User.create(
cd['name'], cd['email'],
cd['password'], cd['last_4_digits'])
if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()
except IntegrityError:
form.addError(cd['email'] + ' is already a member')
अगर हम अपने परीक्षण फिर से चलाते हैं, तो वे उसी त्रुटि के साथ विफल हो जाएंगे जो हमने पहले की थी।
ऐसा क्यों है? लेन-देन सही ढंग से वापस क्यों नहीं हुआ? इसका कारण यह है कि transaction.atomic
किसी प्रकार के अपवाद की तलाश में है और ठीक है, हमने उस त्रुटि को पकड़ लिया (यानी IntegrityError
ब्लॉक को छोड़कर हमारे प्रयास में), इसलिए transaction.atomic
इसे कभी नहीं देखा और इस प्रकार मानक AUTOCOMMIT
कार्यक्षमता ले ली।
लेकिन निश्चित रूप से कोशिश को छोड़कर अपवाद को कॉल श्रृंखला को फेंक दिया जाएगा और संभवतः कहीं और उड़ा दिया जाएगा। तो हम वो भी नहीं कर सकते।
तो चाल परमाणु संदर्भ प्रबंधक को ब्लॉक को छोड़कर कोशिश के अंदर रखना है जो हमने अपने पहले समाधान में किया था। सही कोड को फिर से देख रहे हैं:
from django.db import transaction
try:
with transaction.atomic():
user = User.create(
cd['name'], cd['email'],
cd['password'], cd['last_4_digits'])
if customer:
user.stripe_id = customer.id
user.save()
else:
UnpaidUsers(email=cd['email']).save()
except IntegrityError:
form.addError(cd['email'] + ' is already a member')
जब UnpaidUsers
IntegrityError
को सक्रिय करता है with transaction.atomic()
संदर्भ प्रबंधक इसे पकड़ लेगा और रोलबैक करेगा। जब तक हमारा कोड अपवाद हैंडलर में निष्पादित होता है, (यानी form.addError
लाइन) रोलबैक किया जाएगा और यदि आवश्यक हो तो हम सुरक्षित रूप से डेटाबेस कॉल कर सकते हैं। with transaction.atomic()
. से पहले या बाद में किसी भी डेटाबेस कॉल पर भी ध्यान दें संदर्भ प्रबंधक के अंतिम परिणाम की परवाह किए बिना संदर्भ प्रबंधक अप्रभावित रहेगा।
प्रति HTTP अनुरोध लेनदेन
Django 1.6 (जैसे 1.5) भी आपको "लेन-देन प्रति अनुरोध" मोड में काम करने की अनुमति देता है। इस मोड में Django स्वचालित रूप से आपके व्यू फ़ंक्शन को लेनदेन में लपेट देगा। यदि फ़ंक्शन एक अपवाद फेंकता है, तो Django लेनदेन को वापस ले लेगा, अन्यथा यह लेनदेन करेगा।
इसे सेटअप करने के लिए आपको ATOMIC_REQUEST
. सेट करना होगा प्रत्येक डेटाबेस के लिए डेटाबेस कॉन्फ़िगरेशन में सही करने के लिए जिसे आप यह व्यवहार करना चाहते हैं। इसलिए अपनी “settings.py” में हम इस तरह से बदलाव करते हैं:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(SITE_ROOT, 'test.db'),
'ATOMIC_REQUEST': True,
}
}
व्यवहार में यह ठीक वैसा ही व्यवहार करता है जैसे कि आप डेकोरेटर को हमारे व्यू फंक्शन पर रखते हैं। तो यह यहाँ हमारे उद्देश्यों की पूर्ति नहीं करता है।
हालांकि यह ध्यान देने योग्य है कि ATOMIC_REQUESTS
. दोनों के साथ और @transaction.atomic
डेकोरेटर दृश्य से फेंकने के बाद भी उन त्रुटियों को पकड़ना/संभालना संभव है। उन त्रुटियों को पकड़ने के लिए आपको कुछ कस्टम मिडलवेयर लागू करने होंगे, या आप urls.hadler500 को ओवरराइड कर सकते हैं या 500.html टेम्पलेट बनाकर।
सेव पॉइंट
भले ही लेन-देन परमाणु हैं, फिर भी उन्हें बचत बिंदुओं में तोड़ा जा सकता है। सेवप्वाइंट को आंशिक लेन-देन के रूप में सोचें।
इसलिए यदि आपके पास एक लेन-देन है जो पूरा करने के लिए चार SQL कथन लेता है, तो आप दूसरे कथन के बाद एक सेवपॉइंट बना सकते हैं। एक बार वह सेवपॉइंट बन जाने के बाद, भले ही तीसरा या चौथा स्टेटमेंट विफल हो जाए, आप तीसरे और चौथे स्टेटमेंट से छुटकारा पाकर आंशिक रोलबैक कर सकते हैं, लेकिन पहले दो को रखते हुए।
तो यह मूल रूप से एक लेन-देन को छोटे हल्के लेन-देन में विभाजित करने जैसा है जिससे आप आंशिक रोलबैक या कमिट कर सकते हैं।
<ब्लॉकक्वॉट>
लेकिन ध्यान रखें कि मुख्य लेन-देन कहां से वापस लाया जाए (शायद IntegrityError
के कारण) जो उठाया गया था और पकड़ा नहीं गया था, तो सभी बचत बिंदु भी वापस लुढ़क जाएंगे)।
आइए एक उदाहरण देखें कि सेवप्वाइंट कैसे काम करता है।
@transaction.atomic()
def save_points(self,save=True):
user = User.create('jj','inception','jj','1234')
sp1 = transaction.savepoint()
user.name = 'starting down the rabbit hole'
user.stripe_id = 4
user.save()
if save:
transaction.savepoint_commit(sp1)
else:
transaction.savepoint_rollback(sp1)
यहां पूरा कार्य लेनदेन में है। एक नया उपयोगकर्ता बनाने के बाद हम एक सेवपॉइंट बनाते हैं और सेवपॉइंट का संदर्भ प्राप्त करते हैं। अगले तीन कथन-
user.name = 'starting down the rabbit hole'
user.stripe_id = 4
user.save()
- मौजूदा सेवपॉइंट का हिस्सा नहीं हैं, इसलिए वे अगले savepoint_rollback
का हिस्सा बनने की संभावना रखते हैं , या savepoint_commit
. savepoint_rollback
. के मामले में , लाइन user = User.create('jj','inception','jj','1234')
बाकी अपडेट नहीं होने के बावजूद भी डेटाबेस के लिए प्रतिबद्ध रहेगा।
दूसरे शब्दों में कहें तो ये निम्नलिखित दो परीक्षण बताते हैं कि सेवपॉइंट कैसे काम करते हैं:
def test_savepoint_rollbacks(self):
self.save_points(False)
#verify that everything was stored
users = User.objects.filter(email="inception")
self.assertEquals(len(users), 1)
#note the values here are from the original create call
self.assertEquals(users[0].stripe_id, '')
self.assertEquals(users[0].name, 'jj')
def test_savepoint_commit(self):
self.save_points(True)
#verify that everything was stored
users = User.objects.filter(email="inception")
self.assertEquals(len(users), 1)
#note the values here are from the update calls
self.assertEquals(users[0].stripe_id, '4')
self.assertEquals(users[0].name, 'starting down the rabbit hole')
इसके अलावा हम एक सेवपॉइंट को कमिट या रोलबैक करने के बाद भी उसी लेनदेन में काम करना जारी रख सकते हैं। और वह कार्य पिछले बचत बिंदु के परिणाम से अप्रभावित रहेगा।
उदाहरण के लिए यदि हम अपने save_points
. को अपडेट करते हैं इस प्रकार कार्य करें:
@transaction.atomic()
def save_points(self,save=True):
user = User.create('jj','inception','jj','1234')
sp1 = transaction.savepoint()
user.name = 'starting down the rabbit hole'
user.save()
user.stripe_id = 4
user.save()
if save:
transaction.savepoint_commit(sp1)
else:
transaction.savepoint_rollback(sp1)
user.create('limbo','illbehere@forever','mind blown',
'1111')
भले ही savepoint_commit
या savepoint_rollback
जिसे 'लिम्बो' कहा जाता था, उपयोगकर्ता अभी भी सफलतापूर्वक बनाया जाएगा। जब तक किसी अन्य कारण से पूरे लेन-देन को वापस नहीं लिया जाता है।
नेस्टेड लेनदेन
savepoint()
. के साथ मैन्युअल रूप से सेवपॉइंट निर्दिष्ट करने के अलावा, , savepoint_commit
, और savepoint_rollback
, नेस्टेड ट्रांजैक्शन बनाने से हमारे लिए स्वचालित रूप से एक सेवपॉइंट बन जाएगा, और अगर हमें कोई त्रुटि मिलती है तो उसे वापस रोल करें।
अपने उदाहरण को थोड़ा और आगे बढ़ाते हुए हमें यह मिलता है:
@transaction.atomic()
def save_points(self,save=True):
user = User.create('jj','inception','jj','1234')
sp1 = transaction.savepoint()
user.name = 'starting down the rabbit hole'
user.save()
user.stripe_id = 4
user.save()
if save:
transaction.savepoint_commit(sp1)
else:
transaction.savepoint_rollback(sp1)
try:
with transaction.atomic():
user.create('limbo','illbehere@forever','mind blown',
'1111')
if not save: raise DatabaseError
except DatabaseError:
pass
यहां हम देख सकते हैं कि अपने सेव पॉइंट्स से निपटने के बाद, हम transaction.atomic
का उपयोग कर रहे हैं। संदर्भ प्रबंधक 'लिम्बो' उपयोगकर्ता के हमारे निर्माण को घेरने के लिए। जब उस संदर्भ प्रबंधक को बुलाया जाता है, तो यह वास्तव में एक बचत बिंदु बना रहा है (क्योंकि हम पहले से ही एक लेनदेन में हैं) और संदर्भ प्रबंधक से बाहर निकलने पर उस बचत बिंदु को प्रतिबद्ध या वापस ले लिया जाएगा।
इस प्रकार निम्नलिखित दो परीक्षण उनके व्यवहार का वर्णन करते हैं:
def test_savepoint_rollbacks(self):
self.save_points(False)
#verify that everything was stored
users = User.objects.filter(email="inception")
self.assertEquals(len(users), 1)
#savepoint was rolled back so we should have original values
self.assertEquals(users[0].stripe_id, '')
self.assertEquals(users[0].name, 'jj')
#this save point was rolled back because of DatabaseError
limbo = User.objects.filter(email="illbehere@forever")
self.assertEquals(len(limbo),0)
def test_savepoint_commit(self):
self.save_points(True)
#verify that everything was stored
users = User.objects.filter(email="inception")
self.assertEquals(len(users), 1)
#savepoint was committed
self.assertEquals(users[0].stripe_id, '4')
self.assertEquals(users[0].name, 'starting down the rabbit hole')
#save point was committed by exiting the context_manager without an exception
limbo = User.objects.filter(email="illbehere@forever")
self.assertEquals(len(limbo),1)
तो वास्तव में आप या तो atomic
. का उपयोग कर सकते हैं या savepoint
लेन-देन के अंदर सेवपॉइंट बनाने के लिए। atomic
. के साथ आपको कमिट/रोलबैक के बारे में स्पष्ट रूप से चिंता करने की ज़रूरत नहीं है, जबकि savepoint
. के साथ ऐसा होने पर आपका पूरा नियंत्रण होता है।
निष्कर्ष
यदि आपके पास Django लेनदेन के पुराने संस्करणों के साथ कोई पिछला अनुभव था, तो आप देख सकते हैं कि लेनदेन मॉडल कितना आसान है। इसके अलावा AUTOCOMMIT
. है डिफ़ॉल्ट रूप से "समझदार" डिफ़ॉल्ट का एक बड़ा उदाहरण है कि Django और पायथन दोनों खुद को वितरित करने पर गर्व करते हैं। कई प्रणालियों के लिए आपको सीधे लेन-देन करने की आवश्यकता नहीं होगी, बस AUTOCOMMIT
. दें अपना काम करो। लेकिन अगर आप ऐसा करते हैं, तो उम्मीद है कि इस पोस्ट ने आपको एक समर्थक की तरह Django में लेनदेन को प्रबंधित करने के लिए आवश्यक जानकारी दी होगी।