---
title: "ORMとは？O/Rマッパーの仕組み・メリットとデメリットをSQL比較と実行結果で解説"
url: "https://www.issoh.co.jp/tech/details/3755/"
published: 2024-10-07
updated: 2026-09-26
categories: ["データベース"]
publisher: "株式会社一創"
---

# ORMとは？O/Rマッパーの仕組み・メリットとデメリットをSQL比較と実行結果で解説

ORM（Object-Relational Mapping）は、プログラムの中のオブジェクトとリレーショナルデータベースのテーブルを対応づけ、SQLの組み立てと結果の詰め替えを肩代わりする仕組みです。`select(User).where(User.name == "sato")`のような式から`SELECT`文が組み立てられ、`book.author`のように関連をたどる属性を読んだ時点でSQLが発行されることもあります。

本記事では、同じ処理をSQL直書きとORMで書き比べ、ORMの内部で起きていること、Active RecordとData Mapperの違い、N+1問題やSQLインジェクションの抜け穴を、SQLAlchemy 2.1.1（Python 3.13.14・SQLite 3.53.1、2026年9月26日実行）の実行結果で確かめます。

## まとめ：ORMの要点

- ORMは「クラス＝テーブル、インスタンス＝行、属性＝列」を基本にオブジェクトとテーブルを対応づけ、SQLを自動生成する技術です。読み方は「オーアールエム」で、ツールを指してO/Rマッパーとも呼びます。
- 主な利点は、ORMの比較式を使えば値がバインド変数で渡ること、DBごとのSQL方言の差を吸収すること、変更の追跡とまとめ書き込み（Unit of Work）を任せられることです。
- 代表的な弱点はN+1問題です。SQLAlchemyで著者100人とその本を既定設定のまま読むと101本のSELECTが走り、`selectinload`を1行足すと2本に減りました。
- ORMを使ってもSQLインジェクションは消えません。生SQLへの文字列埋め込みと、バインドできない並べ替え列名が抜け穴として残ります。
- CRUD中心の業務アプリはORM、集計・分析・大量一括更新はSQL直書きかクエリビルダ、と処理単位で使い分けるのが現実的です。

## ORMの意味と読み方・O/Rマッパーとの関係

ORMは「Object-Relational Mapping（オブジェクト関係マッピング）」の略で、オブジェクトとテーブルを対応づける技術を指します。英字のまま「オーアールエム」と読みます。マッピングという考え方を指すときはORMやO/Rマッピング、それを実装したライブラリを指すときはO/RマッパーやORマッパーと呼ぶことが多く、日常的にはほぼ同じ意味で使われます。

ORMが埋めるのは、オブジェクト指向の言語とリレーショナルデータベースの表現の違いです。この違いはインピーダンスミスマッチと呼ばれます。プログラムは「注文オブジェクトが明細のリストを持つ」と参照でつながりますが、データベースは明細テーブルが注文IDを外部キーとして持ち、結合（JOIN）でつなぎます。基本的な対応は次の表のとおりです（継承を使うモデルなどでは、1クラスが複数テーブルにまたがる対応もあります）。

| プログラム側      | データベース側   | 例                        |
| ----------- | --------- | ------------------------ |
| クラス         | テーブル      | `User` と `users`         |
| インスタンス      | 1行（レコード）  | id=1の利用者                 |
| 属性          | 列（カラム）    | `user.email`             |
| オブジェクトの同一性  | 主キー       | 1つのセッション内でid=1は1つのオブジェクト |
| 他オブジェクトへの参照 | 外部キーと結合   | `book.author`            |
| 子オブジェクトのリスト | 1対多の子テーブル | `author.books`           |

テーブル設計そのもの（どこまで分割するか）はORMの守備範囲外です。分割の考え方は[データベース正規化とは？第1〜第3正規形の手順と非正規化の判断を解説](/tech/details/16281/)で扱っています。

## SQL直書きとORMの書き方の違い

利用者を1件登録して名前で検索する処理を、Python標準の`sqlite3`（SQL直書き）とSQLAlchemy 2.1のORMで書き比べます。

```
import sqlite3

# SQL直書き：SQLを自分で書き、結果の列を1つずつ詰め替える
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
conn.execute("INSERT INTO users (name, email) VALUES (?, ?)", ("sato", "sato@example.com"))
row = conn.execute("SELECT id, name, email FROM users WHERE name = ?", ("sato",)).fetchone()
user = {"id": row[0], "name": row[1], "email": row[2]}
```

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

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
    email: Mapped[str] = mapped_column(String(100))

