Session Basics

What does the Session do ?

最も一般的な意味では、 Session はデータベースとのすべての対話を確立し、その存続期間中にロードまたは関連付けられたすべてのオブジェクトの”保持ゾーン”を表します。これは、ORMマップされたオブジェクトを返したり変更したりするSELECTやその他のクエリが行われるインターフェイスを提供します。ORMオブジェクト自体は、 Session 内の identity map と呼ばれる構造体内に保持されます。この構造体は、各オブジェクトの一意のコピーを保持するデータ構造で、”一意”は”特定の主キーを持つ1つのオブジェクトのみ”を意味します。

Session の最も一般的な使用パターンは、ほとんどステートレスな形式で始まります。クエリが発行されるか、他のオブジェクトが永続化されると、 Session に関連付けられた Engine から接続リソースを要求し、その接続でトランザクションを確立します。このトランザクションは、 Session がトランザクションをコミットまたはロールバックするように指示されるまで有効です。トランザクションが終了すると、 Engine に関連付けられた接続リソースは、エンジンが管理する接続プールに:term:released されます。新しいトランザクションは、新しい接続のチェックアウトから始まります。

Session で管理されるORMオブジェクトは instrumented であり、Pythonプログラム内で属性やコレクションが変更されると、変更イベントが生成されて Session に記録されます。データベースへの問い合わせやトランザクションのコミットが行われると、 Session はまず、メモリに保存されている保留中の変更をすべてデータベースに**フラッシュ**します。これは unit of work パターンとして知られています。

Session を使用する場合、データベースの行に対して**プロキシオブジェクト**として保持されているORMマップオブジェクトを考慮すると便利です。データベースの行は、 Session によって保持されているトランザクションに対してローカルです。オブジェクトの状態をデータベース内の実際の状態と一致するように維持するために、同期を維持するためにオブジェクトがデータベースに再アクセスする原因となるさまざまなイベントがあります。 Session からオブジェクトを「切り離し」、その使用を継続することは可能ですが、この方法には注意が必要です。通常は、切り離したオブジェクトをもう一度操作したいときに、別の Session に再関連付けして、データベースの状態を表す通常のタスクを再開できるようにすることが意図されています。

Basics of Using a Session

最も基本的な Session の使用パターンをここに示します。

Opening and Closing a Session

Session は、単独で構築することも、 sessionmaker クラスを使用して構築することもできます。これには通常、事前に接続のソースとして単一の Engine が渡されます。典型的な使用法は次のようになります:

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# create session and add objects
with Session(engine) as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()

上の例では、 Session は特定のデータベースURLに関連付けられた Engine でインスタンス化されています。その後、Pythonのコンテキストマネージャ(つまり、 with: 文)で使用され、ブロックの最後で自動的に閉じられます。これは Session.close() メソッドを呼び出すのと同じです。

Session.commit() の呼び出しはオプションであり、 Session で行った作業に、データベースに永続化される新しいデータが含まれている場合にのみ必要です。SELECT呼び出しを発行するだけで、変更を書き込む必要がなければ、 Session.commit() の呼び出しは不要です。

Note

Session.commit() が明示的に、またはコンテキストマネージャを使用して呼び出された後、 Session に関連付けられたすべてのオブジェクトは expired になります。これは、その内容が次のトランザクション内で再ロードされるために消去されることを意味します。これらのオブジェクトが代わりに detached である場合、この動作を無効にするために Session.expire_on_commit パラメータが使用されない限り、新しい Session に再関連付けられるまで機能しません。詳細については Committing を参照してください。

Framing out a begin / commit / rollback block

また、データベースにデータをコミットする場合に備えて、コンテキストマネージャ内に Session.commit() 呼び出しとトランザクションの全体的な「フレーミング」を含めることもできます。「フレーミング」とは、すべての操作が成功した場合に Session.commit() メソッドが呼び出されることを意味しますが、例外が発生した場合には Session.rollback() メソッドが呼び出され、例外が外部に伝播される前にトランザクションが即座にロールバックされます。Pythonでは、これは基本的に次のような try: / except: / else: ブロックを使って表現されます。:

# verbose version of what a context manager will do
with Session(engine) as session:
    session.begin()
    try:
        session.add(some_object)
        session.add(some_other_object)
    except:
        session.rollback()
        raise
    else:
        session.commit()

Session.begin() メソッドが返す SessionTransaction オブジェクトを利用することで、上記の長い形式の操作シーケンスをより簡潔に実現することができます。 SessionTransaction オブジェクトは、同じ操作シーケンスのコンテキストマネージャインタフェースを提供します。:

# create session and add objects
with Session(engine) as session:
    with session.begin():
        session.add(some_object)
        session.add(some_other_object)
    # inner context calls session.commit(), if there were no exceptions
# outer context calls session.close()

