TypeORMおよびTypeORMのQuery Builder
初めに
TypeORMとは、NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、NativeScript、Expo、Electronといった各プラットフォームで実行でき、TypeScriptおよびJavaScript(ES5、ES6、ES7、ES8)を共用できるORMをいいます。TypeORMの目的としてはJavaScriptの最新機能をサポートでき、追加機能を提供することです。これにより、テーブルの少ないデータベースを使った小規模から、複数のデータベースを使った大規模までのアプリケーションを開発することができます。
ORMとはエンティティとデータベースのテーブルとをマップする手法をいいます。 ORMは、オブジェクトからテーブルへの変換およびテーブルからオブジェクトへの変換を自動化することにより、開発プロセスを簡素化します。
概要
TypeORMはnode.jsで実行される、TypeScriptで作ったObject Relational Mapperライブラリです。 TypeScriptは任意の入力機能を備えたJavaScriptの改良版であり、実行時に解釈しないコンパイラ型言語です。TypeScriptのコンパイラは、TypeScript(.ts)ファイルをJavaScript(.js)ファイルにコンパイルします。
TypeORMは、MySQL、PostgreSQL、MariaDB、SQLite、MS SQL Server、Oracle、SAP Hana、WebSQLといった色々なデータベースをサポートしています。TypeORMはデータベースに接続するようなアプリケーションを新規作成するために使いやすいORMです。TypeORMの機能はRDBMS向けの概念です。
TypeORMの特徴
モデルをもとにデータベースのスキーマを作成することを自動化
データベースへのオブジェクトの登録・更新・削除が易い
テーブルの間とのマッピング作成(1対1、1対多、多対多)
簡単なCLIコマンドの提供
TypeORMの利点
TypeORMは簡単な実装でORMフレームワークを利用しやすいです。以下にその利点を示します。
- 高品質で疎結合のアプリケーション
- 拡張可能なアプリケーション
- 他のモジュールとの結合が易い
- 小規模アプリケーションから大規模な企業アプリケーションまでのあらゆるアーキテクチャに適合する
TypeORMは機能が沢山ありますが、この記事では、TypeScriptとTypeORMのQueryBuilderの使い方について説明させていただきます。
Query Builder
Query Builderは複雑なSQL文を簡単に作るために使われ、Connectionメソッドから初期化されます。
Connection
ConnectionメソッドでのQueryBuilderの使い方について、簡単な例を見てみましょう。
import {getConnection} from "typeorm"; const user = await getConnection() .createQueryBuilder() .from("user", "user") .select("user") .where("user.id = :id", { id: 1 }) .getOne();
Repository
以下のように、Query Builderを作成するためにRepositoryを使えます。
import {getRepository} from "typeorm"; const user = await getRepository(User) .createQueryBuilder("user") .where("user.id = :id", { id: 1 }) .getOne();
Adding expression use getConnection (no model needed)
table user: id, name, age
where は取得条件を満たすレコードを絞るために使われます。
getConnection().createQueryBuilder().from("user", "user").select("user") .where("user.id = :id", { id: 1 }) .getRawOne();
上記のクエリは下記のクエリに相当します。
select * from user where user.id=1;
orderBy はカラムの順序でレコードを並び替えるために使われます。
getConnection().createQueryBuilder().from("user", "user").orderBy("user.name");
上記のクエリは下記のクエリに相当します。
select * from user order by user.name;
groupBy は指定したカラムでレコードをグループ化するために使われます。
getConnection().createQueryBuilder().from("user", "user").groupBy("user.id")
上記のクエリは下記のクエリに相当します。
select * from user group by user.id;
limit は取得件数を指定するために使われます。
getConnection().createQueryBuilder().from("user", "user").limit(5);
上記のクエリは下記のクエリに相当します。
select * from user limit 5;
offset は取得を開始する位置を指定するために使われます。
getConnection().createQueryBuilder().from("user", "user").offset(5);
上記のクエリは下記のクエリに相当します。
select * from user offset 5;
joins は、関連する列に基づいて、2つ以上のテーブルの行を結合するために使われます。例えば、 「student」テーブルと「project」テーブルがあり、そのリレーションが1対多です。
student: id, name, email, phone
project : id, name, student_id
leftJoinAndSelect
const student = await getConnection().createQueryBuilder().from("student", "student") .leftJoinAndSelect("project", "project", "project.student_id = student.id") .where("student.name = :name", { name: "Student1" }) .getOne();
上記のクエリは下記のクエリに相当します。
SELECT student.*, project.* FROM student student LEFT JOIN project project ON project.student_id = student.id WHERE student.name = 'Student1'
innerJoinAndSelect
const student = await getConnection().createQueryBuilder().from("student", "student") .innerJoinAndSelect("project", "project", "project.student_id = student.id") .where("student.name = :name", { name: "student1" }) .getOne();
上記のクエリは下記のクエリに相当します。
SELECT student.*, project.* FROM student student INNER JOIN project project ON project.student_id = student.id WHERE student.name = 'student1';
Insert
import {getConnection} from "typeorm"; await getConnection() .createQueryBuilder() .insert() .into('student') .values([ { name: "test", email: "test@gmail.com", phone: "09011112222"}, { name: "test2", email: "test2@gmail.com", phone: "09011112222"} ]) .execute();
Update
import {getConnection} from "typeorm"; await getConnection().createQueryBuilder() .update('student') .set({ name: "test3", email: "test3@gmail.com"}) .where("id = :id", { id: 1 }) .execute();
Delete
import {getConnection} from "typeorm"; await getConnection() .createQueryBuilder() .delete() .from('student') .where("id = :id", { id: 1 }) .execute();
Subqueries
例1:subQueryを使うことで、「email」に”test”が含まれているレコードの「name」を「student」テーブルから取得します。
例2:subQueryを使うことで、「studentId」、「studentName」、「name」に”test”が含まれているレコードの大きな「projectId」を「project」テーブルから取得します。
const student = await getConnection().createQueryBuilder().select("student.name", "name") .from((subQuery) => { return subQuery.select("student.name", "name") .from("student", "student").where("student.email like :email", { email: '%test%'}) }, "student") .getRawMany(); const student = await getConnection().createQueryBuilder() .select(`student.id as studentId,student.name as studentName,project.id as projectId`)) .from("student", "student").leftJoin("project", "project", "project.student_id = student.id") .where ("project.id = (select max(id) from project where name like '%test%' ")).getRawMany();
Adding expression use getRepository() with model
例えば、以下のようなUserモデルがあります。
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm"; @Entity('user') export class User { @PrimaryGeneratedColumn() id: number; @Column('varchar', {name: 'name', nullable: true, length: 50}) name: string | null; @Column('int', {name: 'age', nullable: true}) name: number | null; }
where は取得条件を満たすレコードを絞るために使われます。
getRepository(User).createQueryBuilder("user").where("user.id= :id", { id: 1 });
上記のクエリは下記のクエリに相当します。
select * from user where user.id=1;
orderBy はカラムの順序でレコードを並び替えるために使われます。
getRepository(User).createQueryBuilder("user").orderBy("user.name");
上記のクエリは下記のクエリに相当します。
select * from user order by user.name;
groupBy は指定したカラムでレコードをグループ化するために使われます。
getRepository(User).createQueryBuilder("user") .groupBy("user.id")
上記のクエリは下記のクエリに相当します。
select * from user group by user.id;
limit は取得件数を指定するために使われます。
getRepository(User).createQueryBuilder("user").limit(5);
上記のクエリは下記のクエリに相当します。
select * from user limit 5;
offset は取得を開始する位置を指定するために使われます。
getRepository(User).createQueryBuilder("user") .offset(5);
上記のクエリは下記のクエリに相当します。
select * from user offset 5;
joins は、関連する列に基づいて、2つ以上のテーブルの行を結合するために使われます。以下の2つのエンティティを見ましょう。
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm"; import {Project} from "./Project"; @Entity('student') export class Student { @PrimaryGeneratedColumn() id: number; @Column('varchar', {name: 'name', nullable: true, length: 50}) name: string | null; @Column('varchar', {name: 'email', nullable: true, length: 30}) email: string | null; @Column('varchar', {name: 'phone', nullable: true, length: 11}) phone: string | null; @OneToMany(()=> Project, project => project.student) projects: project[]; }
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm"; import {Student} from "./Student"; @Entity('project') export class Project { @PrimaryGeneratedColumn() id: number; @Column('varchar', {name: 'name', nullable: true, length: 50}) name: string | null; @Column('bigint', {name: 'student_id', nullable: true, unsigned: true}) studentId: number | null; @ManyToOne(() => Student, student => student.projects) @JoinColumn([{name: 'student_id', referencedColumnName: 'id'}]) student : Student; }
leftJoinAndSelect
const student = await getRepository(Student).createQueryBuilder("student") .leftJoinAndSelect("student.projects", "project") .where("student.name = :name", { name: "Student1" }) .getOne();
上記のクエリは下記のクエリに相当します。
SELECT student.*, project.* FROM student student LEFT JOIN projects project ON project.student = student.id WHERE student.name = 'Student1'
innerJoinAndSelect
const student = await getRepository(Student).createQueryBuilder("student") .innerJoinAndSelect("student.projects", "project") .where("student.name = :name", { name: "student1" }) .getOne();
上記のクエリは下記のクエリに相当します。
SELECT student.*, project.* FROM students student INNER JOIN projects project ON project.student = student.id WHERE student.name = 'Student1';
Insert
import {getConnection} from "typeorm"; await getRepository(Student) .createQueryBuilder() .insert() .into(Student) .values([ { name: "test", email: "test@gmail.com", phone: "09011112222"}, { name: "test2", email: "test2@gmail.com", phone: "09011112222"} ]) .execute();
Update
import {getConnection} from "typeorm"; await getRepository(Student) .createQueryBuilder() .update(Student) .set({ name: "test3", email: "test3@gmail.com"}) .where("id = :id", { id: 1 }) .execute();
Delete
import {getConnection} from "typeorm"; await getRepository(Student).createQueryBuilder() .delete() .from(Student) .where("id = :id", { id: 1 }) .execute();
最後に
簡単なものから複雑なものまでクエリ文を作成できるため、Query Builderを使用することをお勧めします。
シンタックスは純粋なクエリとほぼ同じなので、実装しやすくて、可読性が高いです。
モデル宣言なしで普通どおりにQuery Builderを使うことができます。
パフォーマンス面でQuery Builder利用による抽出時間はTypeORMの既存APIリポジトリの利用よりも早いです。