【TypeORM】typescriptの開発時「synchronize: true」はEntityにもとづいてテーブルを自動生成してくれる。「synchronize: true」の強さの紹介と「synchronize: false」にする重要さの紹介

# 記事概要

掲題の通り。
typeormが持つsynchronizeの機能を使えば、ヒトがテーブルを作成せずともスキーマさえあればテーブル定義を自動生成してくれる。

反面、テーブル定義変更にもとづく自動DROPが行われる場合もあるため、あくまでも開発最初期のスタートダッシュに留めるべき機能と考える。

## こういう人向け

* 筆者自身。「こういうのができましたよ」と後で自他に確認する。
* typeormを他の人はどんな感じに書いてるのか知りたい人
* CREATE TABLEを最初っから書くのが面倒で、entityを書けば自動生成してくれないかなと思ってる人

# 実行例

### 出力Entity

```ts
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, Index } from "typeorm";

import { date_dictionary } from "./datadirectory"

// コードマスタ定義。
@Entity({ name: "m_code" })
// インデックスも張れる。ここでは大分類codeGroupIdと小分類codeIdに分けているイメージ。
@Index("codeid-pair" ,["codeGroupId", "codeId"], {  unique: true })
export class m_code extends BaseEntity {

  // primary keyやtype、サイズ、コメントも設定できる。
  @PrimaryGeneratedColumn({ name: "id", type: "bigint", comment: '自動採番ID' })
  id!: number;
  @Column({ nullable: false, type: "varchar", length: 7, comment: "グループコードID" })
  codeGroupId!: string;
  @Column({ nullable: false, type: "varchar", length: 7, comment: "コードID" })
  codeId!: string;
  @Column({ nullable: true, type: "varchar", length: 85, comment: "コード名" })
  codeName!: string;
  @Column({ nullable: false, type: "int", comment: "表示順" })
  hyoujiJyun!: number;
  // 共通カラム使用。デフォルトだとカラム名が連結される（dateTypeUpdatorみたいになる）ので、prefix:falseにしている
  @Column(() => date_dictionary, { prefix: false })
  dateType!: date_dictionary
}
```


```ts
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, UpdateDateColumn, CreateDateColumn } from "typeorm";

// @Entityが無い、テーブルとしては登録されないクラス。共通カラムを別々の場所で使う時に使える。
export class date_dictionary  {

    @Column({ nullable: true, type: "varchar", length: 12,comment: "作成者"  })
    creator!: string;
    @CreateDateColumn({ nullable: true, type: "datetime",comment: "作成日時"  })
    createdAt!: Date;
    @Column({ nullable: true, type: "varchar", length: 12 ,comment: "更新者" })
    updator!: string;
    @UpdateDateColumn({ nullable: true, type: "datetime",comment: "更新日時"  })
    updatedAt!: Date;
  }
```

### 接続設定

```ts

import dotenv from 'dotenv'
import { ConnectionOptions } from "typeorm";
import {Log4jsLogger} from "./common/logger"

// nuxt.js v14.12なので.envを有効活用している。
// 接続設定を環境変数process.env.～にすることで他環境へも適用できるようにする。
dotenv.config({ path: __dirname + '/.env' });

// https://typeorm.io/#/connection-options
const config: ConnectionOptions = {
    type: "mysql",
    host: process.env.CONNECT_HOST,
    port: Number(process.env.CONNECT_PORT),
    username: process.env.CONNECT_USER,
    password: process.env.CONNECT_PASS,
    database: process.env.CONNECT_DATABASE,
    // 指定したフォルダ内の@Entityを認識してくれる。
    // Nuxt.jsのservermiddlewareにて動かしてる扱いなので以下のようなフォルダ階層としている。models命名はsequelizeで開発してた時の名残
    entities: [
        "server/typeorm/models/*.*"
    ],
    // synchronize: trueの時、上記entityを正としてスキーマに接続、同期させる。
    // デバッグや開発時に役立つ。

    // 公式ドキュメントに書いてあるが、本番環境では絶対にfalseにしておくこと。
    synchronize: true,
    logging: true,
    // https://typeorm.io/#/logging
    // log4jsを用いてガチャガチャカスタムしてるので割愛。
    // Visual Code Studio + typescriptのクイックフィックスで編集必要なメソッドはわかる。
    logger:new Log4jsLogger(),
    timezone:"+09:00"
}
export default {
    config: config
}
```