engine = create_engine("sqlite://", echo=True)   # echo=Trueで発行SQLを表示
Base.metadata.create_all(engine)

with Session(engine) as session:
    session.add(User(name="sato", email="sato@example.com"))
    session.commit()
    user = session.scalars(select(User).where(User.name == "sato")).one()
    print(user.id, user.email)   # 1 sato@example.com
```

`echo=True`で表示されたSQLは次のとおりです（ログの接頭辞は省略）。

```
CREATE TABLE users (
	id INTEGER NOT NULL,
	name VARCHAR(50) NOT NULL,
	email VARCHAR(100) NOT NULL,
	PRIMARY KEY (id)
)
INSERT INTO users (name, email) VALUES (?, ?)
[generated in 0.00031s] ('sato', 'sato@example.com')
SELECT users.id, users.name, users.email
FROM users
WHERE users.name = ?
[generated in 0.00033s] ('sato',)
```

`User.name == "sato"`という比較式が`WHERE users.name = ?`へ変換され、値は別枠のパラメータとして渡されています。型ヒントを`Mapped[str]`（`Optional`なし）にした列は自動で`NOT NULL`になりました。そして結果は辞書への詰め替えなしに`User`オブジェクトとして返ります。SQL直書き側の`row[0]`のような列番号への依存が消えるのが、ORMの最も分かりやすい効果です。

SQLAlchemyの導入や、テーブル定義の変更をDBに反映する手順は[SQLAlchemyとは？Python ORMの基本と使い方を入門から実践まで解説](/tech/details/7912/)と[Alembic（アレンビック）とは？Python/SQLAlchemyのDBマイグレーションツールの使い方を基礎から解説](/tech/details/3417/)で詳しく扱っています。

## ORMの内部の仕組み：Identity Map・Unit of Work・Lazy Load

ORMの動きの多くは、Martin Fowlerが『Patterns of Enterprise Application Architecture』（2002年）で名前を付けた3つのパターンで説明できます（定義は[Fowlerのパターンカタログ](https://martinfowler.com/eaaCatalog/)で公開されています）。SQLAlchemyのREADMEも、ORMが「identity map, unit of work, and data mapper patterns」の上に作られていると明記しています。以下、各パターンの定義とSQLAlchemy 2.1.1での実測結果です。

Identity Mapは、Fowlerの定義では「読み込んだオブジェクトをすべてマップに保持し、各オブジェクトが一度だけ読み込まれることを保証する」ものです。同じセッション内で`session.get(Author, 1)`を2回呼ぶと、発行されたSQLは1本で、2つの戻り値は`is`で比較して同一でした。1つのセッションの中で同じ行が別々のオブジェクトになり、片方の変更がもう片方に反映されない、という食い違いをこれで防いでいます。ただしクエリ結果のキャッシュではないため、`select()`で同じ条件の検索を繰り返せばSQLはその都度発行されます。`session.get`が2回目にSQLを省けたのは、主キーで引く場合にIdentity Mapを先に確認する仕様だからです。

Unit of Workは、業務処理で変更されたオブジェクトの一覧を保持し、書き込みを取りまとめます。`author.name = "renamed"`と代入した時点ではSQLは発行されず、`commit()`で`UPDATE`が1本だけ流れました。書き込みは`session.flush()`で明示的に前倒しできるほか、既定の`autoflush=True`ではクエリを実行する直前にも自動で行われます。開発者が`UPDATE`文を書かなくてよい代わりに、SQLが流れる時点を意識しておく必要があります。

Lazy Loadは、関連オブジェクトを実際に使う時点まで読み込みを遅らせます。SQLAlchemyのドキュメントは、`lazy`を指定しない`relationship()`の既定をlazy loadingとしており、手元でも`Author.books.property.lazy`は`select`でした。この既定が、次に述べるN+1問題の原因になります。

## Active RecordとData Mapper：ORMの2つの設計パターン

ORMは、データベースへの保存処理をどこに置くかで2つの型に分かれます。Active Recordは「テーブルの1行を包み、データベースアクセスとその行に対する業務ロジックを1つのオブジェクトに持たせる」型、Data Mapperは「オブジェクトとデータベースの間でデータを運ぶマッパー層を置き、両者を互いに独立させる」型です（いずれもFowlerの定義）。

| 項目     | Active Record                  | Data Mapper         |
| ------ | ------------------------------ | ------------------- |
| 保存の書き方 | `user.save()`                  | `session.add(user)` |
| モデルの責務 | データ＋DBアクセス                     | データ（＋業務ロジック）のみ      |
| 代表例    | Rails Active Record、Django ORM | SQLAlchemy ORM      |
| 向く規模   | 画面とテーブルが素直に対応する                | 業務ルールが複雑なドメイン       |

Rails Guidesは自らをActive Recordパターンの実装と説明しており、Djangoの設計思想のページも「Martin FowlerのActive Recordデザインパターンに従う」と書いています。TypeORMはREADMEで両方のパターンに対応すると明記している珍しい例です。Laravel Eloquentも1テーブルに1モデルが対応するActive Record型です。

業務ルールをDBと切り離してテストしたいならData Mapper、書き始めの速さを優先するならActive Recordが目安です（後者はモデルが肥大化しやすい）。Rails側から見た詳細は[Active Record（アクティブレコード）とは？Railsを支える設計パターンと基本を実例で解説](/tech/details/2913/)、Eloquentは[Laravel Eloquentとは｜モデル定義・リレーション・eager loadingの基礎とクエリビルダの使い分け](/tech/details/17127/)を参照してください。

## ORMを使うメリット

- **比較式の値はバインド変数で渡る**：前述の実行結果のとおり、ORMの比較式に渡した値はSQL文字列に埋め込まれず`?`の別枠で渡ります（生SQLを組み立てる経路は例外で、後述します）。[OWASPのSQLインジェクション対策チートシート](https://cheatsheetseries.owasp.org/cheatsheets/SQL%5FInjection%5FPrevention%5FCheat%5FSheet.html)も、新規開発では「パラメータ化クエリ、ストアドプロシージャ、またはクエリを組み立てるORM」で作るよう勧めています。
- **SQL方言の差を吸収できる**：同じ`select(User).limit(10)`を方言ごとにコンパイルすると、SQLiteとPostgreSQL・MySQLでは`LIMIT`、SQL Serverでは`SELECT TOP`、Oracleでは`FETCH FIRST … ROWS ONLY`に変換されました。ただし型や照合順序などDBごとの挙動の差は残るため、本番と同じDBでのテストは別途必要です。
- **型と補完が効く**：`Mapped[int]`のような型付きの列定義により、列名の打ち間違いが実行前にエディタや型チェッカーで見つかります。SQL文字列の中の列名は、SQL対応のIDEやスキーマを参照する解析ツールを使わない限り、実行時まで誤りに気づきにくくなります。
- **マイグレーションと連携できる**：モデル定義の変更からスキーマ変更スクリプトを生成するツールと組み合わせられます。比較の相手は異なり、AlembicはDBの現状とモデルを、Djangoの`makemigrations`は過去のマイグレーションファイルから復元した状態と現在のモデルを比べます。

## ORMのデメリットとN+1問題の実測

ORMの弱点の多くは「SQLが見えなくなること」から生じます。

| 弱点               | 起きること                   | 対処            |
| ---------------- | ----------------------- | ------------- |
| N+1問題            | 関連の遅延読み込みでSELECTが件数分増える | Eager Loadを明示 |
| 発行SQLが見えない       | 遅いSQLに気づけない             | SQLログ・件数テスト   |
| 集計・分析が苦手         | ウィンドウ関数やCTEが書きにくい       | その部分だけSQL直書き  |
| 個別オブジェクトの大量更新が遅い | 1行ずつ読み込んで属性を変更          | 一括UPDATE文を発行  |
| 学習コストの二重化        | ORMのAPIとSQLの両方が必要       | 生成SQLを読む習慣    |

N+1問題は、一覧を1本のSQLで取った後、各行の関連データを1本ずつ取りに行く現象です。著者100人に3冊ずつ本を持たせ、SQLAlchemy 2.1.1で全員の本の冊数を数えるコードを3通りに書き、発行されたSQLの本数を数えました。モデル定義から計測までを含む実行可能なコードです。

```
from sqlalchemy import create_engine, event, select, text, ForeignKey, String
from sqlalchemy.orm import (DeclarativeBase, Mapped, mapped_column, relationship,
                            Session, selectinload, joinedload)

