Sequelize + Typescript の実装サンプル

Sequelize + Typescript の実装サンプル

〇サンプルに使用するテーブル

CREATE TABLE shopping_items (
    id bigint IDENTITY(1,1) NOT NULL,
    name varchar(40) COLLATE Japanese_CI_AS NOT NULL,
    price int NOT NULL,
    suryo int NULL,
    description varchar(100) COLLATE Japanese_CI_AS NULL,
    version int NOT NULL,
    CONSTRAINT shopping_items_pk PRIMARY KEY (id)
);

Sequelizeのインストール

Sequelize

npm install sequelize
npm install -D @types/sequelize

〇package.json

{
  "name": "aa",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "sequelize": "^6.20.1",
    "tedious": "^14.5.0",
    "typescript": "^4.7.3"
  }
}

接続先の設定

import { Sequelize, DataTypes, QueryTypes } from 'sequelize';

// 接続先の設定
const sequelize = new Sequelize('Test', 'aa00001', 'Xaa00001', {
    host: 'localhost',
    dialect: 'mssql',
    // コネクションプ-ルの作成
    pool: {
        max: 5,
        min: 0,
        acquire: 60000,
        idle: 30000
    }
});

単一のプロセスからデータベースに接続する場合は、Sequelizeインスタンスを1つだけ作成する必要がある。
複数のプロセスからデータベースに接続する場合は、プロセスごとにSequelizeインスタンスを作成して使い分ける必要がある。

モデルの定義

別途ツールを使用することで、DBに接続してモデルを自動生成することもできる。

github.com

// モデルの定義
const ShoppingItems = sequelize.define('ShoppingItems', {
    id: {
        type: DataTypes.BIGINT,
        primaryKey: true,
        allowNull: false,
        autoIncrement: true
    },
    name: {
        type: DataTypes.STRING,
        allowNull: false
    },
    price: {
        type: DataTypes.INTEGER,
        allowNull: false
    },
    suryo: {
        type: DataTypes.INTEGER
    },
    description: {
        type: DataTypes.STRING
    },
    version: {
        type: DataTypes.INTEGER,
        allowNull: false
    }
}, {
    timestamps: false, // デフォルト:trueの場合「createdAtとupdatedAtを自動で付加する」
    tableName: 'shopping_items', // デフォルト:trueの場合Sequelizeは自動的に複数形になるようにテーブル名を書き換える
    version: true // trueを設定することで楽観排他ができるようになる(実装が悪いのか実現できていない・・・)
});

DB操作処理の記述

(async () => {
    try {
        // ◇ insertサンプル
        await ShoppingItems.create({
            name: 'ラムネ',
            price: 80,
            suryo: 100,
            description: 'ブドウ糖',
            version: 1
        });

        // ◇ updateサンプル
        await ShoppingItems.update({
            name: 'ラムネ',
            price: 120,
            suryo: 1000,
            description: 'みかん味'
        }, {
            where: {
                id : 21
        });

        // ◇ deleteサンプル
        await ShoppingItems.destroy({
            where: {
                id : 10
            }
        )

        // ◇ bulkInsertサンプル
        await ShoppingItems.bulkCreate([{
            name: '白米',
            price: 120,
            suryo: 10,
            description: '新潟県産',
            version: 1
        }, 
        {
            name: 'うまい棒',
            price: 10,
            suryo: 10,
            description: '高カロリー',
            version: 1
        }]);

        // ◇ selectサンプル
        await ShoppingItems.findByPk(14)
        .then((result) => {
            if(result){
                console.log(result);
            }
        })
        .catch((error) => {
            console.log(error)
        });

        // ◇ クエリを書いて実行サンプル
        const records = await sequelize.query('SELECT * FROM shopping_items WHERE price = ?', {
            replacements: [100],
            type: QueryTypes.SELECT,
        })
        records.forEach((record) => {
            console.log(JSON.stringify(record, null, 2));
        })
    });
} catch (error) {
    console.log(error);
}
sequelize.close();
})();

バインド変数は「?」以外に:を使用することで名前付きパラメータも使用することが出来る。

// ◇ クエリを書いて実行サンプル
const records = await sequelize.query('SELECT * FROM shopping_items WHERE price = :price', {
    replacements: { price: 100 },
    type: QueryTypes.SELECT,
});

トランザクション処理

コールバック関数に渡した処理が最後まで例外発生せずに完了したらコミット、途中で例外が発生したらロールバックされる。
内部で実行する処理には、オプションで「transaction: t」を渡す必要がある。