より簡潔に言うと、2つのコンテキストを組み合わせることができます。:

# create session and add objects
with Session(engine) as session, session.begin():
    session.add(some_object)
    session.add(some_other_object)
# inner context calls session.commit(), if there were no exceptions
# outer context calls session.close()

Using a sessionmaker

sessionmaker の目的は、 Session オブジェクトのファクトリを固定の設定で提供することです。アプリケーションがモジュールスコープ内に Engine オブジェクトを持つのが一般的なので、 sessionmaker は、このエンジンに対して Session オブジェクトのファクトリを提供できます:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# an Engine, which the Session will use for connection
# resources, typically in module scope
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)

# we can now construct a Session() without needing to pass the
# engine each time
with Session() as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()
# closes the session

sessionmaker は、関数レベルのセッション/接続のためのモジュールレベルのファクトリとして Engine に似ています。そのため、 Engine.begin() に似た独自の sessionmaker.begin() メソッドもあります。このメソッドは Session オブジェクトを返し、begin/commit/rollbackブロックも保持します:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# an Engine, which the Session will use for connection
# resources
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/")

# a sessionmaker(), also in the same scope as the engine
Session = sessionmaker(engine)

# we can now construct a Session() and include begin()/commit()/rollback()
# at once
with Session.begin() as session:
    session.add(some_object)
    session.add(some_other_object)
# commits the transaction, closes the session

Where above, the Session will both have its transaction committed as well as that the Session will be closed, when the above with: block ends.

上記の場合、 Session のトランザクションがコミットされるとともに、 Session が閉じられます。このとき、上記の with: ブロックが終了します。

アプリケーションを作成するとき、 sessionmaker ファクトリは、 create_engine() によって作成された Engine オブジェクトと同じスコープにする必要があります。これは通常、モジュールレベルまたはグローバルスコープです。これらのオブジェクトは両方ともファクトリであるため、任意の数の関数やスレッドで同時に使用できます。

Querying

クエリの主な方法は、 select() 構文を使用して Select オブジェクトを作成し、これを実行して Session.execute()Session.scalars() などのメソッドを使用して結果を返すことです。結果は Result オブジェクトで返され、 ScalarResult などのサブバリアントも含まれます。

SQLAlchemy ORM問い合わせの完全なガイドは ORM Querying Guide にあります。いくつかの簡単な例を以下に示します:

from sqlalchemy import select
from sqlalchemy.orm import Session

with Session(engine) as session:
    # query for ``User`` objects
    statement = select(User).filter_by(name="ed")

    # list of ``User`` objects
    user_obj = session.scalars(statement).all()

    # query for individual columns
    statement = select(User.name, User.fullname)

    # list of Row objects
    rows = session.execute(statement).all()

Changed in version 2.0:

”2.0” スタイルのクエリが標準になりました。1.xシリーズからの移行に関する注意については 2.0 Migration - ORM Usage を参照してください。

Adding New or Existing Items

Session.add() は、セッション内にインスタンスを配置するために使用されます。 transient (つまり、新しい)インスタンスの場合、これは次のフラッシュ時にそれらのインスタンスに対してINSERTが行われる効果があります。 persistent (つまり、このセッションによってロードされた)インスタンスの場合、それらはすでに存在しており、追加する必要はありません。 detached (つまり、セッションから削除された)インスタンスは、セッションに再関連付けできます。

次のメソッドを使用します。:

user1 = User(name="user1")
user2 = User(name="user2")
session.add(user1)
session.add(user2)

session.commit()  # write changes to the database

セッションアットワンスに項目のリストを追加するには、 Session.add_all() を使用します:

session.add_all([item1, item2, item3])

Session.add() 操作は、 save-update カスケードに**カスケード**します。詳細は Cascades 節を参照してください。

Deleting

Session.delete() メソッドは、削除済みとしてマークされるオブジェクトのSessionのリストにインスタンスを置きます:

# mark two objects to be deleted
session.delete(obj1)
session.delete(obj2)

# commit (or flush)
session.commit()

Session.delete() はオブジェクトに削除のマークを付けます。これにより、影響を受ける主キーごとにDELETE文が発行されます。保留中の削除がフラッシュされる前に、「delete」とマークされたオブジェクトが Session.deleted コレクションに存在します。DELETEの後、それらは Session から削除され、トランザクションがコミットされた後に永続的になります。

