色々なDjangoのトランザクション設定方法のご紹介

皆さん、お疲れ様です。
プラットフォーム事業部のKです。
以前、同じ事業部のHさんが「Djangoのトランザクションの基本」について説明してくださいました。
ご興味のある方は是非ご一読ください。
https://gw-python.com/archives/557
今回はDjangoでトランザクションを設定するさまざまな方法について説明していきたいと思います。
目次
ATOMIC_REQUESTS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'ATOMIC_REQUESTS': True,
}
}
一般的にDjangoでトランザクション管理を行うには、setting.pyのデータベース設定において「ATOMIC_REQUESTS」をTrueに設定します。
この設定により、ビュー関数を呼び出す前にDjango はトランザクションを開始します。問題なくリクエストに対しての応答が生成された場合、Django はトランザクションをコミットします。もしビューが例外を生成した場合、Django は自動でトランザクションをロールバックします。
「ATOMIC_REQUESTS」設定を有効にすることで、開発者は別途ロールバックのための処理を作成する必要がなくなります。しかし、開発する機能によっては以下のような場面も多々あります。
・Worker等、処理の主体がDjangoではないため、「ATOMIC_REQUESTS」設定が効かない
・ロールバック時にデータを処理前の値に戻すのではなく、新しい値に変更したい
上記のような場面で、開発者がトランザクションをコントロールする方法について見ていきましょう。
明示的にトランザクションをコントロール
@transaction.atomic:個々のVIEWにトランザクションを設定
まずDjangoではデコレーターである「@transaction.atomic」を付けることで、特定のView関数にトランザクションを指定することができます。「ATOMIC_REQUESTS」との違いとして「ATOMIC_REQUESTS」の設定は全VIEWに適用されますが、「@transaction.atomic」は個別のVIEWに設定したい場合に適用されます。
from django.db import transaction
@transaction.atomic # VIEWトランザクションを指定
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()
「ATOMIC_REQUESTS」や「@transaction.atomic」はVIEW関数に適用するトランザクション制御の方法であることをご紹介しました。
しかし機能によってはある特定の範囲をトランザクションとして設定したい場面もあるでしょう。
with transaction.atomic():特定の範囲にトランザクションを設定
特定の範囲をトランザクションとして設定したい時は、コンテキストマネージャー「with transaction.atomic()」を使いましょう。ブロック内で記載された処理はトランザクションとして設定されます。
from django.db import transaction
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
with transaction.atomic(): #ブロック内の処理はトランザクション
# This code executes inside a transaction.
do_more_stuff()
全体としての処理が例外で終了しない場合でも、以下のようにトランザクションが保証されると同時にそれ以降の処理もコントロールすることが可能です。
from django.db import IntegrityError, transaction
def viewfunc(request):
create_parent()
try:
with transaction.atomic():
generate_relationships()
except IntegrityError:
handle_exception()
add_children()
set_rollback:特定のタイミングにトランザクションを設定
状況によっては、特定のタイミングでロールバックが走るよう実装したい場面があるかもしれません。Djangoの組み込み関数であるset_rollbackを使うことで、必ずしも例外で処理を抜けなくても、ある条件を満すことで、ロールバックを実行することができます。
注意点としては「set_rollback」は「@transaction.atomic()」またはコンテキストマネージャ内(with transaction.atomic())で使用されます。 また「set_rollback」にTrueを渡すと、ロールバックした結果(変更を破棄)をコミットしますが、Falseを渡すとロールバックを無効となり、変更内容がコミットされます。必要に応じて設定を切り替えましょう。
from django.db import transaction
@transaction.atomic
def viewfunc(request):
a.save()
b.save()
if some_condition is True: # 特定の条件を満すと、ロールバック
transaction.set_rollback(True)
return
'ATOMIC_REQUESTS': Trueを無効化する方法
最後に、個々のVIEWで「ATOMIC_REQUESTS」の設定を解除する方法をご紹介します。VIEW関数にデコレーター「@transaction.non_atomic_requests」を付けることで「ATOMIC_REQUESTS」が設定したトランザクションを解除することができます。 ただし、これはDjangoが提供する便利なトランザクション設定を自ら手放すことになるため、注意して使うべきです。
「@transaction.non_atomic_requests」を付けてまた内部で「with transaction.atomic()」を使うと「ATOMIC_REQUESTS」を設定した時とはまた違う、ロールバックの動きをカスタマイズすることも可能です。
from django.db import transaction
@transaction.non_atomic_requests # VIEWトランザクションを無効
def my_view(request):
do_stuff()
もしデコレーターをVIEW関数ではなく、VIEWクラスに付けたい場合は以下のようにデコレーターを記載します。
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = 'secret.html'
@transaction.non_atomic_requests
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
まとめ
「ATOMIC_REQUEST」設定はとても便利な機能ですが、時にはその設定の影響が及ばない、あるいは適用できない機能を開発することもあるかもしれません。幸いにも、Djangoはトランザクションをコントロールするためのさまざま方法を備えているので、開発状況と仕様に応じて適切なトランザクションを設計しシステムの整合性を保ちましょう。
最後まで読んでいただきありがとうございました。
【公式サイト】
https://docs.djangoproject.com/en/5.1/topics/db/transactions/
https://docs.djangoproject.com/ja/5.2/topics/class-based-views/intro/#decorating-the-class