「transaction: t」をつけ忘れた場合はトランザクション管理から外れる(トランザクションIDが「transaction: t」を書いた処理とは異なる)ため、 「transaction: t」をつけ忘れた処理が成功すればコミット、例外が発生すればロールバックされる。
「transaction: t」をつけ忘れた処理で例外が発生した場合、「transaction: t」を書いた処理で例外が発生するものがなかった場合、「transaction: t」を書いた処理はロールバックされないため、データ不整合になる可能性がある。

「transaction: t」を自動で付与するための記述方法も存在している。

sequelize.org

(async () => {
    try {
        // トランザクションのサンプル
        // コールバック関数に渡した処理が最後まで例外発生せずに完了したらコミット、
        // 処理途中で例外が発生したらロールバックされる
        const result = await sequelize.transaction(async (t) => {

            // ◇ insertサンプル
            await ShoppingItems.create({
                name: 'ラムネ',
                price: 80,
                suryo: 100,
                description: 'ブドウ糖',
                version: 1
            }, {
                transaction: t
            });
        });
    } catch (error) {
        console.log(error);
    }
    sequelize.close();
})();

〇 index.tsの全体

import { Sequelize, DataTypes, QueryTypes } from 'sequelize';

// 接続先の設定
const sequelize = new Sequelize('Test', 'aa00001', 'Xaa00001', {
    host: 'localhost',
    dialect: 'mssql',
    // コネクションプ-ルの作成
    pool: {
        max: 5,
        min: 0,
        acquire: 60000,
        idle: 30000
    }
});

// モデルの定義
const ShoppingItems = sequelize.define('ShoppingItems', {
    id: {
        type: DataTypes.BIGINT,
        primaryKey: true,
        allowNull: false,
        autoIncrement: true
    },
    name: {
        type: DataTypes.STRING,
        allowNull: false
    },
    price: {
        type: DataTypes.INTEGER,
        allowNull: false
    },
    suryo: {
        type: DataTypes.INTEGER
    },
    description: {
        type: DataTypes.STRING
    },
    version: {
        type: DataTypes.INTEGER,
        allowNull: false
    }
}, {
    timestamps: false, // デフォルト:trueの場合「createdAtとupdatedAtを自動で付加する」
    tableName: 'shopping_items', // デフォルト:trueの場合Sequelizeは自動的に複数形になるようにテーブル名を書き換える
    version: true // trueを設定することで楽観排他ができるようになる(実装が悪いのか実現できていない・・・)
});

(async () => {
    try {
        // トランザクションのサンプル
        const result = await sequelize.transaction(async (t) => {

            ◇ insertサンプル
            await ShoppingItems.create({
                name: 'ラムネ',
                price: 80,
                suryo: 100,
                description: 'ブドウ糖',
                version: 1
            }, {
                transaction: t
            });

            // ◇ updateサンプル
            await ShoppingItems.update({
                name: 'ラムネ',
                price: 120,
                suryo: 1000,
                description: 'みかん味'
            }, {
                where: {
                    id : 21
                },
                transaction: t
            });

            // ◇ deleteサンプル
            await ShoppingItems.destroy({
                where: {
                    id : 10
                },
                transaction: t
            })

            // ◇ bulkInsertサンプル
            await ShoppingItems.bulkCreate([{
                name: '白米',
                price: 120,
                suryo: 10,
                description: '新潟県産',
                version: 1
            }, 
            {
                name: 'うまい棒',
                price: 10,
                suryo: 10,
                description: '高カロリー',
                version: 1
            }], {
                transaction: t
            });

            // ◇ selectサンプル
            await ShoppingItems.findByPk(14,  {
                transaction: t
            })
            .then((result) => {
                if(result){
                    console.log(result);
                }
            })
            .catch((error) => {
                console.log(error)
            });

            // ◇ クエリを書いて実行サンプル
            const records = await sequelize.query('SELECT * FROM shopping_items WHERE price = ?', {
                replacements: [100],
                type: QueryTypes.SELECT,
                transaction: t
            })
            records.forEach((record) => {
                console.log(JSON.stringify(record, null, 2));
            })
        });
    } catch (error) {
        console.log(error);
    }
    sequelize.close();
})();

TypeORMの実装サンプルは以下の記事参照

olafnosuke.hatenablog.com

Prismaの実装サンプルは以下の記事参照

olafnosuke.hatenablog.com