記事概要
掲題の通り。
typeormが持つsynchronizeの機能を使えば、ヒトがテーブルを作成せずともスキーマさえあればテーブル定義を自動生成してくれる。
反面、テーブル定義変更にもとづく自動DROPが行われる場合もあるため、あくまでも開発最初期のスタートダッシュに留めるべき機能と考える。
こういう人向け
- 筆者自身。「こういうのができましたよ」と後で自他に確認する。
- typeormを他の人はどんな感じに書いてるのか知りたい人
- CREATE TABLEを最初っから書くのが面倒で、entityを書けば自動生成してくれないかなと思ってる人
実行例
出力Entity
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
}
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;
}
接続設定
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
}
接続時(なんとなく日時情報は抜いている)
[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
出来上がったテーブル
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
自動生成されました!
けど、ちょっとテーブル定義を変更する。
// 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;
実行結果
[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で書き換えてくれた、けど…
[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を使っても良いかもしれない。当然バックアップは取っておく。
上記の方のように、@Entity({name: 'users', synchronize: false})
のようにsynchronizeを対処しても良いかもしれない。
終わりに
Entityを出発点とした自動データベース構築は個人的に非常に好ましいと考える。
自然とEntityとテーブル定義の内容が合致しているため、ありがちなカラム抜けやEntityの手抜きが激減する。
DBの細かい設定にも対応したtypeormのentityは今後も期待している。