基礎から学ぶPython入門 90日コース | データベース・Web API - Day 67:ORMの基礎

Python 90日で身につけるPython
スポンサーリンク
スポンサーリンク

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=Truenullable=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.namec.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()
Python

filter()like() を使うことで、 SQLの WHERELIKE に相当する条件を、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()
Python

DELETEも同じく、

  • 対象のインスタンスを取得する
  • 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()
Python

Day 67のまとめ ― 「テーブルがクラスになった瞬間、コードの景色が変わる」

今日の主役は、

  • ORMとは:オブジェクトとテーブルを対応づける仕組み
  • モデル(Model):テーブルを表すPythonクラス
  • CRUD:Create / Read / Update / Delete を、クラスとインスタンスで行うスタイル

でした。

生のSQLで書いていた操作が、 「クラスを定義して、インスタンスを作って、属性をいじる」 という、Pythonらしい世界に移ってきたのを感じられたと思います。

もちろん、ORMは魔法ではありません。 裏側ではちゃんとSQLが動いていて、 その意味を理解しているほど、ORMも安心して使えるようになります。

でも、アプリケーションを作っていくうえで、 「テーブル=モデル」「レコード=インスタンス」という感覚を持てるようになると、 コード全体の見通しがぐっと良くなっていきます。

タイトルとURLをコピーしました