### 接続時（なんとなく日時情報は抜いている）
```sql
[DEBUG] debug - START TRANSACTION
[DEBUG] debug - undefined
[DEBUG] debug - SELECT DATABASE() AS `db_name`
[DEBUG] debug - undefined
[DEBUG] debug - 
                        SELECT `TABLE_SCHEMA`,
                               `TABLE_NAME`
                        FROM `INFORMATION_SCHEMA`.`TABLES`
                        WHERE `TABLE_SCHEMA` = 'nuxt_typescript_training'
                          AND `TABLE_NAME` = 'm_code'
                    
[DEBUG] debug - undefined
[DEBUG] debug - SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'nuxt_typescript_training' AND `TABLE_NAME` = 'typeorm_metadata'
[DEBUG] debug - undefined
[DEBUG] debug - SELECT SCHEMA() AS `schema_name`
[DEBUG] debug - undefined
[DEBUG] debug - CREATE TABLE `nuxt_typescript_training`.`m_code` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自動採番ID', `codeGroupId` varchar(7) NOT NULL COMMENT 'グループコードID', `codeId` varchar(7) NOT NULL COMMENT 'コードID', `codeName` varchar(85) NULL COMMENT 'コード名', `hyoujiJyun` int NOT NULL COMMENT '表示順', `creator` varchar(12) NULL COMMENT '作成者', `createdAt` datetime(6) NULL COMMENT '作成日時' DEFAULT CURRENT_TIMESTAMP(6), `updator` varchar(12) NULL COMMENT '更新者', `updatedAt` datetime(6) NULL COMMENT '更新日時' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), UNIQUE INDEX `codeid-pair` (`codeGroupId`, `codeId`), PRIMARY KEY (`id`)) ENGINE=InnoDB
[DEBUG] debug - undefined
[DEBUG] debug - COMMIT
```

## 出来上がったテーブル

```sql
CREATE TABLE `m_code` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自動採番ID',
  `codeGroupId` varchar(7) NOT NULL COMMENT 'グループコードID',
  `codeId` varchar(7) NOT NULL COMMENT 'コードID',
  `codeName` varchar(85) DEFAULT NULL COMMENT 'コード名',
  `hyoujiJyun` int(11) NOT NULL COMMENT '表示順',
  `creator` varchar(12) DEFAULT NULL COMMENT '作成者',
  `createdAt` datetime(6) DEFAULT current_timestamp(6) COMMENT '作成日時',
  `updator` varchar(12) DEFAULT NULL COMMENT '更新者',
  `updatedAt` datetime(6) DEFAULT current_timestamp(6) ON UPDATE current_timestamp(6) COMMENT '更新日時',
  PRIMARY KEY (`id`),
  UNIQUE KEY `codeid-pair` (`codeGroupId`,`codeId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
```

### 自動生成されました！
### けど、ちょっとテーブル定義を変更する。

```ts
// lengthを7→10に伸ばす。
  @Column({ nullable: false, type: "varchar", length: 10, comment: "グループコードID" })
  codeGroupId!: string;
  @Column({ nullable: false, type: "varchar", length: 10, comment: "コードID" })
  codeId!: string;
```

### 実行結果

```sql
[DEBUG] debug - START TRANSACTION
[DEBUG] debug - undefined
[DEBUG] debug - SELECT DATABASE() AS `db_name`
[DEBUG] debug - undefined
[DEBUG] debug - 
                        SELECT `TABLE_SCHEMA`,
                               `TABLE_NAME`
                        FROM `INFORMATION_SCHEMA`.`TABLES`
                        WHERE `TABLE_SCHEMA` = 'nuxt_typescript_training'
                          AND `TABLE_NAME` = 'm_code'
                    