Session.delete() 操作に関連して、特に他のオブジェクトやコレクションとの関係がどのように処理されるかに関して、さまざまな重要な動作があります。これがどのように機能するかについての詳細は Cascades のセクションにありますが、一般的なルールは次のとおりです:

  • relationship() ディレクティブを介して削除されたオブジェクトに関連付けられているマップされたオブジェクトに対応する行は、 デフォルトでは削除されません 。これらのオブジェクトが削除される行に対して外部キー制約を持つ場合、これらの列はNULLに設定されます。列がNULLにできない場合、制約違反が発生します。

  • “SET NULL” を関連するオブジェクトの行のDELETEに変更するには、 relationship()delete カスケードを使用してください。

  • relationship.secondary パラメータを介して”多対多”テーブルとしてリンクされているテーブル内の行は、参照先のオブジェクトが削除されると、すべての場合に**削除されます。

  • 関連するオブジェクトに削除されるオブジェクトへの外部キー制約が含まれていて、それらが属する関連するコレクションが現在メモリにロードされていない場合、作業単位はSELECTを発行してすべての関連する行をフェッチし、それらの主キー値を使用してそれらの関連する行に対してUPDATE文またはDELETE文を発行できるようにします。このように、Core ForeignKeyConstraint オブジェクトにON DELETE CASCADEが設定されていても、それ以上の指示のないORMはON DELETE CASCADEの機能を実行します。

  • relationship.passive_deletes パラメータを使用して、この動作を調整し、より自然に”ON DELETE CASCADE”に依存することができます。Trueに設定すると、このSELECT操作は行われなくなりますが、ローカルに存在する行は明示的なSET NULLまたはDELETEの対象になります。 relationship.passive_deletes を文字列 "all" に設定すると、関連する すべての オブジェクトの更新/削除が無効になります。

  • 削除対象としてマークされたオブジェクトに対してDELETEが行われた場合、そのオブジェクトはコレクションやそれを参照するオブジェクト参照から自動的には削除されません。 Session が期限切れになると、これらのコレクションが再度ロードされ、オブジェクトが存在しなくなる可能性があります。しかし、これらのオブジェクトに対して Session.delete() を使用する代わりに、そのオブジェクトをコレクションから削除してから、 delete-orphan を使用して、コレクション削除の二次的な影響として削除することをお勧めします。この例については Notes on Delete - Deleting Objects Referenced from Collections and Scalar Relationships を参照してください。

See also

delete-リードオブジェクトが削除されたときに、関連するオブジェクトに削除のマークを付ける”delete cascade”について説明します。

delete-orphan - “オーファンカスケードの削除”を記述します。これは、関連するオブジェクトが先頭オブジェクトから関連付けを解除されたときに、削除するようにマークします。

Notes on Delete - Deleting Objects Referenced from Collections and Scalar Relationships - Session.delete() の重要な背景には、メモリ内で関係が更新されることが含まれています。

Flushing

Session をデフォルトの設定で使用すると、フラッシュのステップはほとんどの場合透過的に行われます。具体的には、フラッシュは Query または 2.0-styleSession.execute() 呼び出しの結果として個々のSQL文が発行される前に行われます。また、トランザクションがコミットされる前の Session.commit() 呼び出し内でも行われます。 Session.begin_nested() が使用されている場合は、SAVEPOINTが発行される前にも行われます。

Session フラッシュは、 Session.flush() メソッドを呼び出すことで、いつでも強制できます:

session.flush()

特定のメソッドの範囲内で自動的に発生するフラッシュは、 自動フラッシュ と呼ばれます。自動フラッシュは、次のようなメソッドの先頭で発生する、構成可能な自動フラッシュ・コールとして定義されます。:

  • Session.execute() やその他のSQL実行メソッドを、ORMエンティティやORMにマップされた属性を参照する select() オブジェクトのようなORM対応のSQL構文に対して使用した場合

  • データベースにSQLを送るために Query が呼び出された場合

  • データベースに問い合わせる前の Session.merge() メソッド内

  • オブジェクトが refreshed

  • アンロードされたオブジェクト属性に対してORM lazy load 操作が行われた場合。

フラッシュが 無条件に 発生するポイントもあります。これらのポイントは、次のような主要なトランザクション境界内にあります。

前の項目リストに適用された autoflush 動作は、 Session または sessionmaker を構築し、 Session.autoflush パラメータを False として渡すことで無効にすることができます:

Session = sessionmaker(autoflush=False)

さらに、自動フラッシュは、 Session.no_autoflush コンテキストマネージャを使用して、 Session を使用するフロー内で一時的に無効にすることができます:

with mysession.no_autoflush:
    mysession.add(some_object)
    mysession.flush()

繰り返しますが: フラッシュプロセスは 常に発生します Session.commit()Session.begin_nested() のようなトランザクショナルなメソッドが呼び出された場合、”autoflush”の設定に関わらず、 Session に処理すべき未決定の変更が残っている場合です。

SessionDBAPI トランザクションのコンテキスト内でのみデータベースに対してSQLを呼び出すので、DBAPIが driver level autocommit モードでない限り、すべての”フラッシュ”操作自体はデータベーストランザクション内でのみ発生します(データベーストランザクションの isolation level に従います)。これは、データベース接続がトランザクション設定内で atomicity を提供していると仮定すると、フラッシュ内の個々のDML文が失敗した場合、操作全体がロールバックされることを意味します。

