what is it?
a unit of work that is either committed (applied to the database) or rolled back (undone from the database)
default django behaviour
autocommit: each query starts a transaction and either commits or rolls back the transaction as well. If you have a view with three queries, then each will run one-by-one. If one fails, the other two will still be committed
def test_view(request):
user = User.objects.create_user('john', '[email protected]', 'johnpassword')
logger.info(f'create user {user.pk}')
raise Exception('test')in the above, the user is still created, even though an exception was thrown
changing behaviour to atomic
atomic allows us to create a block of code within which the atomicity on the database is guaranteed. If the block of code is successfully completed, the changes are committed to the database. If there is an exception, the changes are rolled back.
from django.db import transaction
def transaction_test(request):
with transaction.atomic():
user = User.objects.create_user('john1', '[email protected]', 'johnpassword')
logger.info(f'create user {user.pk}')
raise Exception('force transaction to rollback')the user create operation will roll back when the exception is raised, so the user will not be created in the end
decorators
@transaction.atomic
def transaction_test2(request):
user = User.objects.create_user('john1', '[email protected]', 'johnpassword')
logger.info(f'create user {user.pk}')
raise Exception('force transaction to rollback')to ensure a method runs in autocommit mode:
@transaction.non_atomic_requests
configure globally
in settings.py:
ATOMIC_REQUESTS=True
# or
DATABASES["default"]["ATOMIC_REQUESTS"] = Truewaiting for an atomic commit to complete
on_commit() allows you to register callbacks that will be executed after the open transaction is successfully committed
from django.db import transaction
from functools import partial
@transaction.atomic
def transaction_celery(request):
username = random_username()
user = User.objects.create_user(username, '[email protected]', 'johnpassword')
logger.info(f'create user {user.pk}')
# the task does not get called until after the transaction is committed
transaction.on_commit(partial(task_send_welcome_email.delay, user.pk))
time.sleep(1)
return HttpResponse('test')partial is used (instead of lambda) to bind the user.pk to the task_send_welcome_email.delay function, this can help avoid the late binding bug