class Base(DeclarativeBase):
    pass

class Author(Base):
    __tablename__ = "authors"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(50))
    books: Mapped[list["Book"]] = relationship(back_populates="author")

class Book(Base):
    __tablename__ = "books"
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column(String(100))
    author_id: Mapped[int] = mapped_column(ForeignKey("authors.id"))
    author: Mapped["Author"] = relationship(back_populates="books")

engine = create_engine("sqlite://")
Base.metadata.create_all(engine)

sql_log = []   # 発行されたSQLを記録する
@event.listens_for(engine, "before_cursor_execute")
def record(conn, cursor, statement, parameters, context, executemany):
    sql_log.append(statement)

with Session(engine) as session:   # 著者100人×3冊を投入
    for i in range(100):
        session.add(Author(name=f"author{i}",
                           books=[Book(title=f"book{i}-{j}") for j in range(3)]))
    session.commit()

def count_books(option=None):
    sql_log.clear()
    with Session(engine) as session:
        stmt = select(Author)
        if option is not None:
            stmt = stmt.options(option)
        authors = session.scalars(stmt).unique().all()
        total = sum(len(a.books) for a in authors)
    return len(sql_log), total

print(count_books())                            # (101, 300) 既定のlazy="select"
print(count_books(selectinload(Author.books)))  # (2, 300)
print(count_books(joinedload(Author.books)))    # (1, 300)
```

| 読み込み方          | 発行SQL数 | 2本目以降のSQL                         |
| -------------- | ------ | --------------------------------- |
| 既定（lazy）       | 101本   | 著者ごとに`WHERE author_id = ?`        |
| `selectinload` | 2本     | `WHERE author_id IN (?, …)`（100個） |
| `joinedload`   | 1本     | なし（`LEFT OUTER JOIN books`）       |

どの書き方でも数えた冊数は300冊で、結果は同じです。一方でSQLの本数に加え、結果の行の形（`joinedload`は著者の列が本の数だけ繰り返される）や転送量も異なります。既定の書き方は読み込み方法を何も指定しないため、コードを読むだけではN+1に気づきにくいのが問題です。著者が1万人になれば既定の書き方は10,001本に増えます。一覧画面を作ったら、発行SQLの本数をテストで固定しておくのが確実な予防策です。Django・Rails・Hibernateでの対策と検出ツールは[N+1問題とは？発行クエリ数の実測と検出方法・ORM別の対策](/tech/details/4262/)にまとめています。

## ORMでも残るSQLインジェクションの抜け穴

ORMの比較式を使う限り値はバインドされますが、ORMには生SQLを書く出口が必ずあります。OWASPのチートシートも、HibernateのHQLに「同じ種類のインジェクション問題（HQL Injection）」があると注意しています。前述のスクリプトの続きとして、名前に`' OR '1'='1`を入力された場合を試しました（`text`は冒頭でimport済み）。

```
inp = "' OR '1'='1"

