はじめに
「SQLAlchemyを使いたいけど、EngineとかSessionとか、何がなんだかわからない…」
そんな悩みを持っている方も多いのではないでしょうか。SQLAlchemyはPythonで最も人気のあるORMライブラリですが、独自の概念が多く、最初はとっつきにくく感じるかもしれません。
この記事では、SQLAlchemyの仕組みと構造を中心に解説します。「なぜそう動くのか」を理解することで、実際のコードを書くときにも迷わなくなりますよ。
対象読者
- Pythonの基礎知識がある方
- SQLAlchemyをこれから学びたい方
- 「とりあえず動くコード」ではなく、仕組みを理解したい方
前提知識
- Pythonの基本文法(クラス、関数など)
- データベースの基礎概念(テーブル、レコード、SQLとは何か程度)
SQLAlchemyとは?
SQLAlchemyは、Pythonでデータベースを操作するためのライブラリです。2006年から開発が続いており、Pythonエコシステムにおいて最も成熟したORMの一つとして知られています。
主な特徴
| 特徴 | 説明 |
|---|---|
| ORM機能 | PythonオブジェクトとDBテーブルを対応付け |
| Pythonic | 生のSQLではなくPythonコードでDB操作 |
| DB非依存 | MySQL、PostgreSQL、SQLite等を同じコードで操作可能 |
| 柔軟性 | 低レベル(SQL直書き)から高レベル(ORM)まで対応 |
なぜSQLAlchemyを使うのか?
生のSQLを直接書くこともできますが、SQLAlchemyを使うことで以下のメリットがあります。
# データベースごとに微妙に文法が違う
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) # MySQL
cursor.execute("SELECT * FROM users WHERE id = $1", (user_id,)) # PostgreSQL
# どのデータベースでも同じコード
result = session.execute(select(User).where(User.id == user_id))
データベースを変更しても、コードの書き換えが最小限で済むのは大きなメリットですね。
SQLAlchemyの2層構造:CoreとORM
SQLAlchemyを理解する上で最も重要なのが、CoreとORMの2層構造です。これを理解すれば、SQLAlchemyの全体像が見えてきます。
SQLAlchemy Coreとは
Coreは、SQLAlchemyの基盤となる低レベルAPIです。
主な役割:
- データベース接続の管理(Engine)
- SQLステートメントの構築(select, insert, update, delete)
- トランザクション管理
Coreは「SQLをPythonで表現するためのツールキット」と考えるとわかりやすいでしょう。
from sqlalchemy import create_engine, text
engine = create_engine("sqlite:///example.db")
with engine.connect() as conn:
result = conn.execute(text("SELECT * FROM users"))
for row in result:
print(row)
SQLAlchemy ORMとは
ORM(Object Relational Mapper)は、Coreの上に構築された高レベルAPIです。
主な役割:
- PythonクラスとDBテーブルのマッピング
- オブジェクトの永続化(保存・更新・削除)
- リレーションシップの管理(1対多、多対多など)
ORMを使うと、データベースのレコードをPythonオブジェクトとして扱えます。
# Userクラスがusersテーブルに対応
user = User(name="田中太郎", email="tanaka@example.com")
session.add(user)
session.commit()
# オブジェクトとして操作できる
print(user.name) # "田中太郎"
CoreとORMの使い分け
| ユースケース | 推奨 |
|---|---|
| Webアプリケーション開発 | ORM |
| データ分析・バッチ処理 | Core または 両方 |
| 複雑なJOINや集計クエリ | Core |
| 単純なCRUD操作 | ORM |
SQLAlchemy 2.0では、ORMでもCoreスタイルのselect()構文が使えるようになり、両者の統合が進んでいます。どちらか一方に縛られる必要はありません。
主要コンポーネントの役割
SQLAlchemyには、いくつかの重要なコンポーネントがあります。それぞれの役割を見ていきましょう。
Engine(エンジン)
Engineは、データベースへの接続の「入り口」となるオブジェクトです。
from sqlalchemy import create_engine
# 接続文字列でデータベースを指定
engine = create_engine("postgresql://user:password@localhost/mydb")
コネクションプーリング
Engineの重要な機能の一つがコネクションプーリングです。
コネクションプーリングとは?
データベース接続の作成は重い処理です。毎回接続を作り直すのではなく、接続を「プール」(貯めておく場所)で管理し、再利用することでパフォーマンスを向上させます。Engineはこれを自動で行ってくれます。
Engineは「接続」ではない
よくある誤解ですが、Engineは接続そのものではありません。Engineは接続を管理するファクトリであり、メソッド実行時に随時コネクションの取得・解放を行います。
# Engineは1つだけ作成(アプリ起動時)
engine = create_engine("sqlite:///example.db")
# 実際の接続はメソッド呼び出し時に取得される
with engine.connect() as connection:
# ここで初めて接続が使われる
result = connection.execute(text("SELECT 1"))
# ここで接続がプールに返却される
Session(セッション)
Sessionは、ORMを使う際の「作業領域」です。オブジェクトの変更を追跡し、データベースとの同期を管理します。
from sqlalchemy.orm import sessionmaker
# Sessionファクトリを作成
Session = sessionmaker(bind=engine)
# Sessionインスタンスを作成
with Session() as session:
user = session.get(User, 1)
user.name = "新しい名前"
session.commit()
Unit of Workパターン
SessionはUnit of Work(作業単位)パターンを実装しています。
- セッション内での変更はメモリ上で追跡される
-
commit()を呼ぶまでデータベースには反映されない - エラー時は
rollback()で全ての変更を取り消せる
Identity Map(同一性マップ)
SessionにはIdentity Mapという仕組みがあります。これは、同じ主キーを持つレコードに対して、常に同じPythonオブジェクトを返す機能です。
# 同じIDで2回取得しても、同じオブジェクトが返される
user1 = session.get(User, 1)
user2 = session.get(User, 1)
print(user1 is user2) # True(同一オブジェクト)
これにより、同じセッション内でデータの不整合が起きることを防げます。
Model(モデル)
Modelは、データベースのテーブルをPythonクラスとして表現したものです。
SQLAlchemy 2.0の書き方
SQLAlchemy 2.0では、型アノテーションを活用した新しい書き方が推奨されています。
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
# ベースクラスの定義
class Base(DeclarativeBase):
pass
# モデルの定義
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
email: Mapped[str] = mapped_column(String(255), unique=True)
is_active: Mapped[bool] = mapped_column(default=True)
| 要素 | 説明 |
|---|---|
DeclarativeBase |
全モデルの基底クラス |
__tablename__ |
対応するテーブル名 |
Mapped[型] |
カラムの型を明示 |
mapped_column() |
カラムの定義 |
従来のdeclarative_base()やColumn()も使えますが、SQLAlchemy 2.0では上記の書き方が推奨されています。新規プロジェクトでは新しい書き方を使いましょう。
データの流れを図解で理解する
ここまで学んだコンポーネントが、実際にどう連携するのか見てみましょう。
実際のデータ操作の流れ
データ取得(SELECT)の例:
# 1. Sessionを通じてクエリを実行
with Session() as session:
# 2. select文を構築
stmt = select(User).where(User.id == 1)
# 3. Session経由でEngineがDBに問い合わせ
user = session.execute(stmt).scalar_one()
# 4. 結果がPythonオブジェクトとして返される
print(user.name)
データ作成(INSERT)の例:
with Session() as session:
# 1. モデルのインスタンスを作成
new_user = User(name="山田花子", email="yamada@example.com")
# 2. Sessionに追加(まだDBには保存されない)
session.add(new_user)
# 3. commit()でDBに反映
session.commit()
# 4. 自動採番されたIDも取得できる
print(new_user.id)
SQLAlchemy 1.x vs 2.0:何が変わった?
SQLAlchemy 2.0は2023年にリリースされた大きなアップデートです。主な変更点を押さえておきましょう。
クエリの書き方
1.x系(レガシー):
# session.query()を使う書き方(非推奨)
users = session.query(User).filter(User.is_active == True).all()
2.0系(推奨):
# select()を使う書き方(推奨)
from sqlalchemy import select
stmt = select(User).where(User.is_active == True)
users = session.execute(stmt).scalars().all()
モデルの定義
1.x系:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(100))
2.0系:
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100))
主な改善点
| 項目 | 説明 |
|---|---|
| 一貫性 | CoreとORMでクエリ構文が統一 |
| 型安全性 | 型アノテーションによるIDEサポート向上 |
| 明示性 | 何が起きているか分かりやすい設計 |
| パフォーマンス | 内部処理の最適化 |
新規プロジェクトでは必ずSQLAlchemy 2.0の書き方を使いましょう。1.x系の書き方はレガシーとされており、将来的に非推奨となる可能性があります。
まとめ
この記事では、SQLAlchemyの基本的な仕組みと概念を解説しました。
要点の振り返り
-
2層構造
- Core:低レベルAPI、SQL構築と接続管理
- ORM:高レベルAPI、オブジェクトとテーブルのマッピング
-
主要コンポーネント
- Engine:接続の入り口、コネクションプーリング
- Session:作業単位、変更の追跡と永続化
- Model:テーブルをPythonクラスで表現
-
SQLAlchemy 2.0
-
select()による統一されたクエリ構文 - 型アノテーションを活用した新しいモデル定義
-
参考リンク