LoginSignup
31

More than 1 year has passed since last update.

SQLAlchemyでのsessionの扱い方

Last updated at Posted at 2020-04-01

はじめに

SQLAlchemyでは,sessionを生成したあと,必要に応じてcommit()rollback(),close()を行う必要がある.
ここでは,DB操作を行うクラスを作成し,sessionの受け渡し方についてまとめる.
以下では,下のようなモデルクラスが存在しているとする.

models.py
class User(Base):
    __tablename__ = "user"  # テーブル名を指定
    id = Column(Integer, primary_key=True)
    first_name = Column(String(255))
    last_name = Column(String(255))

sessionの悪い扱い方

以下のソースコードのようにしてしまうと良くない.

wrong_way.py
from models import User

class FirstName(object):
    def update_first_name(self, user_id, first_name):
        session = Session()
        try:
            user = session.query(User).filter(
                User.id == user_id).one()  # id=user_idであるobjを取り出す
            user.first_name = first_name  # first_nameを変更
            session.commit()
        except:
            session.rollback()
            raise

class LastName(object):
    def update_last_name(self, user_id, last_name):
        session = Session()
        try:
            user = session.query(User).filter(
                User.id == user_id).one()  # id=user_idであるobjを取り出す
            user.first_name = first_name  # last_nameを変更
            session.commit()
        except:
            session.rollback()
            raise

def run_my_program():
    FirstName().update_first_name(1, "update_first_name")
    LastName().update_last_name(1, "update_last_name")

なぜなら,run_my_program中のupdate_first_nameupdate_last_nameで,同一sessionが使われていないため,
「first_nameはupdateされたが,last_nameはupdateされなかった」
のようなことが起きてしまうからである.

sessionの良い扱い方1

そこで,以下のソースコードのようにすれば解決する.

right_way_1.py
from models import User

class FirstName(object):
    def update_first_name(self, user_id, first_name, session):
        user = session.query(User).filter(User.id == user_id).one()
        user.first_name = first_name

class LastName(object):
    def update_last_name(self, user_id, last_name, session):
        user = session.query(User).filter(User.id == user_id).one()
        user.first_name = first_name

def run_my_program():
    session = Session()
    try:
        FirstName().update_first_name(session)
        LastName().update_last_name(session)
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()

こうすることで,update_first_nameupdate_last_nameで,同一sessionが使われ,
「片方だけが正常に実行された」
のような状況が起こらない.

sessionの良い扱い方2

次に,context managerを用いたsessionの扱い方を紹介する.
なお,context managerについては,以下の記事が大変わかりやすく参考になるのでここでは説明を省略する.
Pythonのコンテキストマネージャってなんなの?と思って調べた話

right_way_2.py
from models import User
from contextlib import contextmanager

@contextmanager
def session_scope():
    session = Session()  # def __enter__
    try:
        yield session  # with asでsessionを渡す
        session.commit()  # 何も起こらなければcommit()
    except:
        session.rollback()  # errorが起こればrollback()
        raise
    finally:
        session.close()  # どちらにせよ最終的にはclose()

class FirstName(object):
    def update_first_name(self, user_id, first_name, session):
        user = session.query(User).filter(User.id == user_id).one()
        user.first_name = first_name

class LastName(object):
    def update_last_name(self, user_id, last_name, session):
        user = session.query(User).filter(User.id == user_id).one()
        user.first_name = first_name

def run_my_program():
    with session_scope() as session:
        FirstName().update_first_name(session)
        LastName().update_last_name(session)

参考文献

この記事は以下の情報を参考にして執筆しました.
公式ドキュメント(Session Basics)
Pythonのコンテキストマネージャってなんなの?と思って調べた話

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31