ORMの仕組み — プログラムのメモと、DBの1行を結ぶ
オブジェクトと、テーブルの行を結ぶ
まずは、1行を保存して読み戻す
Python 3の新しい練習用環境へSQLAlchemy 2.0系を追加します。既存プロジェクトとは別の仮想環境を用意してください。
python -m venv .venv
以下のpythonは、WindowsのPowerShellなら.\.venv\Scripts\python.exe、macOS/Linuxなら.venv/bin/pythonに置き換えます。
python -m pip install "SQLAlchemy>=2.0,<2.1"
python orm-demo.py
orm-demo.pyへ次を保存します。DBはメモリ内で、既存のDBへ接続しません。
from sqlalchemy import create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
engine = create_engine("sqlite://", echo=True)
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(User(name="ひよこ"))
session.commit()
user = session.scalars(select(User)).one()
print(user.id, user.name)
SQL等のログの後で1 ひよこと表示されます。echo=TrueのログでCREATE TABLE、INSERT、SELECT等を見てみましょう。オブジェクトで書いても、SQLはなくなりません。
対応と保存の段階を見る
この例ではUserクラスがusersテーブル、nameが列、保存されたUserが1行に対応します。他の対応方法もあるため、すべてのクラスが必ず1テーブルになるとは限りません。
SQLAlchemyのaddはSessionへ追加する操作です。flushは変更をDBへ送り、commitはトランザクションを確定します。問い合わせ前に自動でflushされることもあります。エラー時のrollbackやSessionの終了を含め、確定する場所を意識しましょう。
もう少し詳しく:関連の取得とN+1
一覧を1回で読み、各N件の関連を1回ずつ読むなら、合計N+1回のSELECTになります。JOINで取る方式、selectinload等でINを使ってまとめる方式などがあります。件数による分割や複合キー、行数の増加等の条件があるので、「eager loadingなら必ず2回」で覚えません。
TypeORMはActive RecordとData Mapperの両方を使えます。設計パターンを製品名と1対1で固定せず、モデルの責任・保存を分ける必要・チームの理解で選びます。生SQLや一括操作でも、値はパラメーターで渡し、許す操作と対象を確かめます。
🐧 ペンギン先生のまとめ:「ORM」って出てきたら「プログラムのオブジェクトと、DBのテーブルを結ぶ道具」と思えばだいたいOK!
SQLの基本はSQL入門、道具の比較はPrismaとDrizzleへ進めます。
参考資料
- SQLAlchemy:ORM Quick Start — クラス・列・SQLite・Session・select
- SQLAlchemy:Data Manipulation — add・flush・commit・自動flush
- SQLAlchemy:Relationship Loading — lazy/eager・N+1・IN取得と条件
- TypeORM:Active Record vs Data Mapper — 両パターンの対応と責務の違い