[DEBUG] debug - undefined
[DEBUG] debug - 
                SELECT
                    *
                FROM
                    `INFORMATION_SCHEMA`.`COLUMNS`
                WHERE
                    `TABLE_SCHEMA` = 'nuxt_typescript_training'
                    AND
                    `TABLE_NAME` = 'm_code'
                
[DEBUG] debug - undefined
[DEBUG] debug - SELECT * FROM (
                SELECT
                    *
                FROM `INFORMATION_SCHEMA`.`KEY_COLUMN_USAGE` `kcu`
                WHERE
                    `kcu`.`TABLE_SCHEMA` = 'nuxt_typescript_training'
                    AND
                    `kcu`.`TABLE_NAME` = 'm_code'
            ) `kcu` WHERE `CONSTRAINT_NAME` = 'PRIMARY'
[DEBUG] debug - undefined
[DEBUG] debug - 
            SELECT
                `SCHEMA_NAME`,
                `DEFAULT_CHARACTER_SET_NAME` as `CHARSET`,
                `DEFAULT_COLLATION_NAME` AS `COLLATION`
            FROM `INFORMATION_SCHEMA`.`SCHEMATA`
            
[DEBUG] debug - undefined
[DEBUG] debug - 
            SELECT
                `s`.*
            FROM (
                SELECT
                    *
                FROM `INFORMATION_SCHEMA`.`STATISTICS`
                WHERE
                    `TABLE_SCHEMA` = 'nuxt_typescript_training'
                    AND
                    `TABLE_NAME` = 'm_code'
            ) `s`
            LEFT JOIN (
                SELECT
                    *
                FROM `INFORMATION_SCHEMA`.`REFERENTIAL_CONSTRAINTS`
                WHERE
                    `CONSTRAINT_SCHEMA` = 'nuxt_typescript_training'
                    AND
                    `TABLE_NAME` = 'm_code'
            ) `rc`
                ON
                    `s`.`INDEX_NAME` = `rc`.`CONSTRAINT_NAME`
                    AND
                    `s`.`TABLE_SCHEMA` = `rc`.`CONSTRAINT_SCHEMA`
            WHERE
                `s`.`INDEX_NAME` != 'PRIMARY'
                AND
                `rc`.`CONSTRAINT_NAME` IS NULL
            
[DEBUG] debug - undefined
[DEBUG] debug - 
            SELECT
                `kcu`.`TABLE_SCHEMA`,
                `kcu`.`TABLE_NAME`,
                `kcu`.`CONSTRAINT_NAME`,
                `kcu`.`COLUMN_NAME`,
                `kcu`.`REFERENCED_TABLE_SCHEMA`,
                `kcu`.`REFERENCED_TABLE_NAME`,
                `kcu`.`REFERENCED_COLUMN_NAME`,
                `rc`.`DELETE_RULE` `ON_DELETE`,
                `rc`.`UPDATE_RULE` `ON_UPDATE`
            FROM (
                SELECT
                    *
                FROM `INFORMATION_SCHEMA`.`KEY_COLUMN_USAGE` `kcu`
                WHERE
                    `kcu`.`TABLE_SCHEMA` = 'nuxt_typescript_training'
                    AND
                    `kcu`.`TABLE_NAME` = 'm_code'
            ) `kcu`
            INNER JOIN (
                SELECT
                    *
                FROM `INFORMATION_SCHEMA`.`REFERENTIAL_CONSTRAINTS`
                WHERE
                    `CONSTRAINT_SCHEMA` = 'nuxt_typescript_training'
                    AND
                    `TABLE_NAME` = 'm_code'
            ) `rc`
                ON
                    `rc`.`CONSTRAINT_SCHEMA` = `kcu`.`CONSTRAINT_SCHEMA`
                    AND
                    `rc`.`TABLE_NAME` = `kcu`.`TABLE_NAME`
                    AND
                    `rc`.`CONSTRAINT_NAME` = `kcu`.`CONSTRAINT_NAME`
            