フラッシュ内で障害が発生した場合、同じ Session を使い続けるためには、フラッシュが失敗した後に Session.rollback() を明示的に呼び出す必要があります。これは、基礎となるトランザクションがすでにロールバックされていても(データベースドライバが技術的にはドライバレベルのオートコミットモードであっても)です。これは、いわゆる”サブトランザクション”の全体的なネストパターンが一貫して維持されるようにするためです。FAQのセクション “This Session’s transaction has been rolled back due to a previous exception during flush.” (or similar) には、この動作の詳細な説明があります。

See also

“This Session’s transaction has been rolled back due to a previous exception during flush.” (or similar) - フラッシュが失敗した時になぜ Session.rollback() を呼ばなければならないのかについてのさらなる背景です。

Get by Primary Key

Session はメモリー内の現在のオブジェクトを主キーで参照する identity map を利用するので、オブジェクトを主キーで検索する手段として Session.get() メソッドが提供されています。最初に現在のidentity map内を検索し、次に存在しない値をデータベースに問い合わせます。例えば、主キーのIDを持つ User エンティティを検索するには、 (5, ) とします。:

my_user = session.get(User, 5)

Session.get() には、タプルまたは辞書として渡すことができる複合主キー値の呼び出し形式と、特定のローダおよび実行オプションを可能にする追加パラメータも含まれています。完全なパラメータリストについては Session.get() を参照してください。

See also

Session.get()

Expiring / Refreshing

Session を使用する際によく出てくる重要な考慮事項は、データベースからロードされたオブジェクトに存在する状態を、トランザクションの現在の状態と同期させるという観点から処理することです。SQLAlchemy ORMは identity map の概念に基づいており、オブジェクトがSQLクエリから「ロード」されると、特定のデータベースIDに対応して保持される一意のPythonオブジェクトインスタンスが存在するようになります。つまり、それぞれが同じ行に対して2つの別々のクエリを発行し、マップされたオブジェクトを取得した場合、2つのクエリは同じPythonオブジェクトを返します:

>>> u1 = session.scalars(select(User).where(User.id == 5)).one()
>>> u2 = session.scalars(select(User).where(User.id == 5)).one()
>>> u1 is u2
True

これに従って、ORMがクエリから行を取得するとき、すでにロードされているオブジェクトに対して 属性の入力をスキップ します。ここでの設計上の前提は、トランザクションが完全に分離されていることを前提とし、トランザクションが分離されていない程度まで、アプリケーションは必要に応じてデータベーストランザクションからオブジェクトを更新するための手順を実行できるということです。 I’m re-loading data with my Session but it isn’t seeing changes that I committed elsewhere のFAQエントリでは、この概念について詳しく説明しています。

ORMマップされたオブジェクトがメモリにロードされると、現在のトランザクションからの新しいデータでその内容を更新する一般的な方法が3つあります。:

  • expire()メソッド - Session.expire() メソッドは、オブジェクトの選択された属性またはすべての属性の内容を消去し、次にアクセスされたときにデータベースから読み込まれるようにします。例えば lazy loading パターンを使用します:

    session.expire(u1)
    u1.some_attribute  # <-- lazy loads from the transaction
  • refresh()メソッド - 密接に関連しているのは Session.refresh() メソッドです。これは Session.expire() メソッドが行うことをすべて行いますが、オブジェクトの内容を実際にリフレッシュするために1つ以上のSQLクエリを即座に発行します:

    session.refresh(u1)  # <-- emits a SQL query
    u1.some_attribute  # <-- is refreshed from the transaction
  • populate_existing()メソッドまたは実行オプション - これは現在、 Populate Existing で文書化されている実行オプションです。レガシー形式では Query オブジェクトの Query.populate_existing() メソッドにあります。どちらの形式のこの操作も、クエリから返されるオブジェクトがデータベース内の内容から無条件に再生成されるべきであることを示しています:

    u2 = session.scalars(
        select(User).where(User.id == 5).execution_options(populate_existing=True)
    ).one()

refresh/expireの概念についての詳細な議論は Refreshing / Expiring にあります。

UPDATE and DELETE with arbitrary WHERE clause

SQLAlchemy 2.0には、ORM対応のINSERT、UPDATE、DELETE文を生成する機能が強化されています。ドキュメントは ORM-Enabled INSERT, UPDATE, and DELETE statements のドキュメントを参照してください。

Auto Begin

Session オブジェクトは autobegin として知られている振る舞いを特徴としています。これは SessionSession で何らかの作業が行われるとすぐに、内部的に自身が”トランザクション”状態にあるとみなすことを示しています。これにはオブジェクトの状態の変更に関する Session の内部状態の変更や、データベース接続を必要とする操作が含まれます。

