TypeORMおよびTypeORMのQuery Builder

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フレームワークを利用しやすいです。以下にその利点を示します。

  1. 高品質で疎結合のアプリケーション
  2. 拡張可能なアプリケーション
  3. 他のモジュールとの結合が易い
  4. 小規模アプリケーションから大規模な企業アプリケーションまでのあらゆるアーキテクチャに適合する

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リポジトリの利用よりも早いです。

参照元

https://typeorm.io/