[ typescript ] [ PostgreSQL ] TypeOrmを試してください.



イントロ
今回は、タイプスクリプト( node . js )プロジェクトからPostgreSQLへのアクセスを試みます.

環境
  • ノード.jsバージョン.16.2.0
  • タイプスクリプトver .4.3.2
  • tsノード10.0.0
  • タイプRM.0.2.32
  • pg ver8.6.0
  • メタデータverを反映します.0.1.13

  • 準備
    ドキュメントによると、私はtypeRMをインストールし、いくつかのファイルを追加しました.
  • 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.

  • Ormconfig.JSON
    {
        "type": "postgres",
        "host": "localhost",
        "port": 5432,
        "username": "postgres",
        "password": "example",
        "database": "print_sample",
        "synchronize": true,
        "logging": true,
        "entities": [
           "ts/src/entity/**/*.ts"
        ],
        "migrations": [
           "src/migration/**/*.ts"
        ],
        "subscribers": [
           "ts/src/subscriber/**/*.ts"
        ],
        "cli": {
           "entitiesDir": "src/entity",
           "migrationsDir": "src/migration",
           "subscribersDir": "src/subscriber"
        }
     }
    

    TSconfigJSON
    {
      "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "lib": ["DOM", "ES5", "ES2015"],
        "sourceMap": true,
        "outDir": "./js",
        "strict": true,
        "noImplicitAny": true,"strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictPropertyInitialization": true,
        "noImplicitThis": true,
        "alwaysStrict": true,
        "moduleResolution": "node",
        "esModuleInterop": true,
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
      }
    }
    

    インデックス.TS
    import "reflect-metadata";
    import {createConnection} from "typeorm";
    import { ExecutionItem } from "./src/entity/executionItem";
    
    function start() {
        createConnection().then(async connection => {
            // create a new record
            const sampleItem = new ExecutionItem();
            sampleItem.filePath = 'filepath.pdf';
            sampleItem.executionOrder = 1;
            sampleItem.printType = 2;
            sampleItem.lastUpdateDate = new Date();
            sampleItem.remarks = 'hello';
            await connection.manager.save(sampleItem);    
        }).catch(error => console.error(error));
    }
    start();
    

    移動

    最初に?
    既存のデータベースからエンティティークラスを生成する方法を見つけることができませんでした.
    この問題によると、私は“typeRMモデルジェネレータ”を使用することができます.
    しかしメンテナンスフェーズに入っている.
  • Converting database to typeorm entities (Reverse engineering) · Issue #540 · typeorm/typeorm · GitHub

  • コードファースト
    これはEntity Frameworkコアと同じです.
  • エンティティークラスの作成または更新
  • 移行ファイルの作成
  • ラン

  • エンティティクラスの作成または更新
    Entity Frameworkコアと同じように、各テーブルと列の名前、データ型などを指定できます.

    executionItem .TS
    import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
    @Entity('execution_item')
    export class ExecutionItem {
        @PrimaryGeneratedColumn()
        id: number = -1;
        @Column({
            name: 'file_path',
            type: 'text',
            nullable: false
        })
        filePath: string = '';
        @Column({
            name: 'execution_order',
            type: 'integer',
            nullable: false,
        })
        executionOrder: number = 0;
        @Column({
            name: 'print_type',
            type: 'integer',
            nullable: false,
        })
        printType: number = 0;
        @Column({
            name: 'finished_time',
            nullable: true,
            type: 'timestamp with time zone'
        })
        finishedTime: Date|null = null;
        @Column({
            name: 'error_message',
            type: 'text',
            nullable: true
        })
        errorMessage: string|null = null;
        @Column({
            name: 'last_update_date',
            nullable: false,
            type: 'timestamp with time zone'
        })
        lastUpdateDate: Date = new Date();
    }
    
    つの重要なことは、データ型を明示的に指定するとき、すべての列を指定する必要があります.
    さもないとエラーが出ます.
    DataTypeNotSupportedError: Data type "Object" in "ExecutionItem.errorMessage" is not supported by "postgres" database.
        at new DataTypeNotSupportedError (C:\Users\example\OneDrive\Documents\workspace\node-typeorm-sample\node_modules\typeorm\error\DataTypeNotSupportedError.js:8:28)
    ...
    
  • Column options
  • Column types for postgres

  • 2 .移行ファイルの作成
    私は2つの方法で移行ファイルを作成できます.
    "移行:作成"空の移行ファイルを生成します.
    npx typeorm migration:create -n CreateAddRemarks
    

    1622389988549TS
    import {MigrationInterface, QueryRunner} from "typeorm";
    
    export class CreateAddRemarks1622389988549 implements MigrationInterface {
        public async up(queryRunner: QueryRunner): Promise<void> {
        }
    
        public async down(queryRunner: QueryRunner): Promise<void> {
        }
    }
    
    それは何もしない.
    それで、私は自分で移住ファイルを書かなければなりません.
    「移行:生成」は、最後の移行からの違いによって更新操作を生成します.
    npx typeorm migration:generate -n GenerateAddRemarks
    
    import {MigrationInterface, QueryRunner} from "typeorm";
    
    export class GenerateAddRemarks1622382632575 implements MigrationInterface {
        name = 'GenerateAddRemarks1622382632575'
    
        public async up(queryRunner: QueryRunner): Promise<void> {
            await queryRunner.query(`ALTER TABLE "execution_item" ADD "remarks" text`);
        }
    
        public async down(queryRunner: QueryRunner): Promise<void> {
            await queryRunner.query(`ALTER TABLE "execution_item" DROP COLUMN "remarks"`);
        }
    
    }
    

    ラン
    “ts loader”を使っているので、このコマンドを実行すればerrrorを取得します.
    npx typeorm migration:run
    
    そこで、以下のコマンドを変更します.
    npx ts-node ./node_modules/typeorm/cli.js migration:run
    
  • # Running and reverting migrations
  • Migrations

  • クラッド

    取引
    typeRMは、トランザクションを作成し、使用するいくつかの方法があります.
    私にとって分かりやすいので、QueryRunnerを使って選びました.

    印刷する.サービスTS
    import { Connection } from "typeorm";
    import { ExecutionItem } from "../entity/executionItem";
    
    export async function create(connection: Connection): Promise<void> {
        const queryRunner = connection.createQueryRunner();
        await queryRunner.startTransaction();
        try
        {
    ...
            await queryRunner.manager.save(sampleItem);
            queryRunner.commitTransaction();
        } catch(error) {
            await queryRunner.rollbackTransaction();
            console.error(error);
        }
    }
    
  • Transactions
  • データベースにアクセスするとき、エラーを避けるために単一の接続インスタンスを共有しなければなりません.
    C:\Users\example\OneDrive\Documents\workspace\node-typeorm-sample\src\error\AlreadyHasActiveConnectionError.ts:8
            super();
            ^
    AlreadyHasActiveConnectionError: Cannot create a new connection named "default", because connection with such name already exist and it now has an active connection session.
        at new AlreadyHasActiveConnectionError (C:\Users\example\OneDrive\Documents\workspace\node-typeorm-sample\src\error\AlreadyHasActiveConnectionError.ts:8:9)
        at ConnectionManager.create (C:\Users\example\OneDrive\Documents\workspace\node-typeorm-sample\src\connection\ConnectionManager.ts:57:23)
    ...
    
    そこで最初に接続を作成し、すべてのメソッドの引数として設定します.

    インデックス.TS
    import "reflect-metadata";
    import {createConnection} from "typeorm";
    import * as prints from './src/prints/print.service';
    
    async function start() {
        const connection = await createConnection();
        const item01 = await prints.getItem(connection, 205);
        if(item01 == null) {
            await prints.create(connection);
        } else {
            item01.finishedTime = new Date();
            await prints.update(connection, item01);
        }
        await prints.deleteTarget(connection, 204);
        process.exit(0);
    }
    start();
    

    挿入、更新、削除
    import { Connection } from "typeorm";
    import { ExecutionItem } from "../entity/executionItem";
    
    export async function create(connection: Connection): Promise<void> {
        const queryRunner = connection.createQueryRunner();
        await queryRunner.startTransaction();
        try
        {
            const sampleItem = new ExecutionItem();
            sampleItem.filePath = 'filepath.pdf';
            sampleItem.executionOrder = 1;
            sampleItem.printType = 2;
            sampleItem.lastUpdateDate = new Date();
            sampleItem.remarks = 'hello';
            await queryRunner.manager.save(sampleItem);
            queryRunner.commitTransaction();
        } catch(error) {
            await queryRunner.rollbackTransaction();
            console.error(error);
        }
    }
    export async function getItem(connection: Connection, id: number): Promise<ExecutionItem|null> {
        const result = await connection.getRepository(ExecutionItem)
            .createQueryBuilder('execution_item')
            .where('execution_item.id = :id', { id })
            .getOne();        
        if(result == null) {
            return null;
        }
        return result;
    }
    export async function update(connection: Connection, target: ExecutionItem) {
        const queryRunner = connection.createQueryRunner();
        await queryRunner.startTransaction();
        try
        {
            const updateTarget = await getItem(connection, target.id);
            if(updateTarget == null) {
                console.error('target was not found');
                return;
            }
            updateTarget.filePath = target.filePath;
            updateTarget.executionOrder = target.executionOrder;
            updateTarget.printType = target.printType;
            updateTarget.finishedTime = target.finishedTime;
            updateTarget.errorMessage = target.errorMessage;
            updateTarget.lastUpdateDate = new Date();
            updateTarget.remarks = target.remarks;
            await queryRunner.manager.save(updateTarget);
            queryRunner.commitTransaction();
        } catch(error) {
            await queryRunner.rollbackTransaction();
            console.error(error);
        }
    }
    export async function deleteTarget(connection: Connection, targetId: number) {
        const queryRunner = connection.createQueryRunner();
        await queryRunner.startTransaction();
        try
        {
            const target = await getItem(connection, targetId);
            if(target == null) {
                console.error('target was not found');
                return;
            }
            await queryRunner.manager.remove(target);
            queryRunner.commitTransaction();
        } catch(error) {
            await queryRunner.rollbackTransaction();
            console.error(error);
        }
    }