Session が最初に構築されたときには、トランザクション状態は存在しません。トランザクション状態は、 Session.add()Session.execute() のようなメソッドが呼び出されたときや、結果を返すために Query が実行されたとき(最終的には Session.execute() を使用します)、あるいは persistent オブジェクトの属性が変更されたときに、自動的に開始されます。

トランザクション状態は、 Session.in_transaction() メソッドにアクセスすることでチェックできます。このメソッドは、”autobegin”ステップが進行したかどうかを示す True または False を返します。通常は必要ありませんが、 Session.get_transaction() メソッドは、このトランザクション状態を表す実際の SessionTransaction オブジェクトを返します。

Session のトランザクション状態は、 Session.begin() メソッドを呼び出すことで、明示的に開始することもできます。このメソッドが呼び出されると、 Session は無条件に”トランザクション”状態になります。 Session.begin() は、 Framing out a begin / commit / rollback block で説明されているように、コンテキストマネージャとして使用できます。

Disabling Autobegin to Prevent Implicit Transactions

「autobegin」の動作は、 Session.autobegin パラメータを False に設定することで無効にすることができます。このパラメータを使用すると、 SessionSession.begin() メソッドを明示的に呼び出す必要があります。構築時、および Session.rollback()Session.commit() 、または Session.close() メソッドのいずれかが呼び出された後も、 Session は暗黙的に新しいトランザクションを開始せず、最初に Session.begin() を呼び出さずに Session を使用しようとするとエラーが発生します:

with Session(engine, autobegin=False) as session:
    session.begin()  # <-- required, else InvalidRequestError raised on next call

    session.add(User(name="u1"))
    session.commit()

    session.begin()  # <-- required, else InvalidRequestError raised on next call

    u1 = session.scalar(select(User).filter_by(name="u1"))

New in version 2.0: Session.autobegin が追加され、”autobegin”の動作を無効にできるようになりました。

Committing

Session.commit() は、現在のトランザクションをコミットするために使用されます。これは基本的に、進行中のトランザクションを持つ現在のすべてのデータベース接続に対して COMMIT を発行することを意味します。 DBAPI から見ると、これは各DBAPI接続に対して connection.commit() DBAPIメソッドが呼び出されることを意味します。

前回の Session.commit() の呼び出し以降、この Session に対して操作が呼び出されていないことを示す、 Session のトランザクションが存在しない場合、メソッドは内部のみの”論理”トランザクションを開始してコミットします。このトランザクションは、保留中のフラッシュ変更が検出されない限り、通常はデータベースに影響を与えませんが、イベントハンドラとオブジェクトの有効期限ルールは呼び出します。

Session.commit() 操作は、関連するデータベース接続でCOMMITを発行する前に、無条件に Session.flush() を発行します。保留中の変更が検出されない場合、データベースにSQLは発行されません。この動作は設定できず、 Session.autoflush パラメータの影響も受けません。

その後、 SessionEngine にバインドされていると仮定すると、 Session.commit() は、実際のデータベーストランザクションが開始されていれば、そのトランザクションをコミットします。コミット後、そのトランザクションに関連付けられた Connection オブジェクトが閉じられ、その基礎となるDBAPI接続が、 Session がバインドされている Engine に関連付けられた接続プールに released されます。

(例えば Partitioning Strategies で説明されているように)複数のエンジンにバインドされた Session の場合、コミットされている”論理”トランザクション内で動作している Engine/Connection ごとに同じCOMMITステップが実行されます。これらのデータベーストランザクションは、 two-phase features が有効になっていない限り、相互に調整されません。

SessionConnection に直接バインドすることで、他の接続相互作用パターンも利用できます。この場合、外部で管理されたトランザクションが存在すると想定され、実際のCOMMITは自動的には発行されません。このパターンの背景については Joining a Session into an External Transaction (such as for test suites) を参照してください。

最後に、 Session 内のすべてのオブジェクトは、トランザクションがクローズされたときに expired になります。これは、インスタンスが次にアクセスされたときに、属性アクセスかSELECTの結果に存在することによって、最新の状態を受け取るようにするためです。この動作は Session.expire_on_commit フラグで制御できます。このフラグは、この動作が望ましくない場合には False に設定できます。

See also

Auto Begin

Rolling Back

Session.rollback() は、もしあれば、現在のトランザクションをロールバックします。トランザクションが存在しない場合、メソッドは黙って通過します。

