Day 67:ORMの基礎 ― 「SQLを書かずに、データベースと仲良くなる」
Day 67では、いよいよ ORM(オーアールエム) という少し新しい世界に足を踏み入れていきます。
キーワードはこの3つです。
- ORMとは
- モデル(Model)
- CRUD(Create / Read / Update / Delete)
ここまで、SQLiteとPythonを組み合わせて、 生のSQLを書きながらテーブルを操作してきました。
今日は、その一歩先―― 「SQLを直接書かずに、Pythonのクラスやメソッドでデータベースを扱う」 というスタイルを体験していきます。
ORMとは ― 「テーブルをクラスとして扱うための橋」
ORMのざっくりしたイメージ
まずは、言葉の意味から。
ORM(Object-Relational Mapping) は、
オブジェクト(Pythonのクラス・インスタンス)と リレーショナルデータベース(テーブル・レコード)を いい感じに対応づけてくれる仕組み
のことです。
もう少し感覚的に言うと、
- データベースの「テーブル」を、Pythonの「クラス」として扱えるようにする
- テーブルの「レコード」を、Pythonの「インスタンス」として扱えるようにする
- SQL文を自分で書かなくても、クラスのメソッドでCRUDができるようにする
というイメージです。
なぜORMを使うのか?
ORMを使うメリットは、たとえばこんなところにあります。
- SQLを直接書く量が減る
- Pythonのコードとして読みやすくなる
- テーブル構造とコードの対応が分かりやすくなる
- データベースを差し替えやすくなる(SQLite → PostgreSQLなど)
もちろん、SQLを知っていることはとても大事です。 でも、アプリケーションのコードを書くときには、 「毎回生のSQLを書くより、クラスやメソッドで扱えたほうが楽」という場面が多くなってきます。
モデル(Model)とは ― 「テーブルを表すPythonクラス」
モデル=テーブルの“Python版”
ORMの世界では、 テーブルを表すPythonクラスのことを「モデル(Model)」と呼ぶ ことが多いです。
たとえば、顧客管理の customers テーブルがあるとき、 それに対応する Customer クラスを作る、というイメージです。
ここでは、Pythonでよく使われるORMのひとつ SQLAlchemy を例にしてみます。
※ 実際に動かすには pip install sqlalchemy が必要です。
SQLAlchemyでモデルを定義してみる
# day67_orm_basic.py
from sqlalchemy import create_engine, Column, Integer, String, Text
from sqlalchemy.orm import declarative_base, sessionmaker
# ベースクラスを作成します(全モデルの親になります)
Base = declarative_base()
class Customer(Base):
"""
customers テーブルに対応するモデルクラスです。
テーブルのカラムを、クラスの属性として定義します。
"""
__tablename__ = "customers" # 対応するテーブル名
id = Column(Integer, primary_key=True, autoincrement=True) # 主キー
name = Column(String(100), nullable=False) # NOT NULL な名前
email = Column(String(255), nullable=False, unique=True) # UNIQUE なメール
phone = Column(String(50), nullable=True) # 任意の電話番号
note = Column(Text, nullable=True) # 任意のメモ
def __repr__(self) -> str:
"""
インスタンスを print したときの表示を分かりやすくするためのメソッドです。
"""
return f"<Customer id={self.id} name={self.name} email={self.email}>"
Pythonここでやっていることは、
Customerクラスがcustomersテーブルを表しているid,name,email,phone,noteがテーブルのカラムに対応しているprimary_key=Trueやnullable=Falseなどで、テーブルの制約も一緒に書いている
という、まさに「テーブルのPython版」を作る作業です。
セッションを用意する ― 「データベースとの会話窓口」
エンジンとセッションを作る
ORMでは、 セッション(Session) というものを通してデータベースとやり取りします。
# データベースへの接続(SQLiteファイルを使います)
engine = create_engine("sqlite:///day67_orm_example.db", echo=False)
# テーブルを作成します(モデル定義に基づいて自動生成)
Base.metadata.create_all(engine)
# セッション工場を作成し、そこからセッションを生成します。
SessionLocal = sessionmaker(bind=engine)
def get_session():
"""
セッション(データベースとの会話窓口)を取得するための関数です。
"""
return SessionLocal()
Pythonここまでで、
- SQLiteのファイル
day67_orm_example.dbに接続するエンジンを作る Customerモデルに基づいてcustomersテーブルを自動生成する- セッションを作るための仕組みを用意する
という準備が整いました。
ORMでのCRUD ― 「クラスとインスタンスでデータを操作する」
ここからが、ORMのいちばん楽しいところです。 生のSQLではなく、PythonのクラスとインスタンスでCRUDを行う ところを見ていきます。
Create(作る)― 新しい顧客を追加する
def create_customer(name: str, email: str, phone: str | None = None, note: str | None = None) -> None:
"""
新しい顧客を作成して、データベースに保存する関数です。
ORMでは、モデルのインスタンスを作って session に追加します。
"""
session = get_session()
# Customer インスタンスを作成します(まだDBには保存されていません)
customer = Customer(
name=name,
email=email,
phone=phone,
note=note,
)
# セッションに追加して、コミットするとDBに反映されます。
session.add(customer)
session.commit()
print(f"[CREATE] 顧客を追加しました: {customer}")
session.close()
Pythonここでのポイントは、
Customer(...)で「レコードの候補」をインスタンスとして作るsession.add()で「保存したいよ」とセッションに伝えるsession.commit()で「確定」して、実際にDBに書き込む
という流れです。
SQL文は一切書いていませんが、 裏側ではちゃんと INSERT が発行されています。
Read(読む)― 顧客一覧を取得する
def list_customers() -> None:
"""
顧客一覧を取得して表示する関数です。
ORMでは、session.query(Customer) を使ってレコードを取得します。
"""
session = get_session()
# 全顧客を取得します(ORDER BY id ASC に相当)
customers = session.query(Customer).order_by(Customer.id.asc()).all()
if not customers:
print("[READ] 顧客はまだ登録されていません。")
session.close()
return
print("[READ] 顧客一覧:")
for c in customers:
# c は Customer インスタンスです。
print(f" id={c.id}, name={c.name}, email={c.email}, phone={c.phone}, note={c.note}")
session.close()
Pythonここでのポイントは、
session.query(Customer)で「Customerテーブルを対象にする」.all()で「全部ください」- 返ってくるのは、タプルではなく Customerインスタンスのリスト
というところです。
「レコード」ではなく「オブジェクト」として扱えるので、 c.name や c.email のように、自然な形でアクセスできます。
Read(条件付き)― 名前で検索する
def search_customers_by_name(keyword: str) -> None:
"""
名前に keyword を含む顧客を検索して表示する関数です。
ORMでは、filter() を使って条件を指定します。
"""
session = get_session()
# LIKE '%keyword%' に相当する条件を指定します。
pattern = f"%{keyword}%"
customers = (
session.query(Customer)
.filter(Customer.name.like(pattern))
.order_by(Customer.id.asc())
.all()
)
if not customers:
print(f"[READ] キーワード '{keyword}' に一致する顧客は見つかりませんでした。")
session.close()
return
print(f"[READ] キーワード '{keyword}' に一致した顧客:")
for c in customers:
print(f" id={c.id}, name={c.name}, email={c.email}, phone={c.phone}, note={c.note}")
session.close()
Pythonfilter() や like() を使うことで、 SQLの WHERE や LIKE に相当する条件を、Pythonのメソッドチェーンとして書けます。
Update(直す)― 顧客情報を更新する
def update_customer_email(customer_id: int, new_email: str) -> None:
"""
指定したIDの顧客のメールアドレスを更新する関数です。
ORMでは、対象のインスタンスを取得して、属性を書き換えます。
"""
session = get_session()
# 対象の顧客を1件取得します(存在しない場合は None)
customer = session.query(Customer).filter(Customer.id == customer_id).one_or_none()
if customer is None:
print(f"[UPDATE] ID={customer_id} の顧客は見つかりませんでした。")
session.close()
return
print(f"[UPDATE] 更新前: {customer}")
customer.email = new_email # 属性を書き換えます
session.commit() # 変更をDBに反映します
print(f"[UPDATE] 更新後: {customer}")
session.close()
Pythonここでのポイントは、
session.query(...).filter(...).one_or_none()で「1件だけ」取得する- 取得したインスタンスの属性を直接書き換える
session.commit()で変更を確定する
という流れです。
SQLの UPDATE を自分で書く必要はなく、 「オブジェクトの状態を変える」ことに集中できる のが、ORMの心地よさです。
Delete(消す)― 顧客を削除する
def delete_customer(customer_id: int) -> None:
"""
指定したIDの顧客を削除する関数です。
ORMでは、インスタンスを session.delete() に渡します。
"""
session = get_session()
customer = session.query(Customer).filter(Customer.id == customer_id).one_or_none()
if customer is None:
print(f"[DELETE] ID={customer_id} の顧客は見つかりませんでした。")
session.close()
return
print(f"[DELETE] 削除対象: {customer}")
session.delete(customer) # 削除を指示します
session.commit() # DBに反映します
print("[DELETE] 顧客を削除しました。")
session.close()
PythonDELETEも同じく、
- 対象のインスタンスを取得する
session.delete()で「消したい」と伝えるsession.commit()で確定する
という流れです。
Day 67ミニテンプレート:ORM基礎セット
最後に、今日の内容をひとまとめにした ORM基礎テンプレート を載せておきます。
# day67_orm_template.py
from sqlalchemy import create_engine, Column, Integer, String, Text
from sqlalchemy.orm import declarative_base, sessionmaker
Base = declarative_base()
class Customer(Base):
__tablename__ = "customers"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False)
email = Column(String(255), nullable=False, unique=True)
phone = Column(String(50), nullable=True)
note = Column(Text, nullable=True)
def __repr__(self) -> str:
return f"<Customer id={self.id} name={self.name} email={self.email}>"
engine = create_engine("sqlite:///day67_orm_example.db", echo=False)
Base.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine)
def get_session():
return SessionLocal()
def demo():
session = get_session()
# Create
c1 = Customer(name="Taro", email="taro@example.com", phone="090-0000-0000", note="VIP顧客")
c2 = Customer(name="Hanako", email="hanako@example.com")
session.add_all([c1, c2])
session.commit()
# Read
customers = session.query(Customer).order_by(Customer.id.asc()).all()
print("=== 顧客一覧 ===")
for c in customers:
print(c)
# Update
c1.email = "taro.new@example.com"
session.commit()
print("=== 更新後 ===")
print(c1)
# Delete
session.delete(c2)
session.commit()
print("=== 削除後 ===")
customers = session.query(Customer).order_by(Customer.id.asc()).all()
for c in customers:
print(c)
session.close()
if __name__ == "__main__":
demo()
PythonDay 67のまとめ ― 「テーブルがクラスになった瞬間、コードの景色が変わる」
今日の主役は、
- ORMとは:オブジェクトとテーブルを対応づける仕組み
- モデル(Model):テーブルを表すPythonクラス
- CRUD:Create / Read / Update / Delete を、クラスとインスタンスで行うスタイル
でした。
生のSQLで書いていた操作が、 「クラスを定義して、インスタンスを作って、属性をいじる」 という、Pythonらしい世界に移ってきたのを感じられたと思います。
もちろん、ORMは魔法ではありません。 裏側ではちゃんとSQLが動いていて、 その意味を理解しているほど、ORMも安心して使えるようになります。
でも、アプリケーションを作っていくうえで、 「テーブル=モデル」「レコード=インスタンス」という感覚を持てるようになると、 コード全体の見通しがぐっと良くなっていきます。
