TypeORMに触れてみる 1
はじめに
久々すぎて書き方も忘れつつある今日このごろ。 プライベートでは TypeScript ばかり触れているわけなのですが、今回は TypeORM を試すことにしました。
あらかじめ DB(テーブル)を用意する方法もありますが、今回はコードから生成してみます。
- GitHub - typeorm/typeorm
- TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.
インストールとプロジェクトの作成
インストール
もちろん TypeScript や TypeORM 、 PostgreSQL のドライバーは必要なのですが、それに加えて "reflect-metadata" が必要になります。
npm install --save typeorm reflect-metadata pg typescript tsc npm install --save-dev @types/node
あと必須ではありませんが、 JavaScript に変換せずに TypeScript そのままで実行できるよう、"ts-node" も入れておきます。
npm install --save ts-node
準備
TypeORM のコマンドでプロジェクトを作ることもできます。
npx typeorm init --name gen-typeorm-sample --database postgres
今回はこれで生成されるファイルを参考に、順を追ってプロジェクトを作成してみます。
フォルダーとファイルの追加
とりあえず tsconfig.json を作っておきます。
npx tsc --init
lib やデコレーターの有効化などを行います。
tsconfig.json
{ "compilerOptions": { /* Basic Options */ "incremental": true, "target": "es5", "module": "commonjs", "lib": [ "es5", "es6" ], "sourceMap": true, "outDir": "./build", /* Strict Type-Checking Options */ "strict": true, "noImplicitAny": true, "strictNullChecks": true, "alwaysStrict": true, /* Additional Checks */ "noUnusedLocals": true, /* Module Resolution Options */ "moduleResolution": "node", "esModuleInterop": true, /* Experimental Options */ "experimentalDecorators": true, "emitDecoratorMetadata": true, /* Advanced Options */ "skipLibCheck": true, "forceConsistentCasingInFileNames": true } }
で、 TypeORM で使用するファイルを格納するフォルダーを追加しておきます。
typeorm-sample(my project name) L node_modules L src L entity L migration L package-lock.json L package.json L tsconfig.json
PostgreSQL の接続文字列などを設定する、 ormconfig.json を追加します。
ormconfig.json
{ "type": "postgres", "host": "localhost", "port": 5432, "username": "test", "password": "test", "database": "test", "synchronize": true, "logging": true, "cache": false, "entities": [ "src/entity/**/*.ts" ], "migrations": [ "src/migration/**/*.ts" ], "subscribers": [ "src/subscriber/**/*.ts" ], "cli": { "entitiesDir": "src/entity", "migrationsDir": "src/migration", "subscribersDir": "src/subscriber" } }
※テーブルはコードから生成できますが、データベースは先に作っておく必要があります。
というのを忘れて実行したせいでエラーになりました\(^o^)/
Entity クラスを追加する
sample-user.ts
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm"; @Entity("SampleUser") export class SampleUser { @PrimaryGeneratedColumn() id: number = -1; @Column() firstName: string = ''; @Column() lastName: string = ''; @Column() age: number = -1; }
テーブル名はファイル名から決定されます。
そのため、今回のサンプルでそのまま実行するとテーブル名は "sample_user" となります。
@Entity() に "SampleUser" のように設定することで、テーブル名を指定できます。
余談ですが、 TypeORM のコマンドでプロジェクトを生成すると "user" というテーブルが作られることになるのですが、 "SELECT * FROM user" としてしまうと PostgreSQL のユーザー情報が出てきてしまうので、コマンドで作る場合もテーブルの名前は変えたほうが良いかもしれません。
テーブルを作成する
テーブルは最初にデータベースのテーブルにアクセスしたときに作成されます。
[2020-06-05 Update] テーブルが作成されるのはデータベースに接続したとき、でした。。。
index.ts
import "reflect-metadata"; import {createConnection} from "typeorm"; import { SampleUser } from "./entity/sample-user"; createConnection().then(async connection => { const user = new SampleUser(); user.firstName = "Timber"; user.lastName = "Saw"; user.age = 25; // テーブルが生成される. await connection.manager.save(user); // 上のコードがない場合、ここで空のテーブルが生成される. const users = await connection.manager.find(User); console.log("Loaded users: ", users); }).catch(error => console.log(error));
Primary key(自動採番)
"@PrimaryGeneratedColumn()" を指定すると、カラムの型は "serial" になり自動採番されます。
注意が必要なのは、この値(今回のサンプルでは "id" )は自分で値を変えることができない、ということです。
Create のときは "id" に入れた値が無視され、 Update のときは "id" の値を変えると別物として扱われます(新規登録される)。
NOT NULL
デフォルトでは "NOT NULL" としてカラムの型が設定されます。
Nullable にする必要がある場合は @Column() にオプションを追加します。
sample-user.ts
@Entity("SampleUser") export class SampleUser { ... @Column({ nullable: true }) age: number = -1; }
@Column() で指定できるオプションはデフォルト値や型などいろいろな種類があります。 typeorm/entities.md at master · typeorm/typeorm · GitHub
また、 PostgreSQL で使用できる型の種類はこちらから。
typeorm/entities.md at master · typeorm/typeorm · GitHub
トランザクションを利用する
トランザクションを利用するのはどうすれば良いでしょうか。
調べてみるといくつか方法があるようですが、今回は QueryRunner を使うことにしました。
index.ts
import "reflect-metadata"; import {createConnection } from "typeorm"; import { SampleUser } from "./entity/sample-user"; createConnection().then(async connection => { const queryRunner = connection.createQueryRunner(); await queryRunner.startTransaction(); try{ const secondUser = new SampleUser(); secondUser.firstName = 'Hello2'; secondUser.lastName = 'World2'; secondUser.age = 36; await queryRunner.manager.save(secondUser); queryRunner.commitTransaction(); }catch(error) { await queryRunner.rollbackTransaction(); } finally { await queryRunner.release(); } }).catch(error => console.log(error));
注意点として、 "queryRunner.startTransaction()" や "queryRunner.rollbackTransaction()" は "connection.manager" には効かない、ということです。 上記の "queryRunner.manager.save(secondUser)" を "connection.manager.save(secondUser)" にしても動作はしますが、途中で例外が発生してもロールバックはされません。
続きます。