デフォルトで設定されたセッションでは、 autobegin または Session.begin() メソッドを明示的に呼び出してトランザクションが開始された後の、セッションのロールバック後の状態は次のようになります。:

  • データベーストランザクションはロールバックされます。 Session が単一の Engine にバインドされている場合、これはROLLBACKが現在使用中の単一の Connection に対してのみ発行されることを意味します。 Session オブジェクトが複数の Engine オブジェクトにバインドされている場合、ROLLBACKはチェックアウトされたすべての Connection オブジェクトに対して発行されます。

  • データベース接続は released です。これは Committing と同じ接続関連の動作に従います。ここで Engine オブジェクトから取得された Connection オブジェクトは閉じられ、DBAPI接続は Engine 内の接続プールに対して released になります。新しいトランザクションが開始されると、新しい接続は Engine からチェックアウトされます。

  • Joining a Session into an External Transaction (such as for test suites) で説明されているように、 Connection に直接バインドされている Session の場合、この Connection のロールバック動作は、 Session.join_transaction_mode パラメータで指定された動作に従います。これには、セーブポイントのロールバックや実際のROLLBACKの発行が含まれます。

  • トランザクションの有効期間内に :class`~sqlalchemy.orm.session.Session` に追加されたとき、最初は pending 状態にあったオブジェクトは、ロールバックされるINSERT文に対応して削除されます。それらの属性の状態は変更されません。

  • トランザクションの有効期間内に deleted としてマークされたオブジェクトは、ロールバックされるDELETE文に対応して、 persistent 状態に戻されます。これらのオブジェクトがトランザクション内で最初に pending であった場合、その操作が優先されることに注意してください。

  • 削除されなかったすべてのオブジェクトは完全に期限切れになります-これは paramref:_orm.Session.expire_on_commit の設定に関係ありません。

この状態が理解されれば、 Session はロールバックが発生した後も安全に使用を続けることができます。

Changed in version 1.4:

Session オブジェクトは、 autobegin で説明されているように、遅延された”begin”動作を特徴とするようになりました。トランザクションが開始されない場合、 Session.commit()Session.rollback() のようなメソッドは効果がありません。非オートコミットモードでは、トランザクションは常に暗黙的に存在するので、この動作は1.4より前には見られませんでした。

Session.flush() が失敗した場合、通常はプライマリキー、外部キー、”not nullable”制約違反などの理由で、ROLLBACKが自動的に発行されます(現在のところ、部分的な失敗の後にフラッシュを続けることはできません)。しかし、 Session はこの時点で”inactive”と呼ばれる状態になり、呼び出し側のアプリケーションは Session が使用可能な状態に戻れるように、常に Session.rollback() メソッドを明示的に呼び出さなければなりません(単に閉じて破棄することもできます)。詳細については “This Session’s transaction has been rolled back due to a previous exception during flush.” (or similar) のFAQ項目を参照してください。

See also

Auto Begin

Closing

Session.close() メソッドは Session.expunge_all() を発行して、ORMマップされたすべてのオブジェクトをセッションから削除し、 releases して、それがバインドされている Engine オブジェクトからすべてのトランザクション/接続リソースを解放します。接続が接続プールに戻されると、トランザクション状態もロールバックされます。

デフォルトでは、 Session が閉じられると、基本的には最初に構築されたときと同じ元の状態になり、 再び使用することができます 。この意味で、 Session.close() メソッドは、”データベースを閉じる”メソッドというよりも、クリーンな状態に戻す”リセット”のようなものです。この操作モードでは、メソッド Session.reset()Session.close() のエイリアスであり、同じように動作します。

Session.close() のデフォルトの動作は、パラメータ Session.close_resets_onlyFalse に設定することで変更できます。これは、メソッド Session.close() が呼び出された後は、 Session を再利用できないことを示します。この操作モードでは、 Session.reset() メソッドはセッションの複数回の”リセット”を許可し、 Session.close_resets_onlyTrue に設定されている場合の Session.close() のように動作します。

New in version 2.0.22.

特に Session.commit() または Session.rollback() メソッドが使用されていない場合は、最後に Session.close() を呼び出すことで Session のスコープを制限することをお勧めします。 Session をコンテキストマネージャとして使用して、 Session.close() が確実に呼び出されるようにすることができます:

with Session(engine) as session:
    result = session.execute(select(User))

# closes session automatically

Changed in version 1.4:

Session オブジェクトは、 autobegin で説明されているように、遅延された”begin”動作を特徴としています。 Session.close() メソッドが呼び出された後、すぐに新しいトランザクションを開始しなくなりました。

Session Frequently Asked Questions

この時点で、すでに多くのユーザがセッションに関して疑問を持っています。このセクションでは、 Session を使用する際に提示される最も基本的な問題のミニFAQ( real FAQ もあることに注意してください)を示します。

When do I make a sessionmaker?

