閉じる

Python×SQLAlchemyで始めるORM入門: モデル定義からCRUDまで

データベースをPythonのオブジェクトとして扱えるのがORMです。

本記事ではSQLAlchemyを使って「モデルの定義」から「CRUDの基本操作」までを一気通貫で体験します。

初心者の方でも手を動かしながら理解できるよう、最小構成と実行例を丁寧に解説します。

SQLAlchemyとは(ORMの基礎)

ORMの仕組みとメリット

ORM(Object-Relational Mapping)はテーブルをクラスに、行をインスタンスに対応付けて扱う仕組みです。

これによりアプリケーションからはuser.nameのように属性へアクセスするだけで、裏側では適切なSQLが発行されます。

SQLの細かな文法に気を取られず、アプリ本来のロジックに集中できるのが大きな利点です。

またORMは保守性と移植性の向上にも寄与します。

モデルクラスやリレーションを中心に設計を統一でき、DB固有の違いはドライバやダイアレクトに吸収されます。

アプリが大きくなってもコードの見通しを保ちやすいことが実務でも評価されています。

SQLAlchemyの特徴と構成

SQLAlchemyはPythonで定番のORMで、「Core」と「ORM」という2層構造を持っています。

ORMはクラス中心の操作を、CoreはSQL式を組み立てる低レイヤを提供します。

必要に応じてORMとCoreを使い分けられる柔軟さが特長です。

主な構成要素は次の通りです。

  • ORMレイヤ: Declarative(モデル定義)、Session(トランザクション管理)、Query API(取得・更新)
  • Coreレイヤ: SQL Expression Language(型安全なSQL組み立て)
  • Dialect: SQLite、PostgreSQL、MySQLなど各DB向けアダプタ

SQLAlchemy 2系では型ヒントを活用した記述が推奨され、モダンなPythonに馴染む書き方になっています。

1系のスタイルとは異なる点があるため、新規学習では2系スタイルを前提にしましょう

初心者が押さえる用語(モデル/セッション/クエリ)

学び始めで最初に理解しておくと楽になる3つの用語があります。

モデル、セッション、クエリの関係を意識すると全体像がつかみやすいです。

モデルはテーブルを表すPythonクラスで、カラムをクラス属性として宣言します。

セッションはDBとの対話を担う単位で、トランザクション境界を作ります。

クエリはデータ取得のための問い合わせで、ORMではSQLに相当する操作を記述します。

下表は用語の超要約です。

用語役割
モデル(Model)テーブルの設計図をクラスで表現class User(Base): ...
セッション(Session)DB接続とトランザクションの単位with Session(engine) as s: ...
クエリ(Query)取得や絞り込みの指示select(User).where(User.id==1)

実際の処理は「セッション内でモデルを使ってクエリする」流れになります。

基本形はwith Session(engine) as session: ...です。

準備とインストール(Python×SQLAlchemy)

必要環境(Python/SQLite)

最小構成ではPythonとSQLiteだけで始められます。

Python 3.8以上とSQLiteは標準で利用できるため追加セットアップが少ないのが魅力です。

Windows、macOS、LinuxいずれもOKです。

バージョン確認は次の通りです。

コマンドの結果で環境を確認してから進みましょう。

Shell
python --version
python -c "import sqlite3; print('sqlite3 version:', sqlite3.sqlite_version)"
実行結果
Python 3.11.6
sqlite3 version: 3.42.0

SQLAlchemyのインストール(pip)

インストールはpipで完了します。

2系を明示して入れるとサンプルと表記が一致します。

Shell
python -m pip install "SQLAlchemy>=2.0"
実行結果
Successfully installed SQLAlchemy-2.0.x ...

特別なドライバは不要で、SQLiteであれば追加の依存はありません。

他DBを使う時はpsycopg(PostgreSQL)やPyMySQL(MySQL)などを別途導入します。

最小プロジェクト構成

最初は小さく始めるのが理解の近道です。

以下の3ファイル構成で進めます。

text
orm-tutorial/
├─ database.py     # EngineとBaseの定義
├─ models.py       # モデル定義(Userなど)
└─ crud_demo.py    # CRUDの実行スクリプト(実行例つき)
ファイル役割
database.pyエンジン作成とDeclarative Baseの用意
models.pyテーブルに対応するモデルクラスを定義
crud_demo.pyセッションを開いてCRUDを実行し、動作を確認

躓きやすいのはファイルの役割分担です。

最初はこの最小構成を真似てから、必要に応じて分割や拡張を行うと安全です。

モデル定義とテーブル作成(SQLAlchemy)

Declarative Baseの用意

まずはORMの土台となるBaseクラスを用意します。

全てのモデルはこのBaseを継承します。

加えてSQLiteに接続するエンジンを作成します。

Python
# database.py
# SQLAlchemy 2系スタイルでのBaseとEngine定義

from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase

DB_URL = "sqlite:///./orm_tutorial.db"  # カレントディレクトリにDBファイルを作成

class Base(DeclarativeBase):
    """全モデルの共通基底クラス"""
    pass

