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 sessionsessionmaker は、関数レベルのセッション/接続のためのモジュールレベルのファクトリとして 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 sessionWhere 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 を参照してください。
See also
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 操作が行われた場合。
フラッシュが 無条件に 発生するポイントもあります。これらのポイントは、次のような主要なトランザクション境界内にあります。
Session.commit()メソッドのプロセス内Session.begin_nested()が呼び出されたときSession.prepare()2 PCメソッドが使用されている場合。
前の項目リストに適用された 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 に処理すべき未決定の変更が残っている場合です。
Session は DBAPI トランザクションのコンテキスト内でのみデータベースに対して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
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 として知られている振る舞いを特徴としています。これは Session が Session で何らかの作業が行われるとすぐに、内部的に自身が”トランザクション”状態にあるとみなすことを示しています。これにはオブジェクトの状態の変更に関する 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 に設定することで無効にすることができます。このパラメータを使用すると、 Session は Session.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 パラメータの影響も受けません。
その後、 Session が Engine にバインドされていると仮定すると、 Session.commit() は、実際のデータベーストランザクションが開始されていれば、そのトランザクションをコミットします。コミット後、そのトランザクションに関連付けられた Connection オブジェクトが閉じられ、その基礎となるDBAPI接続が、 Session がバインドされている Engine に関連付けられた接続プールに released されます。
(例えば Partitioning Strategies で説明されているように)複数のエンジンにバインドされた Session の場合、コミットされている”論理”トランザクション内で動作している Engine/Connection ごとに同じCOMMITステップが実行されます。これらのデータベーストランザクションは、 two-phase features が有効になっていない限り、相互に調整されません。
Session を Connection に直接バインドすることで、他の接続相互作用パターンも利用できます。この場合、外部で管理されたトランザクションが存在すると想定され、実際のCOMMITは自動的には発行されません。このパターンの背景については Joining a Session into an External Transaction (such as for test suites) を参照してください。
最後に、 Session 内のすべてのオブジェクトは、トランザクションがクローズされたときに expired になります。これは、インスタンスが次にアクセスされたときに、属性アクセスかSELECTの結果に存在することによって、最新の状態を受け取るようにするためです。この動作は Session.expire_on_commit フラグで制御できます。このフラグは、この動作が望ましくない場合には False に設定できます。
See also
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
Closing¶
Session.close() メソッドは Session.expunge_all() を発行して、ORMマップされたすべてのオブジェクトをセッションから削除し、 releases して、それがバインドされている Engine オブジェクトからすべてのトランザクション/接続リソースを解放します。接続が接続プールに戻されると、トランザクション状態もロールバックされます。
デフォルトでは、 Session が閉じられると、基本的には最初に構築されたときと同じ元の状態になり、 再び使用することができます 。この意味で、 Session.close() メソッドは、”データベースを閉じる”メソッドというよりも、クリーンな状態に戻す”リセット”のようなものです。この操作モードでは、メソッド Session.reset() は Session.close() のエイリアスであり、同じように動作します。
Session.close() のデフォルトの動作は、パラメータ Session.close_resets_only を False に設定することで変更できます。これは、メソッド Session.close() が呼び出された後は、 Session を再利用できないことを示します。この操作モードでは、 Session.reset() メソッドはセッションの複数回の”リセット”を許可し、 Session.close_resets_only が True に設定されている場合の 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 automaticallyChanged 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