[DEBUG] debug - undefined
[DEBUG] debug - SELECT VERSION() AS `version`
[DEBUG] debug - undefined
[DEBUG] debug - SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'nuxt_typescript_training' AND `TABLE_NAME` = 'typeorm_metadata'
[DEBUG] debug - undefined
[DEBUG] debug - SELECT SCHEMA() AS `schema_name`
[DEBUG] debug - undefined
[DEBUG] debug - DROP INDEX `codeid-pair` ON `nuxt_typescript_training`.`m_code`
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` DROP COLUMN `codeGroupId`
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` ADD `codeGroupId` varchar(10) NOT NULL COMMENT 'グループコードID'
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` DROP COLUMN `codeId`
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` ADD `codeId` varchar(10) NOT NULL COMMENT 'コードID'
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` CHANGE `codeName` `codeName` varchar(85) NULL COMMENT 'コード名'
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` CHANGE `creator` `creator` varchar(12) NULL COMMENT '作成者'
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` CHANGE `updator` `updator` varchar(12) NULL COMMENT '更新者'
[DEBUG] debug - undefined
[DEBUG] debug - CREATE UNIQUE INDEX `codeid-pair` ON `nuxt_typescript_training`.`m_code` (`codeGroupId`, `codeId`)
[DEBUG] debug - undefined
[DEBUG] debug - COMMIT
```

### ALTERで書き換えてくれた、けど…

```sql
[DEBUG] debug - DROP INDEX `codeid-pair` ON `nuxt_typescript_training`.`m_code`
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` DROP COLUMN `codeGroupId`
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` ADD `codeGroupId` varchar(10) NOT NULL COMMENT 'グループコードID'
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` DROP COLUMN `codeId`
[DEBUG] debug - undefined
[DEBUG] debug - ALTER TABLE `nuxt_typescript_training`.`m_code` ADD `codeId` varchar(10) NOT NULL COMMENT 'コードID'
[DEBUG] debug - undefined
```

###「synchronize: true」だとテーブル定義変更時は（少なくとも0.2.37段階のmysql対象だと）DROPしたあとに入れ直してくる！

当然新規作成されたレコードはnullないし初期値になるので、十分に留意する必要がある。
あと順序を考慮していないので、新しい列は最下部にドシドシ追加されていく形になる。


## 動作環境

https://typeorm.io/#/
上記公式ドキュメントの「Installation」を一読。
以下はあくまで2021年筆者の環境であるため、どこかの推奨というわけではない。

```
    "express": "^4.17.1",
    "mysql2": "^2.3.0",
    "nuxt": "<2.15.0",
    "reflect-metadata": "^0.1.13",
    "typeorm": "^0.2.37",
```


# 上記仕様を知った上での個人的考察

* 開発初期に「synchronize: true」でガシガシ作る。この時にEntityをしっかり作り込む。
* 出来上がったCREATE TABLEをDDLとして設計書に起こす。この時に「synchronize: false」にする
* あとは必要に応じて人の手でALTER TABLEなりを行う。この時Migrationを使っても良いかもしれない。当然バックアップは取っておく。

https://qiita.com/jnst/items/9a4c1a9f15b165e0e420

上記の方のように、`@Entity({name: 'users', synchronize: false})`のようにsynchronizeを対処しても良いかもしれない。


# 終わりに

Entityを出発点とした自動データベース構築は個人的に非常に好ましいと考える。
自然とEntityとテーブル定義の内容が合致しているため、ありがちなカラム抜けやEntityの手抜きが激減する。
DBの細かい設定にも対応したtypeormのentityは今後も期待している。