はじめに
TypeORMのQueryBuilderの基本的な使い方のまとめです。詳しくはこちらに公式のドキュメントがあります。
QueryBuilderとは
TypeORMで提供されているQueryBuilderはカスタマイズ性が高い、SQLを構築するためのクラスです。
TypeORMのEntityManager
やRepository
の標準のメソッド(find、findOne、saveなど)だけでは実現が難しい、複数テーブルを跨いだ処理などがある場合、TypeORMのQueryBuilderを使うのも一つの選択肢です。
ただしQueryBuilderを使うということは生のSQLの記述により近くなり、そもそもORMを使っているのにSQLを書くようなイメージで、特に理由がなければ標準のメソッドだけで実装するのが良さそうです。カスタマイズ性が高い分、自分でしっかり設計した上で実装しないとパフォーマンスに影響が出る可能性もあり、後に説明するのですが、SQLインジェクションなど、標準のメソッドを使う際にはあまり気にしなくてもよい部分も考慮する必要があったりします。
QueryBuilderのパターン
QueryBuilderの実装パターンには以下の3種類があります。
Connectionを使ったやり方
from
の第一引数に対象のデータモデルのクラスを指定します(第二引数はSQLでのalias)。
import { getConnection } from "typeorm";
const user = await getConnection()
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.id = :id", { id: 1 })
.getOne();
Entity Managerを使ったやり方
createQueryBuilder
の第一引数に対象のデータモデルのクラスを指定します(第二引数はSQLでのalias)。
import { getManager } from "typeorm";
const user = await getManager()
.createQueryBuilder(User, "user")
.where("user.id = :id", { id: 1 })
.getOne();
Repositoryを使ったやり方
getRepository
の引数に対象のデータモデルのクラスを指定します(createQueryBuilder
の引数はSQLでのalias)。
import { getRepository } from "typeorm";
const user = await getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getOne();
基本的なメソッド
基本的なメソッドには以下の4つがあります。
Select
getOne
やgetMany
メソッドで繋ぎ、単一もしくは複数レコードを取得(findOne
、find
と同様の返り値)
import { getConnection } from "typeorm";
const user = await getConnection()
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.id = :id", { id: 1 })
.getOne();
Insert
values
で値を渡し、execute
でレコードの保存(save
と同様の返り値)
import { getConnection } from "typeorm";
await getConnection()
.createQueryBuilder()
.insert()
.into(User)
.values([
{ firstName: "Timber", lastName: "Saw" },
{ firstName: "Phantom", lastName: "Lancer" }
])
.execute();
Update
set
で値を渡し、execute
でレコードの更新(save
と同様の返り値)
import { getConnection } from "typeorm";
await getConnection()
.createQueryBuilder()
.update(User)
.set({ firstName: "Timber", lastName: "Saw" })
.where("id = :id", { id: 1 })
.execute();
Delete
where
で対象となるレコードを指定し、execute
で削除
import { getConnection } from "typeorm";
await getConnection()
.createQueryBuilder()
.delete()
.from(User)
.where("id = :id", { id: 1 })
.execute();
Raw Results
TypeORMのEntity
クラスで定義したデータモデルとは異なる形のオブジェクトが返却される場合にはgetRawOne
もしくはgetRawMany
メソッドを使います。
// getRawOneに型を渡さないと返却される値がanyになる
interface Sum {
sum: number;
}
const { sum } = await getRepository(User)
.createQueryBuilder("user")
.select("SUM(user.photosCount)", "sum")
.where("user.id = :id", { id: 1 })
.getRawOne<Sum>(); // { sum: 100 }
// getRawManyに型を渡さないと返却される値がanyの配列になる
interface Sum {
id: number;
sum: number;
}
const photosSums = await getRepository(User)
.createQueryBuilder("user")
.select("user.id")
.addSelect("SUM(user.photosCount)", "sum")
.groupBy("user.id")
.getRawMany<Sum[]>(); // [{ id: 1, sum: 25 }, { id: 2, sum: 13 }, ...]
SQLインジェクション対策
where
などの第二引数にパラメータのオブジェクトを渡すことで対象の値をエスケープすることができます。これによりユーザーの入力値などによるSQLインジェクションを防止します。
.where("user.name = :name", { name: "Timber" })
配列の値もエスケープが可能。
.where("user.name IN (:...names)", { names: [ "Timber", "Cristal", "Lina" ] })
まとめ
以上、QueryBuilderの基本的な使い方でした。ちなみに.createQueryBuilder()
ではなく、.query()
で引数に生のSQLを投入することもできます。複数テーブルのデータを統合するなど、いざという時に知っておくと力を発揮するかもしれません。