sqlalchemy
ORMとは何か?
SQL Alchemyの基本的な機能
主に注目すべき機能は以下2つonigiri.w2.icon
1. ORM(Object Relational Mapping)
SQL Alchemyの基本キーワード
Engine:DBとの接続関連のマスター。グローバルで1インスタンスだけ存在すればいい。
Connection:DBを操作する時の1コネクションを管理するオブジェクト。
Pool:コネクションプール。普段ユーザーが意識することはない。
Dialect:SQLの種類による方言の違いを吸収するオブジェクト。普段ユーザーが意識することは少ない。
Session:ORMを利用する際に登場するオブジェクト。中でPoolなどのオブジェクトを内包してる。
SQL AlchemyがDB操作する際の基盤構造
https://scrapbox.io/files/635d0e265d35240022030b04.png
DBAPIはpython標準で定められたpythonでDB操作するライブラリの仕様・取り決め。 最初は詳細をちゃんと理解しなくてもいい。「ふーん」くらいに抑えておけばOK。
SQL Alchemyの基本中の操作
Engineインスタンスを1つ作成して、DB操作のための基盤を作る
このEngineがSQLAlchemyを利用する際の唯一無二の基地となる。
EngineがDBとのコネクションをクライアントに提供してくれたりする。
SQLAlchemyにおける全てのDB操作の出発点は、このEngineから始まる。
code: sample.py
from sqlalchemy import create_engine
engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")
「コネクションプーリング戦略」「プールサイズ」などもこの時行うことになる。
encoding, echoなどのオプションも設定できる。
Engineは1システムに対して必ず1つだけ用意して使い回すこと。2つ以上作ってはいけない。
ORMを利用しない場合の基本操作
基本的には...
engine.connect()でConnectionオブジェクトを取得。
そのConnectionオブジェクトを用いて、DB操作を実施する。
code: sample.py
with engine.connect() as connection:
result = connection.execute("select username from users")
for row in result:
connect()で返されたConnectionオブジェクトを用いてDB操作を行なっていく。
トランザクションも使える
code: sample.py
with engine.connect() as connection:
with connection.begin():
r1 = connection.execute(table1.select())
connection.execute(table1.insert(), {"col1": 7, "col2": "this is some data"})
もしconnection.begin()内でエラーが発生したら、rollback()が実行される。
ORM対象となるオブジェクトを作る
declarative_baseを用いて生成したBaseクラスを継承したオブジェクトを作成する
code: sample.py
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
Baseを継承した永続化したいオブジェクトを作成
code: sample.py
class User(Base):
"""
ユーザモデル
"""
__tablename__ = 'users'
__table_args__ = {
'comment': 'ユーザー情報のマスターテーブル'
}
id = Column('id', Integer, primary_key=True, autoincrement=True)
name = Column('name', String(200))
age = Column('age', Integer)
email = Column('email', String(100))
Baseを継承することで、このクラスをORMの操作対象にできる。
ORM対象オブジェクトをSessionでDB連携する
Sessionクラスを最初に定義しておく
code: sample.py
Session = sessionmaker(bind=engine)
sessionmakerについては以下参考
ORM操作時にSessionをインスタンス化して利用する
code: sample.py
sesion = Session()
user = User(id=..., name=..., ...)
# 追加/更新
session.add(user)
session.commit()
# 削除
session.delete(user)
session.commit()
注意.icon SessionのDB操作は内部で、Engineから配布されたConnectionを利用して実現してる
SQL Alchemyの覚えておきたいTips
DBとのコネクションの確立タイミングの遅延
DBとの新規コネクション確立は、engine.connect(), engine.execute()メソッドが実行されるタイミングで行われる。
なお、コネクションプールに待機中のものがある場合は、そのコネクションが利用されるだろう。
Sessionのトランザクションについて整理
Sessionでトランザクションを貼る方法は主に2つ存在する
1. commit()によって、自動的にトランザクション境界が決まるパターン
commit()を実行した時点、そのDB状態がセーブされてしまう。
code: sample.py
session = Session()
user = User(...)
session.add(user)
session.commit()
# ----- この時点でトランザクションが1度固まってる。
try:
session.add(user2)
session.flush()
except ...:
session.rollback()
# もしここでロールバックしたとしても、前のsession.commit()の結果は戻らない
2. begin()で、明示的にトランザクション境界を決めるパターン
begin()のコンテキスト内をトランザクション境界にしてしまう
もしエラーが発生したら、ロールバックする。
エラーが発生しなかったら、コミットする。
code: sample.py
session = Session()
user = User(...)
with session.begin():
session.add(user)
onigiri.w2.iconとしては明示的に決めたい派ではあるので「2.」を使いたいなとは思う。
WebサービスでSQLAlchemyを利用する場合は、「scoped_session」を使おう
Webサービスでのリクエスト内で別々のSessionを使うのは非効率。
なので、リクエスト中は常に同じSessionを使えるようにしたい。
それを実現するのがscoped_session。
リクエスト毎にSessionをシングルトンにしてくれるので、毎回Sessionを作るのを削減できる。
code: sample.py
from sqlalchemy.orm import sessionmaker, scoped_session
Session = scoped_session(
sessionmaker(bind=engine)
)
参考:
Baseを使って、継承オブジェクトのテーブルを一括作成できる
code: sample.py
engine = create_engine("mysql+pymysql://<user>:<pass>@<host>/<dbname>?charset=utf8")
Base.metadata.create_all(bind=engine)
調査ログ
hr.icon
データ接続周りの話
code: settings.py
from sqlalchemy import create_engine
# 接続先DBの設定
DATABASE = 'sqlite:///db.sqlite3'
# Engine の作成
Engine = create_engine(
DATABASE,
encoding="utf-8",
echo=False
)
ここがまず基本中の基本ぽいなonigiri.w2.icon
接続先DBのURLを作って、そこに対するengineを作成すると。
q.icon URLはSQLAlchemy特有のものかな?
a.icon 多分違う。各DBで指定されてるURLを記述するはず。
重要.icon engineの仕組みについて調べてみよう
データベースを操作するには、sessionの仕組みを使うんだと
code: sample.py
session = Session(
autocommit = False,
autoflush = True,
bind = Engine
)
user = User()
user.name = '一郎'
session.add(user)
session.commit()
WebアプリケーションでSessionを利用するなら、scoped_sessionの仕組みを使った方がいいんだとよ
重要.icon sessionの仕組みについて調べておこう
ORMのモデルを作成するためには、必ずBaseを使え
このBaseを使って、SQLAlchemyにORMのモデルを認識させるのだ。
EngineはSQLAlchemyにおける出発点
https://scrapbox.io/files/635d0e265d35240022030b04.png
q.icon ふむふむ、PoolとDialectは何者やろうか?なぜ並列になってる?
a.icon
Dialectは、DBAPIの実装版のこと。MySQL, SQLiteなど、それぞれのDBに合わせた実装がなされてる。
もちろん、DBAPIの仕様に則った上で。
そもそもdialectの和訳は方言。
Pool
これはコネクションプーリングのこと
コネクションプーリングが返すコネクションオブジェクトはDBAPIの仕様に則って実装されてるんだとよ。
というか...
Dialectオブジェクトが返ってるかも???
指定された方言に合わせたdialectオブジェクトが返ってる可能性ある
q.iconDBAPIって何???
エンジンの作成は、create_engine()を呼び出すだけ
code: sample.py
from sqlalchemy import create_engine
engine = create_engine("postgresql://scott:tiger@localhost:5432/mydatabase")
メソッドが呼び出されるか、このメソッドに依存する操作が呼び出される まで、およびその基礎となるは 最初の実際の DBAPI 接続を確立しないことに注意してください。
ここめっちゃ重要やonigiri.w2.icon
URLの指定方法
postgresql
code: sample.py
# default
engine = create_engine("postgresql://scott:tiger@localhost/mydatabase")
# psycopg2
engine = create_engine("postgresql+psycopg2://scott:tiger@localhost/mydatabase")
# pg8000
engine = create_engine("postgresql+pg8000://scott:tiger@localhost/mydatabase")
なるほどね、pythonのdriverを指定できるのね。postgresqlならpsycopg2 or pg8000をサポートしていると...onigiri.w2.icon
なお、エンジンは実際にはDBへの接続そのものではありません(別にConnectionというクラスがある)。 内部でコネクションプーリングが行われており、エンジンのメソッドを実行するたびに随時コネクションの取得・解放を行なっています(Connection Pooling)。
ホイホイなるほどonigiri.w2.icon
コネクションプーリングから取得したりするのね。そして足りなければ作成的なことをすると、なるほどonigiri.w2.icon
接続プールは、アプリケーションが同時に使用する可能性のある接続の総数を管理するだけでなく、効率的に再利用するために長時間実行される接続をメモリ内に維持するために使用される標準的な手法です。
特にサーバー側の Web アプリケーションの場合、接続プールは、要求間で再利用されるアクティブなデータベース接続の「プール」をメモリ内に維持するための標準的な方法です。
todo_done.icon コネクションプーリングについて軽く調べよう
多分、コネクションプールの話はめっちゃ奥深いと思われるonigiri.w2.icon
コネクションプーリングの戦略は、create_engine()で指定するのよonigiri.w2.icon
poolclassで指定するんだってよ。
基本的にはQueuePoolを使っておけば良さそうやonigiri.w2.icon
コネクション数を適度にプーリングしてくれる感じで、制限超えたらそれ以上の接続を弾くようになる。
そしてNullPoolはプーリングしないんだとよ。
Engineこのセクションでは、 、 Connection、および関連オブジェクトの直接的な使用法について詳しく説明します。SQLAlchemy ORM を使用する場合、これらのオブジェクトは通常アクセスされないことに注意することが重要です。代わりに、Sessionオブジェクトはデータベースへのインターフェイスとして使用されます。ただし、ORM の上位レベルの管理サービスが関与することなく、テキスト SQL ステートメントや SQL 式構造を直接使用するように構築されたアプリケーションの場合、Engineと Connectionが王様 (そして女王様?) です。読み進めてください。
言い換えると...onigiri.w2.icon
ORMを使う場合は、Sessionで操作する。
SQLを直接使う場合は、Engine, Connectionで操作する。
ここ重要なところ
create_engine()、特定のデータベース URL ごとに 1 回で、単一のアプリケーション プロセスの存続期間中グローバルに保持されます
なるほど、create_engineは単一のプロセスで1回だけ実行し、グローバル変数に保持しておけとのことですonigiri.w2.icon
engine, connectionを使ったDB操作の例
code: saple.py
from sqlalchemy import text
with engine.connect() as connection:
result = connection.execute(text("select username from users"))
for row in result:
はいはい、よくありそうな形ねonigiri.w2.icon
トランザクションを作りたい場合は...
code: sample.py
with engine.connect() as connection:
with connection.begin():
r1 = connection.execute(table1.select())
connection.execute(table1.insert(), {"col1": 7, "col2": "this is some data"})
ホイホイ理解理解
境界づけられたトランザクション
code: sample.py
with session.begin():
session.add(some_object())
session.add(some_other_object())
# commits transaction at the end, or rolls back if there
# was an exception raised
上記のコンテキストの最後に、例外が発生しなかったと仮定すると、保留中のオブジェクトがデータベースにフラッシュされ、データベース トランザクションがコミットされます。上記のブロック内で例外が発生した場合、トランザクションはロールバックされます。どちらの場合も、 Sessionブロックを終了した後の上記は、後続のトランザクションで使用する準備ができています。
なるほど、例外が発生しないならコミット、発生したらロールバックできるってわけか!!onigiri.w2.icon