あなたのアプリケーションのグローバルスコープのどこかで一度だけです。これはあなたのアプリケーションの設定の一部と見なされるべきです。もしあなたのアプリケーションがパッケージの中に3つの.pyファイルを持っているなら、例えば、 sessionmaker 行をあなたの __init__.py ファイルに置くことができます。その時点から、あなたの他のモジュールでは”from mypackage import Session”と言います。そうすれば、他のすべての人は Session() を使うだけで、そのセッションの設定はその中心点によって制御されます。

アプリケーションが起動してインポートを行っても、接続先のデータベースがわからない場合は、 sessionmaker.configure() を使用して、後で Session を”クラス”レベルでエンジンにバインドすることができます。

このセクションの例では、実際に Session を呼び出す行のすぐ上に sessionmaker が作成されることを頻繁に示します。しかし、それは単なる例です!実際には、 sessionmaker はモジュールレベルのどこかにあります。 Session をインスタンス化するための呼び出しは、データベースの会話が始まるアプリケーション内のポイントに配置されます。

When do I construct a Session, when do I commit it, and when do I close it?

Session は通常、データベースアクセスが潜在的に予想される論理演算の最初に構築されます。

Session は、データベースとの通信に使用される場合は常に、通信を開始するとすぐにデータベーストランザクションを開始します。このトランザクションは、 Session がロールバック、コミット、またはクローズされるまで進行中です。 Session は、前のトランザクションが終了した後に再び使用されると、新しいトランザクションを開始します。このことから、 Session は、一度に1つだけではありますが、多くのトランザクションにわたって寿命を持つことができることになります。これら2つの概念を トランザクションスコープ および セッションスコープ と呼びます。

通常、 Session のスコープを開始したり終了したりする最適なポイントを決定するのはそれほど難しくありませんが、可能なアプリケーションアーキテクチャが多様であるため、困難な状況が発生する可能性があります。

次のようなシナリオ例があります。:

  • Webアプリケーション。この場合、使用中のWebフレームワークによって提供されるSQLAlchemy統合を利用するのが最善です。または、基本的なパターンは、Webリクエストの最初に Session を作成し、POST、PUT、またはDELETEを行うWebリクエストの最後に Session.commit() メソッドを呼び出し、Webリクエストの最後にセッションを閉じることです。また、通常は Session.expire_on_commit をFalseに設定して、ビューレイヤ内の Session から来たオブジェクトへの後続のアクセスが、トランザクションがすでにコミットされている場合に、オブジェクトを更新するために新しいSQLクエリを発行する必要がないようにすることもお勧めします。

  • 子forkを生成するバックグラウンドデーモンは、それぞれの子プロセスに対してローカルな Session を作成し、forkが処理している”ジョブ”の間ずっとその Session を操作し、ジョブが完了したらそれを破棄したいと思うでしょう。

  • コマンドラインスクリプトの場合、アプリケーションは単一のグローバルな Session を作成します。このセッションは、プログラムが作業を開始したときに確立され、プログラムがタスクを完了するときに正しくコミットされます。

  • GUIインターフェイス駆動型アプリケーションの場合、 Session のスコープは、ボタン押下などのユーザが生成したイベントのスコープ内にあるのが最適です。または、スコープは、ユーザが一連のレコードを”オープン”してから”セーブ”するなど、明示的なユーザインタラクションに対応する場合もあります。

原則として、アプリケーションは、特定のデータを処理する関数に対して、セッションのライフサイクルを 外部的に 管理する必要があります。これは、データ固有の操作が、そのデータにアクセスして操作するコンテキストに依存しないようにするための、基本的な関心事の分離です。

E.g. こうしてはいけません:

### this is the **wrong way to do it** ###

class ThingOne:
    def go(self):
        session = Session()
        try:
            session.execute(update(FooBar).values(x=5))
            session.commit()
        except:
            session.rollback()
            raise

class ThingTwo:
    def go(self):
        session = Session()
        try:
            session.execute(update(Widget).values(q=18))
            session.commit()
        except:
            session.rollback()
            raise

def run_my_program():
    ThingOne().go()
    ThingTwo().go()

セッション(および通常はトランザクション)のライフサイクルを**分離して外部に**保持します。以下の例では、これがどのように見えるかを示し、さらにPythonコンテキストマネージャ(つまり、 with: キーワード)を使用して、 Session とそのトランザクションのスコープを自動的に管理します:

### this is a **better** (but not the only) way to do it ###

class ThingOne:
    def go(self, session):
        session.execute(update(FooBar).values(x=5))

class ThingTwo:
    def go(self, session):
        session.execute(update(Widget).values(q=18))

def run_my_program():
    with Session() as session:
        with session.begin():
            ThingOne().go(session)
            ThingTwo().go(session)

Changed in version 1.4: Session は外部ヘルパー関数を使わずにコンテキストマネージャとして使うことができます。

Is the Session a cache?