# 危険：text()にf文字列で埋め込む → 100行すべて返った
session.scalars(select(Author).where(text(f"name = '{inp}'"))).all()

# 安全：text()でもバインド変数を使う → 0行
session.scalars(select(Author).where(text("name = :n")).params(n=inp)).all()

# 安全：ORMの比較式 → 0行
session.scalars(select(Author).where(Author.name == inp)).all()
```

見落としやすいのは並べ替えです。OWASPは、テーブル名・列名・並び順（ASC／DESC）はバインド変数にできないため、許可リストでの入力検証か設計の見直しが必要だとしています。URLの`?sort=`をそのまま`order_by(text(sort))`に渡すと、次の入力で他の行の中身を1文字ずつ推測できました。

```
# 条件が真なら id昇順（先頭 id=1）、偽なら降順（先頭 id=100）
sort = "(CASE WHEN (SELECT substr(name,1,6) FROM authors WHERE id=1)='author' THEN id ELSE -id END)"
session.scalars(select(Author).order_by(text(sort)).limit(1)).first().id   # 1
# 'author' を 'xxxxxx' に変えると 100 になる

# 対策：受け付ける値を辞書で列オブジェクトに対応づける
ALLOWED = {"name": Author.name, "id": Author.id}
col = ALLOWED.get(sort, Author.id)   # 想定外の値は既定列に倒す
session.scalars(select(Author).order_by(col)).all()
```

先頭行が1になるか100になるかで、SQLの条件の真偽が外から分かり、エラーも出ません。ORMを採用しただけでインジェクション対策済みとはせず、`text()`・`raw()`・`Sequelize.literal()`のような生SQLの出口と、並べ替え・列選択のパラメータをコードレビューの確認項目に入れてください。Java（MyBatis）での動的SQLの注意点は[MyBatis Dynamic SQL（動的SQL）の書き方｜if・foreach・whereとインジェクション対策](/tech/details/2290/)で扱っています。

## ORM・クエリビルダ・SQLマッパー・DAOの違い

「ORMかSQL直書きか」の間には、SQLをどこまで自分で書くかで段階があります。

| 方式          | SQLを書くのは  | 結果の受け取り    | 例                               |
| ----------- | --------- | ---------- | ------------------------------- |
| フルORM       | ORM（自動生成） | モデルのオブジェクト | SQLAlchemy ORM、Hibernate、Prisma |
| クエリビルダ      | コードで組み立て  | 行・型付きの値    | SQLAlchemy Core、Kysely          |
| SQLマッパー     | 開発者（SQL文） | 指定したクラス    | MyBatis、Dapper、sqlx             |
| SQLからのコード生成 | 開発者（SQL文） | 生成された型     | sqlc                            |
| SQL直書き      | 開発者       | 行（自分で詰め替え） | JDBC、database/sql               |

MyBatisは自らを「SQL mapper framework」、Dapperは「a simple object mapper」、Kyselyは「type-safe TypeScript SQL query builder」と説明しています。SQLは自分で書き、結果の詰め替えだけを任せたい場合はSQLマッパーが、SQLの構造を保ったまま型の恩恵を受けたい場合はクエリビルダやコード生成が選択肢になります。

DAO（Data Access Object）はこれらと同列の選択肢ではなく、データベースアクセスを1つのクラスの背後に隠す設計上の層です。DAOの中身をSQL直書きで実装することも、ORMで実装することもできます。「DAOかORMか」ではなく、「DAOの中で何を使うか」と考えると整理しやすくなります。

## 言語別の代表的なORM

| 言語         | 代表的なORM                        | 補足                        |
| ---------- | ------------------------------ | ------------------------- |
| Java       | Hibernate（Jakarta Persistence） | SQLマッパーならMyBatis          |
| Python     | SQLAlchemy、Django ORM          | Django ORMはActive Record型 |
| Ruby       | Active Record                  | Railsに同梱                  |
| PHP        | Eloquent（Laravel）              | 1テーブル1モデル                 |
| TypeScript | Prisma、TypeORM、Drizzle ORM     | TypeORMは2パターン両対応          |
| C#         | Entity Framework Core          | 軽量な選択肢にDapper             |
| Go         | GORM、ent                       | sqlx・sqlcとの比較は下記          |

言語ごとの選び方は、Javaは[Java ORM（O/Rマッパー）の種類と選び方｜Hibernate・MyBatis・jOOQを比較【2026年版】](/tech/details/3301/)、TypeScriptは[TypeORMとPrismaを比較｜2026年版の違いと選び方（型安全・マイグレーション・パフォーマンス）](/tech/details/4331/)と[Drizzle ORM（ドリズル）とは？使い方・Prismaとの比較・v1.0 RCの変更点【2026年9月】](/tech/details/7939/)、Djangoは[Django ORMのクエリ設計｜遅延評価と集計の順序・生SQLへ落とす判断](/tech/details/17082/)で詳しく扱っています。

### Goのdatabase/sql・sqlx・sqlc・GORM・entの位置づけ

Goは標準ライブラリの`database/sql`が「SQL（またはSQLに似た）データベースへの汎用インターフェース」を提供し、その上に性格の違うライブラリが並びます。2026年9月26日時点のGitHubの最新リリースで整理します。

| ライブラリ        | 分類                         | 最新リリース              |
| ------------ | -------------------------- | ------------------- |
| database/sql | 標準の汎用インターフェース              | Go本体に同梱             |
| sqlx         | database/sqlの拡張（SQLマッパー）   | v1.4.0（2024-04-23）  |
| sqlc         | SQLから型付きコードを生成             | v1.31.1（2026-04-22） |
| GORM         | フルORM                      | v1.31.2（2026-06-25） |
| ent          | スキーマをGoコードで書くエンティティフレームワーク | v0.14.6（2026-03-23） |
| SQLBoiler    | DBスキーマからORMを生成             | v4.19.5（2025-06-27） |

SQLBoilerは、[GitHubのREADME](https://github.com/volatiletech/sqlboiler)で「maintenance mode」に入ったことを宣言しています。新機能は原則受け付けず、報告された不具合もメンテナーが通常は対応しないと書かれ、代替としてBobとsqlcを挙げています。これから新規に選ぶなら、SQLを自分で書くチームはsqlc、モデル中心で書きたいチームはGORMかent、が2026年時点の現実的な分岐です。GORMの一括挿入とトランザクションの範囲は[GORM CreateInBatchesの一括挿入とトランザクション範囲｜v1.31.2の基本操作](/tech/details/3402/)、ジェネリクス対応の新APIは[GORMのGenerics APIとは？従来APIとの違いと基本の実装パターン](/tech/details/7062/)で解説しています。

## ORMを使うべき処理とSQL直書きに切り替える判断基準

ORMは全面採用か不採用かの二択ではなく、1つのアプリの中で処理の性格ごとに切り替えられます。判断基準は次のとおりです。

- **ORMで書く**：1件〜数十件のCRUD、画面とテーブルが素直に対応する管理画面、関連をたどる業務処理。Identity MapとUnit of Workの恩恵が最も大きい領域です。
- **SQL直書き（またはクエリビルダ）に切り替える**：売上集計・ランキングのようにウィンドウ関数やCTEを使う分析系、条件に合う行をまとめて書き換える一括更新、PostgreSQLのJSONB演算子や全文検索などDB固有の機能を使う処理。
- **ORMを採用しない**：ほぼすべての処理が集計・レポートで、モデルとして扱う対象が少ないアプリ。ORMの学習コストと抽象化の層だけが残ります。

避けたいのは、集計画面をORMのオブジェクトで組み、全件を読み込んだうえで関連をたどりながらアプリ側のループで合計する書き方です。関連の読み込み方を指定していなければ行数分のSELECTが加わり、全件をオブジェクト化する分のメモリも消費します。件数が少ない開発環境では問題が表に出ず、本番データで初めて表面化します。SQLAlchemyなら同じセッションの中で`text()`（バインド変数付き）やCoreの`select()`に切り替えられるので、トランザクションを分けずに部分的にSQLへ降りられます。

## よくある質問

### ORMの読み方は？

英字のまま「オーアールエム」と読みます。Object-Relational Mappingの略で、日本語ではオブジェクト関係マッピング、ツールを指す場合はO/Rマッパー（オーアールマッパー）とも呼びます。

### ORMを使えばSQLは覚えなくてよいですか？

いいえ。N+1問題の検出や遅いクエリの調査では、ORMが生成したSQLを読む必要があります。集計やDB固有機能を使う処理はSQLで書く場面も残るため、少なくともSELECT・JOIN・インデックスの基本は必要です。

### DAOとORMの違いは？

DAOはデータベースアクセスを1つのクラスの背後に隠す設計上の層で、ORMはオブジェクトとテーブルを対応づけるライブラリです。DAOの内部実装としてORMを使うことも、SQL直書きを使うこともできます。

### PythonのORMは何を選べばよいですか？

Djangoでアプリを作るならDjangoに同梱のDjango ORMを使います。FastAPIやFlaskなどフレームワークを問わず使うなら、Data Mapper型のSQLAlchemyが定番です。SQLAlchemyの最新は2.1.1（2026年9月25日公開）で、Python 3.11以上が必要です。

### ORMはNoSQLでも使えますか？

ORMの「R」はリレーショナルを指すため、MongoDBのようなドキュメント型DBでは、同じ役割のライブラリをODM（Object-Document Mapper）と呼び分けることがあります。Node.jsのMongooseが代表例で、自らを「MongoDB object modeling」と説明しています。

## 関連記事

- [N+1問題とは？発行クエリ数の実測と検出方法・ORM別の対策](/tech/details/4262/)
- [Active Record（アクティブレコード）とは？Railsを支える設計パターンと基本を実例で解説](/tech/details/2913/)
- [SQLAlchemyとは？Python ORMの基本と使い方を入門から実践まで解説](/tech/details/7912/)
- [Java ORM（O/Rマッパー）の種類と選び方｜Hibernate・MyBatis・jOOQを比較【2026年版】](/tech/details/3301/)
- [TypeORMとPrismaを比較｜2026年版の違いと選び方（型安全・マイグレーション・パフォーマンス）](/tech/details/4331/)

---

出典: [ORMとは？O/Rマッパーの仕組み・メリットとデメリットをSQL比較と実行結果で解説](<https://www.issoh.co.jp/tech/details/3755/>)（株式会社一創）
