ORM モデルって何?まずはイメージから
「ORM モデル」は、
データベースのテーブルを「Python のクラスとして表現したもの」です。
ORM は Object Relational Mapping(オブジェクトとリレーショナルの対応付け)の略で、
「テーブルの 1 行を Python のオブジェクトとして扱えるようにする仕組み」です。
テーブル=クラス
行=インスタンス
この対応が、ORM モデルの一番大事なイメージです。
これが腹落ちすると、SQL を直接書かなくても、
Python のコードだけで INSERT / SELECT / UPDATE / DELETE が書ける意味が見えてきます。
テーブルと ORM モデルの対応を具体例で見る
素の SQL でのテーブル定義
まず、SQL だけで users テーブルを作るとします。
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
);
SQLこのテーブルは、「ユーザーの一覧」を表しています。
1 行が 1 人のユーザーです。
ここまでは、普通の SQL の世界です。
同じものを ORM モデルで書く(SQLAlchemy)
これを SQLAlchemy の ORM モデルで書くと、こうなります。
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String, nullable=False)
email = Column(String, unique=True, nullable=False)
Pythonここで起きていることを整理すると、
User クラスが users テーブルを表している。
User クラスの id, name, email が、テーブルのカラムを表している。
primary_key, unique, nullable などの制約も、クラス側に書いている。
つまり、「さっきの CREATE TABLE の情報を、Python のクラスとして書き直した」のが ORM モデルです。
ORM モデルの一番大事な対応関係
「クラス=テーブル」「インスタンス=行」をちゃんと掴む
User クラスを定義したら、次のようなコードが書けます。
user = User(name="Taro", email="taro@example.com")
Pythonこの user は、「users テーブルの 1 行になる予定のデータ」です。
まだ DB には保存されていませんが、「1 行分の情報を持ったオブジェクト」です。
セッションを使って保存すると、これが実際の行になります。
from sqlalchemy.orm import sessionmaker
engine = create_engine("sqlite:///example.db")
SessionLocal = sessionmaker(bind=engine)
Base.metadata.create_all(engine) # テーブル作成
session = SessionLocal()
user = User(name="Taro", email="taro@example.com")
session.add(user)
session.commit()
session.close()
Pythonここで、
User クラス=users テーブル
user インスタンス=users テーブルの 1 行
という対応が、実際に「DB に書き込まれる形」で動いています。
この感覚がつかめると、
「ORM モデルを触る=テーブルの行を触る」
という意識でコードを書けるようになります。
ORM モデルを使った CRUD の流れ
作成(Create)=インスタンスを作って保存する
新しいユーザーを作るときは、
Python 的には「インスタンスを作る → セッションに add → commit」です。
session = SessionLocal()
new_user = User(name="Hanako", email="hanako@example.com")
session.add(new_user)
session.commit()
session.close()
PythonSQL にすると、
INSERT INTO users (name, email)
VALUES ('Hanako', 'hanako@example.com');
SQLですが、ORM モデルを使うと、
「INSERT 文を書く」のではなく「オブジェクトを作る」という感覚になります。
読み取り(Read)=クエリでモデルを取得する
ユーザーを検索するときは、
「User をクエリする」という書き方になります。
session = SessionLocal()
user = session.query(User).filter_by(email="hanako@example.com").first()
print(user.id, user.name, user.email)
session.close()
Python戻ってくるのは dict ではなく User インスタンスです。user.name のように属性アクセスで扱えるのが、ORM モデルの気持ちよさです。
更新(Update)=属性を書き換えて commit
更新は、「オブジェクトを取ってきて、属性を書き換えて、commit」です。
session = SessionLocal()
user = session.query(User).filter_by(email="hanako@example.com").first()
user.name = "Hanako Yamada"
session.commit()
session.close()
PythonSQL なら、
UPDATE users
SET name = 'Hanako Yamada'
WHERE email = 'hanako@example.com';
SQLですが、ORM モデルでは「Python のオブジェクトの属性を書き換える」という形で表現できます。
削除(Delete)=オブジェクトを delete して commit
削除も同じ流れです。
session = SessionLocal()
user = session.query(User).filter_by(email="hanako@example.com").first()
session.delete(user)
session.commit()
session.close()
PythonSQL なら、
DELETE FROM users WHERE email = 'hanako@example.com';
SQLですが、ORM モデルでは「オブジェクトを delete する」という操作になります。
リレーション(関係)を ORM モデルで表現する
1 対多の関係をクラスで書く
ORM モデルの真価は、「テーブル同士の関係もクラスで表現できる」ことです。
例えば、User と Post(記事)の関係を考えます。
User 1 人は複数の Post を持つ(1 対多)
Post 1 件は 1 人の User に属する
これを SQL で書くと、Post に user_id 外部キーを持たせます。
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL,
user_id INTEGER NOT NULL REFERENCES users(id)
);
SQL同じものを ORM モデルで書くと、こうなります。
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
posts = relationship("Post", back_populates="author")
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String, nullable=False)
body = Column(String, nullable=False)
user_id = Column(Integer, ForeignKey("users.id"))
author = relationship("User", back_populates="posts")
Pythonここで重要なのは、
ForeignKey で「どのカラムがどのテーブルを参照しているか」を書き、
relationship で「Python 側のオブジェクト同士のつながり」を書いていることです。
リレーションを使った操作のイメージ
このモデルがあると、次のようなコードが書けます。
session = SessionLocal()
user = User(name="Taro")
post1 = Post(title="Hello", body="First post", author=user)
post2 = Post(title="Second", body="Another post", author=user)
session.add(user)
session.add_all([post1, post2])
session.commit()
session.close()
PythonPost を作るときに author=user と書いているのがポイントです。
これで post.user_id に user.id が入り、
DB 的にも「この Post はこの User に属する」という関係が作られます。
取り出すときは、こうです。
session = SessionLocal()
u = session.query(User).filter_by(name="Taro").first()
for p in u.posts:
print(p.title)
session.close()
Pythonここで u.posts は、
裏で JOIN されたクエリの結果を、
「Post オブジェクトのリスト」として見せてくれています。
JOIN の SQL を自分で書かなくても、
「オブジェクト同士の関係」として扱えるのが、ORM モデルの大きなメリットです。
Django の ORM モデルとの比較で理解を深める
Django のモデルも「ORM モデル」
もし Django を触ったことがあれば、
Django の models.Model も、まさに ORM モデルです。
例えば、Django での User もどきを書くとこうなります。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
Pythonこれも、
User クラス=users テーブル
User インスタンス=1 行
という対応です。
使い方も同じで、
user = User.objects.create(name="Taro", email="taro@example.com")
user = User.objects.get(email="taro@example.com")
user.name = "Taro Yamada"
user.save()
user.delete()
Pythonという流れになります。
SQLAlchemy でも Django でも、
「ORM モデル」という考え方は共通です。
クラスでテーブルを表し、インスタンスで行を表す。
これさえ掴めば、ORM の種類が変わっても応用が効きます。
まとめ(ORM モデルは「DB をオブジェクトとして扱うための土台」)
ORM モデルを初心者目線でまとめると、こうなります。
テーブルを Python のクラスとして表現したものが ORM モデルで、クラス=テーブル、インスタンス=行という対応になっている。
INSERT / SELECT / UPDATE / DELETE を、SQL ではなく「オブジェクトの生成・検索・属性変更・削除」として書けるようになる。
リレーション(1 対多、多対多など)もクラスに書けるので、JOIN を意識せずに「オブジェクト同士の関係」として扱える。
SQLAlchemy でも Django でも、根っこにある考え方は同じなので、一度「ORM モデルの感覚」が身につけば他のフレームワークにも応用できる。
ここから一歩進むなら、
「自分で簡単なアプリのモデルを設計してみる」のが一番身につきます。
例えば、
タスク管理なら「User」「Task」
読書ログなら「User」「Book」「Review」