ええ…いいえ。 identity map パターンを実装し、オブジェクトをその主キーにキー付けして保存するという点で、キャッシュとして使用されています。しかし、クエリのキャッシュは一切行いません。つまり、 session.scalars(select(Foo).filter_by(name='bar')) と言った場合、 Foo(name='bar') がIDマップのすぐそこにあっても、セッションはそれについて何も知りません。データベースにSQLを発行して行を取得し、行に主キーがあることを確認したら、ローカルのIDマップを調べてオブジェクトがすでにそこにあることを確認できます。 Session がクエリを発行する必要がないのは、 query.get({some primary key}) と言った場合だけです。

さらに、Sessionはデフォルトで弱参照を使用してオブジェクト・インスタンスを格納します。これにより、Sessionをキャッシュとして使用する目的も無効になります。

Session は、誰もがオブジェクトの「レジストリ」として参照するグローバルオブジェクトになるようには設計されていません。それはむしろ**セカンドレベルキャッシュ**の仕事です。SQLAlchemyは、 Dogpile Caching の例を通じて、 dogpile.cache を使ってセカンドレベルキャッシュを実装するためのパターンを提供します。

How can I get the Session for a certain object?

Session で利用可能な Session.object_session() クラスメソッドを使用してください:

session = Session.object_session(someobject)

新しい Runtime Inspection API システムも使用できます:

from sqlalchemy import inspect

session = inspect(someobject).session

Is the Session thread-safe? Is AsyncSession safe to share in concurrent tasks?

Session は、 単一のデータベーストランザクション**を 表す* *可変でステートフルな オブジェクトです。したがって、 Session のインスタンスは 慎重に同期しない限り、並行スレッドや非同期タスクの間で共有することはできませんSession並行でない 方法で使用することを意図しています。つまり、 Session の特定のインスタンスは、一度に1つのスレッドまたはタスクでのみ使用する必要があります。

SQLAlchemyの asyncio 拡張から AsyncSession オブジェクトを使用する場合、このオブジェクトは Session の上の薄いプロキシに過ぎず、同じ規則が適用されます。つまり、非同期で、可変で、ステートフルなオブジェクト**であるため、 :class:`_asyncio.AsyncSession` の単一インスタンスを複数のasyncioタスクで同時に使用することは**安全では**ありません

Session または AsyncSession のインスタンスは、単一の論理データベーストランザクションを表し、オブジェクトがバインドされている特定の Engine または AsyncEngine に対して一度に1つの Connection だけを参照します(これらのオブジェクトは同時に複数のエンジンにバインドすることをサポートしていますが、この場合、トランザクションのスコープ内ではエンジンごとに1つの接続しか存在しません)。

トランザクション内のデータベース接続は、並行ではないシーケンシャルな方法で操作されることを意図したステートフルなオブジェクトでもあります。コマンドは接続に対して順番に発行され、データベースサーバーによって正確な順序で処理されます。 Session がこの接続に対してコマンドを発行し、結果を受け取ると、 Session 自体は、この接続に存在するコマンドとデータの状態に合わせた内部状態の変化を通じて遷移します。状態には、トランザクションが開始されたか、コミットされたか、ロールバックされたか、SAVEPOINTが実行されている場合はその内容、ローカルのORMマップオブジェクトと個々のデータベース行の状態のきめ細かい同期などが含まれます。

並行性のためにデータベース・アプリケーションを設計する場合、適切なモデルは、各並行タスク/スレッドが独自のデータベース・トランザクションで動作することです。これが、データベースの並行性の問題を説明するときに使用される標準的な用語が 複数の並行トランザクション である理由です。従来のRDMSには、複数のコマンドを同時に受信して処理する単一のデータベース・トランザクションに相当するものはありません。

したがって、SQLAlchemyの SessionAsyncSession の並行性モデルは、 スレッドごとのセッション、タスクごとのAsyncSession となります。複数のスレッドを使うアプリケーションや、例えば asyncio.gather() のようなAPIを使うようなasyncioで複数のタスクを使うアプリケーションでは、それぞれのスレッドが独自の Session を持ち、それぞれのasyncioタスクが独自の AsyncSession を持つようにしたいでしょう。

これを確実に使用する最善の方法は、スレッドまたはタスク内にあるトップレベルのPython関数内でローカルに standard context manager pattern を使用することです。これにより、 Session または AsyncSession の寿命がローカルスコープ内で維持されます。

Session オブジェクトを必要とする特定の関数やメソッドに渡すことができない「グローバルな」 Session を持つことで利益を得るアプリケーションでは、 scoped_session アプローチは「スレッドローカルな」 Session オブジェクトを提供できます。背景については Contextual/Thread-local Sessions のセクションを参照してください。asyncioコンテキスト内では、 async_scoped_session オブジェクトは scoped_session のasyncio版ですが、カスタムの「コンテキスト」関数を必要とするため、設定がより困難です。