# echo=Trueで発行SQLをログ出力(学習時は可視化が便利)
engine = create_engine(DB_URL, echo=True, future=True)

echo=Trueを有効にすると生成されたSQLが見えるため、ORMの挙動理解に役立ちます。

学習時だけ有効化し、実運用では無効化するのがおすすめです。

モデルクラスの定義(Column/型)

次にテーブルに対応するモデルクラスを宣言します。

属性に型と制約を与えることで、テーブル設計をコードで表現します。

Python
# models.py
# Userテーブルを表すモデルを定義(最小限のカラム構成)

from datetime import datetime
from sqlalchemy import String, DateTime
from sqlalchemy.orm import Mapped, mapped_column
from database import Base  # 同一ディレクトリ想定

class User(Base):
    __tablename__ = "users"

    # 主キー。自動採番(INT AUTOINCREMENT相当)
    id: Mapped[int] = mapped_column(primary_key=True)

    # 必須項目。最大50文字
    name: Mapped[str] = mapped_column(String(50), nullable=False)

    # 一意制約とインデックスを付与
    email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)

    # 作成日時。Python側で挿入時に自動設定
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)

    def __repr__(self) -> str:
        return f"User(id={self.id}, name={self.name!r}, email={self.email!r})"

型ヒント(Mapped[T])とmapped_columnの組み合わせが2系の基本です。

これによりエディタ補完が効き、静的解析にも強くなります。

エンジン作成と接続(SQLite)

エンジンはDBとの接続やドライバ管理を担います。

SQLiteではURLにファイルパスを指定するだけで使い始められます。

Python
# 接続テストの例(任意)
# 既存のdatabase.pyのengineを流用して簡単な生SQLを実行
from database import engine

with engine.begin() as conn:
    result = conn.exec_driver_sql("SELECT 1")
    print("Connection OK:", result.scalar())
実行結果
Connection OK: 1

ORMを使っていても低レイヤのCore APIに降りられるのがSQLAlchemyの強みです。

必要に応じて併用できます。

テーブル作成(create_all)

モデル定義が揃ったらテーブルを作成します。

Base.metadata.create_allがマイグレーションの初期手段として有効です。

Python
# init_db.py
# モデルに基づいてテーブルを作成するワンショットスクリプト

from database import engine
from models import Base

Base.metadata.create_all(bind=engine)
print("Tables created!")
実行結果
# echo=Trueのため一部のSQLログが出力されます(例)
BEGIN (implicit)
PRAGMA main.table_info("users")
PRAGMA temp.table_info("users")
CREATE TABLE users (
    id INTEGER NOT NULL,
    name VARCHAR(50) NOT NULL,
    email VARCHAR(255) NOT NULL,
    created_at DATETIME NOT NULL,
    PRIMARY KEY (id),
    UNIQUE (email)
)
COMMIT
Tables created!

create_allは「ないテーブルだけ作る」ため、繰り返し実行しても基本的に安全です。

スキーマ変更の管理は将来的にAlembicの導入を検討してください。

CRUDの基本操作(ORM)

セッションの作成と終了(Session)

実データの操作はセッションを通じて行います。

with文でスコープを区切り、自動的にクローズするのが安全です。

Python
# セッション基本形(例)
from sqlalchemy.orm import Session
from database import engine

with Session(engine) as session:
    # session.add(...) / session.execute(...) などを記述
    # 必要に応じてsession.commit()
    pass  # このブロックを出ると自動的にセッションが閉じます

コミットしない限り変更は確定されない点に注意してください。

エラー時はsession.rollback()で元に戻せます。

Create(追加とコミット)

新規レコードはモデルのインスタンスを作りsession.addで登録、session.commitで確定します。

コミット後は主キーなどが自動採番されます。

Read(取得/フィルタ/並び替え)

取得はselect(User)を使い、session.scalarsでモデルの配列として受け取るのが2系の基本です。

whereで絞り込み、order_byで並び替えます。

Update(更新とコミット)

取得したインスタンスの属性を書き換えてcommitすれば更新されます。

commit後にrefreshで最新状態を反映すると確実です。

Delete(削除とコミット)

削除はsession.delete(obj)で対象を登録し、commitで確定します。

参照整合性には注意しましょう。

以下はCRUDをひと通り実行する最小サンプルです。

1ファイルで完結するため、そのまま実行できます。

Python
# crud_demo.py
# 1ファイルでモデル定義からCRUD、例外処理までを実演します。

from __future__ import annotations

import os
from datetime import datetime

from sqlalchemy import String, DateTime, select, create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session

DB_FILE = "orm_tutorial.db"
DB_URL = f"sqlite:///./{DB_FILE}"

# 学習用に毎回クリーンな状態にする(既存DBを削除)
if os.path.exists(DB_FILE):
    os.remove(DB_FILE)

# Declarative Baseの用意
class Base(DeclarativeBase):
    pass

