3秒でわかる
データベースの行をプログラムのオブジェクトとして扱う仕組み。SQL を直接書かずに取得や保存ができ、型の助けも受けられます。
もう少し詳しく
どういうものか
Object-Relational Mapping の略で、テーブルの 1 行をプログラム側の 1 オブジェクトに対応づけるライブラリのこと。users テーブルの行が User オブジェクトになり、カラムがプロパティになる。開発者はメソッドを呼ぶだけでよく、実際に発行される SQL は ORM が組み立てる。
代表的なものに Prisma や TypeORM、Java の Hibernate、Python の SQLAlchemy、Ruby の Active Record がある。どれもテーブル定義とクラス定義を結びつける点は同じで、書き味だけが違う。
なぜ必要か
生の SQL を文字列として書くと、カラム名の打ち間違いが実行するまで分からない。値を文字列連結で埋め込めば SQL インジェクションの穴にもなる。ORM を通すと、カラム名の誤りはコンパイルや型検査の段階で見つかり、値は自動でプレースホルダに渡されるため注入も防げる。
さらに、テーブル定義を変更したときに影響範囲を追いやすい。カラムを消せば、そのプロパティを使っている箇所が一斉にエラーになる。
具体例
Prisma で書いた場合と、同じ処理を生の SQL で書いた場合の対比。
// ORM を使う場合
const users = await prisma.user.findMany({
where: { age: { gte: 20 } },
orderBy: { createdAt: "desc" },
take: 10,
include: { posts: true }, // 関連する投稿もまとめて取る
});
// 生の SQL で同じことをする場合
// SELECT * FROM users WHERE age >= 20 ORDER BY created_at DESC LIMIT 10
// さらに posts を取るために、もう一度クエリを書いて手で結合するusers と posts のような関連を、include のひと言でたどれるのが ORM の分かりやすい利点になる。
つまずきやすいところ
最大の落とし穴が N+1 問題。一覧を 1 回のクエリで取ったあと、各要素の関連データをループの中で取りにいくと、100 件の一覧で 101 回のクエリが飛ぶ。include や join の指定でまとめて取れば 1 回か 2 回に収まる。
もう 1 つは、ORM を使えば SQL を知らなくてよいという誤解。生成された SQL が遅いとき、原因を突き止めるにはインデックスや実行計画の知識が要る。ORM は SQL を隠すのではなく、書く量を減らす道具になる。
似た用語との違い
| 名前 | 立ち位置 |
|---|---|
| ORM | 行をオブジェクトに変換する。SQL はほぼ書かない |
| クエリビルダ | SQL の構造をメソッドで組み立てる。SQL の形は見える |
| ドライバ | SQL 文字列をそのまま DB に送るだけ |