# モデル定義(Userテーブル)
class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)  # 主キー(INT, AUTOINCREMENT)
    name: Mapped[str] = mapped_column(String(50), nullable=False)  # 必須の文字列
    email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)  # 一意制約つき
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)  # デフォルト現在時刻

    def __repr__(self) -> str:
        return f"User(id={self.id}, name={self.name!r}, email={self.email!r})"

# Engine作成。学習ではecho=TrueでSQLログを見るのも有効ですが、出力を見やすくするためFalseにしています。
engine = create_engine(DB_URL, echo=False, future=True)

# テーブル作成
Base.metadata.create_all(bind=engine)

# セッションを開いてCRUDを順番に実行
with Session(engine) as session:
    # === Create: レコード追加 ===
    alice = User(name="Alice", email="alice@example.com")
    bob = User(name="Bob", email="bob@example.com")
    charlie = User(name="Charlie", email="charlie@example.com")

    # まとめて追加してコミット
    session.add_all([alice, bob, charlie])
    session.commit()  # ここでidが採番される

    print("After insert:", [alice, bob, charlie])

    # === Read: 全件と絞り込み ===
    all_users = session.scalars(select(User).order_by(User.id)).all()
    print("All users:", all_users)

    # 名前に'a'を含むユーザ( SQLiteのLIKEはASCIIでは大文字小文字を区別しないことが多い )
    users_with_a = session.scalars(
        select(User).where(User.name.like("%a%")).order_by(User.created_at.desc())
    ).all()
    print("Name contains 'a':", users_with_a)

    # 個別取得(getは主キー検索に最適)
    u2 = session.get(User, 2)
    print("Get id=2:", u2)

    # === Update: 値の変更とコミット ===
    u2.email = "bobby@example.com"  # 値を変更
    session.commit()                # 永続化
    session.refresh(u2)             # DB上の最新値でオブジェクトを更新
    print("Updated user id=2:", u2)

    # === Delete: 削除とコミット ===
    to_delete = session.get(User, 3)
    session.delete(to_delete)  # 削除を登録
    session.commit()           # 確定
    remaining = session.scalars(select(User).order_by(User.id)).all()
    print("Deleted id=3. Remaining:", remaining)

    # === 例外処理: 一意制約違反を誘発してロールバック ===
    try:
        # すでにalice@example.comが存在するため一意制約違反になる
        session.add(User(name="Eve", email="alice@example.com"))
        session.commit()
    except Exception as e:
        session.rollback()  # 直前のトランザクションを取り消す
        print("Rollback due to error:", type(e).__name__)
実行結果
After insert: [User(id=1, name='Alice', email='alice@example.com'), User(id=2, name='Bob', email='bob@example.com'), User(id=3, name='Charlie', email='charlie@example.com')]
All users: [User(id=1, name='Alice', email='alice@example.com'), User(id=2, name='Bob', email='bob@example.com'), User(id=3, name='Charlie', email='charlie@example.com')]
Name contains 'a': [User(id=3, name='Charlie', email='charlie@example.com'), User(id=1, name='Alice', email='alice@example.com')]
Get id=2: User(id=2, name='Bob', email='bob@example.com')
Updated user id=2: User(id=2, name='Bob', email='bobby@example.com')
Deleted id=3. Remaining: [User(id=1, name='Alice', email='alice@example.com'), User(id=2, name='Bob', email='bobby@example.com')]
Rollback due to error: IntegrityError

セッションはトランザクションの境界です。

複数の操作を1つのトランザクションにまとめたい時は、コミットのタイミングを意識してください。

例外処理とロールバック

DB操作では一意制約違反や外部キー制約違反などの例外に備える必要があります。

典型的にはtry/exceptIntegrityErrorSQLAlchemyErrorを捕捉し、session.rollback()で直前の変更を元に戻します。

Python
# 例外処理のテンプレート(部分例)
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from sqlalchemy.orm import Session
from database import engine
from models import User

with Session(engine) as session:
    try:
        session.add(User(name="Duplicate", email="alice@example.com"))
        session.commit()
    except IntegrityError as ie:
        session.rollback()
        print("Unique constraint violated:", ie.orig)  # DBドライバ由来の例外メッセージ
    except SQLAlchemyError as se:
        session.rollback()
        print("Database error:", se)
    except Exception as e:
        session.rollback()
        print("Unexpected error:", e)

例外が起きたら必ずrollbackという習慣をつけると、トランザクションが中途半端な状態で残る事故を防げます。

commitに成功するまではDBの状態は確定していない点を意識して設計しましょう。

まとめ

本記事では、SQLAlchemyを使ったORMの基本を、モデル定義からテーブル作成、そしてCRUDと例外処理まで一通り解説しました。

モデルはクラス、行はインスタンス、セッションはトランザクションの単位という関係を押さえると、「オブジェクトとしてDBを扱う」感覚が自然に身につきます。

小さなスクリプトで動作を確認したら、プロジェクト構成を整え、必要に応じてAlembicなどのマイグレーションツールや他DBドライバの導入に進むと良いでしょう。

まずは本稿のサンプルを手元で実行し、ORMの手触りを体験することから始めてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!