299
285

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Sequelizeを使用してデータベースを操作するための基本的な情報(2020.09更新)

Last updated at Posted at 2014-07-04

#Sequelizeとは
SequelizeはMYSQL,MariaDB,SQLite,Postgresに簡単にアクセスするためのNode.jsのライブラリである。

以下の機能を有している。

  • オブジェクトとDBの関連を取り持ってくれる。これは1テーブルだけの関係ではなく、複数のテーブルの関連を定義することができる。
  • 入力されたデータが適切かどうかのバリデーションチェックを行う。
  • トランザクションのサポートしている。 
  • マイグレーションの機能をサポートしている。これにより、データベースのスキーマの更新が容易になる。
  • ロックの機能をサポートしている。

導入方法

SQLiteを操作する場合

npm install --save sequelize
npm install --save sqlite3

その他DBについては下記を参照
https://sequelize.org/master/manual/getting-started.html

下記のDBを操作するためのサンプルが乗っている。

  • Postgres
  • mysql2
  • mariadb
  • sqlite3
  • Microsoft SQL Server

実装のサンプル

このサンプルの動作環境は以下の通りである。

macos 10.15.6
node.js v14.10.1
sequelize 6.3.5

単純なSQL文の実行

SQLiteに接続し単純なSQLを発行する例を以下に示す

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName : {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName : {
    type: DataTypes.STRING
  }
}, {
});
(async()=>{
  await User.sync({ force: true});
  const user = await User.create({
    firstName : 'alice',
    lastName : 'husigi'
  });
  const rows = await sequelize.query('select * from Users');
  console.log(rows);
})();

単純なモデルを使った操作例:

defineメソッドでモデルの構造を定義できる。
定義したモデルはsyncメソッドを使用してデータベースと同期する。モデルに対応するテーブルが存在しない場合はテーブルを自動的に作成することになる。もし、存在する場合はパラメータによって挙動が変わる。

  • User.sync() テーブルが存在しない場合はテーブルを作成するが、すでに存在する場合は何もしない
  • User.sync({ force: true }) すでに存在する場合は最初に削除され手からテーブルを作成する
  • User.sync({ alter: true }) -データベース内のテーブルの現在の状態(どの列にあるか、それらのデータ型など)をチェックしてから、テーブルに必要な変更を加えてモデルと一致させる。

定義したモデルを経由して、追加、更新、削除が可能である。
以下の例ではUserモデルを作成し、データを追加後、更新、削除している。

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName : {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName : {
    type: DataTypes.STRING
  },
  age : {
    type: DataTypes.INTEGER
  }
}, {
});
(async()=>{
  await User.sync({ force: true});

  // createでデータを挿入する例
  const user1 = await User.create({
    firstName : 'alice',
    lastName : 'husigi',
    age : 23
  });
  // build+saveでデータを挿入する例
  const user2 = User.build({
    firstName : 'Joe',
    lastName : 'Yabuki',
    age : 18
  });
  await user2.save();

  // 単純なSELECTクエリの例
  let users = await User.findAll();
  users.map(user=> console.log(user.firstName, user.lastName, user.age));

  // 更新の例
  const userJoe = await User.findOne({where: {firstName: 'Joe'} });
  userJoe.age = 20;
  await userJoe.save();

  // 更新後の確認
  users = await User.findAll();
  users.map(user=> console.log(user.firstName, user.lastName, user.age));

  // 削除の例
  await userJoe.destroy();
  users = await User.findAll();
  users.map(user=> console.log(user.firstName, user.lastName, user.age));

})();

バリデーションによる値チェックの例:

バリデーションにより、各フィールドに対する入力制限を指定できる。
これは、各フィールドについてだけでなく、パスワードとユーザ名が同じだったらエラーとするというような複数のフィールドの入力をチェックした入力規制も記述できる。

その他詳細は下記を参考のこと。
https://sequelize.org/master/manual/validations-and-constraints.html

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  username : {
    type: DataTypes.STRING,
    allowNull: false,
    validate: {
      len: [5, 15]
    }
  },
  password : {
    type: DataTypes.STRING,
    validate: {
      is: /^[0-9a-zA-Z]+$/i
    }
  }
}, {
  validate: {
    sameValue() {
      if (this.password === this.username) {
        throw new Error('password === username');
      }
    }
  }
});

(async()=>{
  await User.sync({ force: true});

  // bulkCreateはデフォルトではvalidateチェックを行わない
  // そのためvalidate:trueとする必要がある。
  // どれか一つでもエラーだと全てエラーとなる
  User.bulkCreate(
    [
      {username: 'YabukiJoe', password: 'password'},
      //{username: 'joe', password: 'xxx'}, // 短すぎる名前
      //{username: 'joe1111111111111111111', password: 'xxx'}, // 長すぎる名前
      //{username: 'nishi', password: 'nishi!'}, // パスワードに認められない記号
      //{username: 'tangedanpei', password: 'tangedanpei'}, // username===password
      {username: 'RikiisiTouru', password: 'rikiishi'},
    ],
    {validate:true}
  ).then(async()=>{
    const users = await User.findAll();
    users.map(user=> console.log(user.username, user.password));
  }).catch(async(error)=> {
    console.log('error---------------');
    error.errors.map((err)=>{
      console.log(err.message);
      console.log(err.record.username, err.record.password);
    });
    console.log('table--------------');
    const users = await User.findAll();
    users.map(user=> console.log(user.username, user.password));
  });
})();

GetterとSetterと仮想フィールド

Sequelizeを使用すると、モデルの属性のカスタムゲッターとセッターを定義できる。これにより、フィールドの値を強制的に小文字にしたりすることができるようになる。
また、実際のデータベースには存在しない仮想的なフィールドの設定が行える。

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName : {
    type: DataTypes.STRING,
    allowNull: false,
    set (v) {
      // 強制的に小文字にする
      this.setDataValue('firstName', v.toString().toLowerCase());
    }
  },
  lastName : {
    type: DataTypes.STRING,
    get () {
      // 先頭に*を付与する。これはデータベースには格納されない
      return '*' + this.getDataValue('lastName');
    }
  },
  fullName: {
    // 仮想フィールドの例
    type: DataTypes.VIRTUAL,
    get() {
      return`${this.firstName} ${this.lastName}`;
    },
    set(v) {
      throw new Error('Do not try to set the fullName value.');
    }
  }
}, {
});
(async()=>{
  await User.sync({ force: true});
  const user = await User.create({
    firstName : 'Alice',
    lastName : 'HogeHoge'
  });
  const users = await User.findAll();
  users.map(user=>console.log(user.firstName, user.lastName, user.fullName));
  const rows = await sequelize.query('select * from Users');
  console.log(rows);
})();

テーブルの関連付け:

外部キーなどで接続してあるテーブルを取得する例を示す。
https://sequelize.org/master/manual/assocs.html
https://sequelize.org/master/manual/eager-loading.html

1対1の場合

1対1の関係を構築するには以下のメソッドを利用する。

  • A.hasOne(B)
  • A.belongsTo(B)

hasOneとbelongsToの違いは外部キーを作成するテーブルの違いがある。
A.hasOne(B)の場合はBに外部キーが作成される。
A.belongsTo(B)の場合はAに外部キーが作成される。

hasOne

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName : {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName : {
    type: DataTypes.STRING
  }
}, {
});
const Address = sequelize.define('Address', {
  address : {
    type: DataTypes.STRING
  },
  postalcode: {
    type: DataTypes.STRING
  }
},{
});
User.hasOne(Address);
(async()=>{
  await sequelize.sync({ force: true});
  const user = await User.create({
    firstName : 'alice',
    lastName : 'husigi'
  });
  await user.createAddress({
    address: '東京都どっか',
    postalcode: '999-9999'
  });
  const users = await User.findAll({include:Address});
  users.map((user)=>{
    console.log(user.firstName, user.lastName, user.Address.address, user.Address.postalcode);
  });
})();

このサンプルコードを作成した場合、以下のCREATE TABLEが実行される。

CREATE TABLE IF NOT EXISTS `Users` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT, 
  `firstName` VARCHAR(255) NOT NULL, 
  `lastName` VARCHAR(255), 
  `createdAt` DATETIME NOT NULL, 
  `updatedAt` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS `Addresses` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `address` VARCHAR(255),
  `postalcode` VARCHAR(255),
  `createdAt` DATETIME NOT NULL,
  `updatedAt` DATETIME NOT NULL,
  `UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
);

Userに紐づくAddressを作成するには以下のようにする。

  await user.createAddress({
    address: '東京都どっか',
    postalcode: '999-9999'
  });

Userを取得する際に、以下のようにするとUserに紐づくAddressが取得される。

const users = await User.findAll({include:Address});

この時に発行されるSQLは以下の通りである。

SELECT
  `User`.`id`, 
  `User`.`firstName`, 
  `User`.`lastName`, 
  `User`.`createdAt`, 
  `User`.`updatedAt`, 
  `Address`.`id` AS `Address.id`, 
  `Address`.`address` AS `Address.address`, 
  `Address`.`postalcode` AS `Address.postalcode`, 
  `Address`.`createdAt` AS `Address.createdAt`, 
  `Address`.`updatedAt` AS `Address.updatedAt`, 
  `Address`.`UserId` AS `Address.UserId` 
FROM 
  `Users` AS `User` 
  LEFT OUTER JOIN `Addresses` AS `Address` ON `User`.`id` = `Address`.`UserId`;

belongsTo

hasOneで使用したサンプルコードのhasOneをbelongsToに置き換えただけである。

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName : {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName : {
    type: DataTypes.STRING
  }
}, {
});
const Address = sequelize.define('Address', {
  address : {
    type: DataTypes.STRING
  },
  postalcode: {
    type: DataTypes.STRING
  }
},{
});
User.belongsTo(Address);
(async()=>{
  await sequelize.sync({ force: true});
  const user = await User.create({
    firstName : 'alice',
    lastName : 'husigi'
  });
  await user.createAddress({
    address: '東京都どっか',
    postalcode: '999-9999'
  });
  const users = await User.findAll({include:Address});
  users.map((user)=>{
    console.log(user.firstName, user.lastName, user.Address.address, user.Address.postalcode);
  });
})();

このサンプルコードを作成した場合、以下のCREATE TABLEが実行される。

CREATE TABLE IF NOT EXISTS `Addresses` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT, 
  `address` VARCHAR(255), 
  `postalcode` VARCHAR(255), 
  `createdAt` DATETIME NOT NULL, 
  `updatedAt` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS `Users` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT, 
  `firstName` VARCHAR(255) NOT NULL, 
  `lastName` VARCHAR(255), 
  `createdAt` DATETIME NOT NULL, 
  `updatedAt` DATETIME NOT NULL, 
  `AddressId` INTEGER REFERENCES `Addresses` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
);

1対多の場合

1対多の関連を作成するには以下のメソッドを使用する。

  • hasMany
const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName : {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName : {
    type: DataTypes.STRING
  }
}, {
});
const Address = sequelize.define('Address', {
  address : {
    type: DataTypes.STRING
  },
  postalcode: {
    type: DataTypes.STRING
  }
},{
});
User.hasMany(Address);
(async()=>{
  await sequelize.sync({ force: true});
  const user = await User.create({
    firstName : 'alice',
    lastName : 'husigi'
  });
  await user.createAddress({
    address: '東京都どっか',
    postalcode: '999-9999'
  });
  await user.createAddress({
    address: '北海道のどっか',
    postalcode: '888-8888'
  })
  const users = await User.findAll({include:Address});
  users.map((user)=>{
    console.log(user.firstName, user.lastName);
    user.Addresses.map((address)=> {
      console.log('  ', address.address, address.postalcode);
    });
  });
})();

このテストコードを実行した場合、以下のCREATE TABLE文が実行される。

CREATE TABLE IF NOT EXISTS `Users` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `firstName` VARCHAR(255) NOT NULL,
  `lastName` VARCHAR(255), 
  `createdAt` DATETIME NOT NULL,
  `updatedAt` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS `Addresses` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `address` VARCHAR(255), 
  `postalcode` VARCHAR(255),
  `createdAt` DATETIME NOT NULL,
  `updatedAt` DATETIME NOT NULL, 
  `UserId` INTEGER REFERENCES `Users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
);

User.findAllを実行した結果はhasToの時と違いUserに紐づくAddressは複数存在することになる。
findAllを実行した場合に発行されるSQLは以下の通りである。

SELECT 
  `User`.`id`, 
  `User`.`firstName`, 
  `User`.`lastName`, 
  `User`.`createdAt`, 
  `User`.`updatedAt`, 
  `Addresses`.`id` AS `Addresses.id`, 
  `Addresses`.`address` AS `Addresses.address`, 
  `Addresses`.`postalcode` AS `Addresses.postalcode`, 
  `Addresses`.`createdAt` AS `Addresses.createdAt`, 
  `Addresses`.`updatedAt` AS `Addresses.updatedAt`, 
  `Addresses`.`UserId` AS `Addresses.UserId` 
FROM 
  `Users` AS `User` 
  LEFT OUTER JOIN `Addresses` AS `Addresses` ON `User`.`id` = `Addresses`.`UserId`;

多対多の場合

多対多の関連を表すには以下のメソッドを利用する。

  • belongsToMany
const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName : {
    type: DataTypes.STRING,
    allowNull: false
  },
  lastName : {
    type: DataTypes.STRING
  }
}, {
});
const Group = sequelize.define('Group', {
  name : {
    type: DataTypes.STRING
  }
},{
});
const GroupUser = sequelize.define('GroupUser', {
},{
});

User.belongsToMany(Group, {through: GroupUser});
Group.belongsToMany(User, {through: GroupUser});
(async()=>{
  await sequelize.sync({ force: true});
  const user1 = await User.create({
    firstName : 'alice',
    lastName : 'husigi'
  });
  const user2 = await User.create({
    firstName : 'joe',
    lastName : 'yabuki'
  });
  const grp1 = await Group.create({
    name : 'group1'
  });
  const grp2 = await Group.create({
    name : 'group2'
  });
  const grp3 = await Group.create({
    name: 'group3'
  });

  await user1.addGroups([grp1, grp2, grp3]);
  await user2.addGroups([grp2, grp3]);
  const users = await User.findAll({include:Group});
  users.map((user)=>{
    console.log(user.firstName, user.lastName);
    user.Groups.map((group)=> {
      console.log('  ', group.name);
    });
  });
  const groups = await Group.findAll({include:User});
  groups.map((group)=>{
    console.log(group.name);
    group.Users.map((user)=> {
      console.log('  ', user.firstName, user.lastName);
    });
  });
})();

このテストコードを実行すると以下のようなCREATE TABLEのSQLが作成される。

CREATE TABLE IF NOT EXISTS `Users` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT, 
  `firstName` VARCHAR(255) NOT NULL, 
  `lastName` VARCHAR(255), 
  `createdAt` DATETIME NOT NULL, 
  `updatedAt` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS `Groups` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT, 
  `name` VARCHAR(255), 
  `createdAt` DATETIME NOT NULL, 
  `updatedAt` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS `GroupUsers` (
  `createdAt` DATETIME NOT NULL, 
  `updatedAt` DATETIME NOT NULL, 
  `UserId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 
  `GroupId` INTEGER NOT NULL REFERENCES `Groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 
  PRIMARY KEY (`UserId`, `GroupId`)
);

user1.addGroups([grp1, grp2, grp3])を実行した場合には以下のようなSQLが発行される。

SELECT 
  `createdAt`, 
  `updatedAt`, 
  `UserId`, 
  `GroupId` 
FROM 
  `GroupUsers` AS `GroupUser` 
WHERE 
  `GroupUser`.`UserId` = 1 
  AND `GroupUser`.`GroupId` IN (1, 2, 3);

INSERT INTO `GroupUsers` (
  `createdAt`,
  `updatedAt`,
  `UserId`,
  `GroupId`
) VALUES (
  '2020-09-25 09:06:54.065 +00:00',
  '2020-09-25 09:06:54.065 +00:00',
  1,
  1
),(
  '2020-09-25 09:06:54.065 +00:00',
  '2020-09-25 09:06:54.065 +00:00',
  1,
  2
),(
  '2020-09-25 09:06:54.065 +00:00',
  '2020-09-25 09:06:54.065 +00:00',
  1,
  3
);

User.findAll({include:Group})を実行した場合は以下のSQLが実行される。

SELECT 
  `User`.`id`, 
  `User`.`firstName`, 
  `User`.`lastName`, 
  `User`.`createdAt`, 
  `User`.`updatedAt`, 
  `Groups`.`id` AS `Groups.id`, 
  `Groups`.`name` AS `Groups.name`, 
  `Groups`.`createdAt` AS `Groups.createdAt`, 
  `Groups`.`updatedAt` AS `Groups.updatedAt`, 
  `Groups->GroupUser`.`createdAt` AS `Groups.GroupUser.createdAt`, 
  `Groups->GroupUser`.`updatedAt` AS `Groups.GroupUser.updatedAt`, 
  `Groups->GroupUser`.`UserId` AS `Groups.GroupUser.UserId`, 
  `Groups->GroupUser`.`GroupId` AS `Groups.GroupUser.GroupId` 
FROM 
  `Users` AS `User` 
  LEFT OUTER JOIN `GroupUsers` AS `Groups->GroupUser` ON `User`.`id` = `Groups->GroupUser`.`UserId` 
  LEFT OUTER JOIN `Groups` AS `Groups` ON `Groups`.`id` = `Groups->GroupUser`.`GroupId`;

Group.findAll({include:User})を実行した場合は以下のSQLが発行される。

SELECT 
  `Group`.`id`, 
  `Group`.`name`, 
  `Group`.`createdAt`, 
  `Group`.`updatedAt`, 
  `Users`.`id` AS `Users.id`, 
  `Users`.`firstName` AS `Users.firstName`, 
  `Users`.`lastName` AS `Users.lastName`, 
  `Users`.`createdAt` AS `Users.createdAt`, 
  `Users`.`updatedAt` AS `Users.updatedAt`, 
  `Users->GroupUser`.`createdAt` AS `Users.GroupUser.createdAt`, 
  `Users->GroupUser`.`updatedAt` AS `Users.GroupUser.updatedAt`, 
  `Users->GroupUser`.`UserId` AS `Users.GroupUser.UserId`, 
  `Users->GroupUser`.`GroupId` AS `Users.GroupUser.GroupId` 
FROM 
  `Groups` AS `Group` 
  LEFT OUTER JOIN `GroupUsers` AS `Users->GroupUser` ON `Group`.`id` = `Users->GroupUser`.`GroupId` 
  LEFT OUTER JOIN `Users` AS `Users` ON `Users`.`id` = `Users->GroupUser`.`UserId`;

ユニーク制約の使用方法

モデルのフィールドにuniqueプロパティを指定することでユニーク制約をつけることが可能である。

  • uniqueプロパティはboolean型ならば、その列にユニーク制約を与える。
  • 文字列の場合は、同じ文字列の列と組み合わせてユニーク制約となる。

以下のコードの場合、userIdの列は重複を認めない。
firstName、lastNameの列はそれぞれ重複を認めるが、firstNameが等しくてかつlastNameが等しい場合はユニーク制約によりエラーとなる。

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  userId : {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  firstName: {
    type: DataTypes.STRING,
    unique: 'fullNameUnique'
  },
  lastName : {
    type: DataTypes.STRING,
    unique: 'fullNameUnique'
  }
}, {
});
(async()=>{
  await User.sync({ force: true});
  const user1 = await User.create({
    userId : 'u0001',
    firstName : 'alice',
    lastName : 'kirisame'
  });
  const user2 = await User.create({
    userId : 'u0002',
    firstName : 'marisa',
    lastName : 'kirisame'
  });
  const user3 = await User.create({
    userId : 'u0003',
    firstName : 'alice',
    lastName : 'yukkuri'
  });
  /* ユニーク制約でエラーとなる
  const user4 = await User.create({
    userId : 'u0004',
    firstName : 'alice',
    lastName : 'kirisame'
  }); */
})();

トランザクションの例:

Sequelizeではトランザクションを使用することができる。
https://sequelize.org/master/class/lib/transaction.js~Transaction.html
https://github.com/sequelize/sequelize/blob/8fe367e97f1820938f32834e52ddb07450d28173/docs/manual/other-topics/transactions.md

トランザクションの操作には2種類の方法がある。

  • アンマネージトランザクション:トランザクションのコミットとロールバックは、ユーザーが手動で行う必要がある
  • マネージドトランザクション:Sequelizeは、エラーがスローされた場合にトランザクションを自動的にロールバックするか、そうでなければトランザクションをコミットする

アンマネージャートランザクションの例:

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName: {
    type: DataTypes.STRING,
  },
  lastName : {
    type: DataTypes.STRING,
  }
}, {
});
(async()=>{
  await User.sync({ force: true});
  let trn = await sequelize.transaction();
  try {
    // your transactions
    const user1 = await User.create({
      firstName : 'alice',
      lastName : 'kirisame'
    },{
      transaction: trn
    });
    const user2 = await User.create({
      firstName : 'marisa',
      lastName : 'kirisame'
    },{
      transaction: trn
    });
    let users;
    console.log('SELECT()------------------');
    users = await User.findAll();
    users.map((user)=>{
      console.log(user.firstName, user.lastName);
    });
    // commitを明示的に実行しなくてもスコープ抜けるとCOMMITされる
    await trn.commit();
    trn = await sequelize.transaction();
    const user3 = await User.create({
      firstName : 'hakurei',
      lastName : 'reimu'
    },{
      transaction: trn
    });
    console.log('SELECT(ROLLBACK前)------------------');
    users = await User.findAll({transaction: trn});
    users.map((user)=>{
      console.log(user.firstName, user.lastName);
    });
    await trn.rollback();

    console.log('SELECT(ROLLBACK後)------------------');
    users = await User.findAll();
    users.map((user)=>{
      console.log(user.firstName, user.lastName);
    });
  } catch(err) {
    console.log('error', err);
  }
})();

マネージドトランザクションの例

const {Sequelize, DataTypes} = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
  firstName: {
    type: DataTypes.STRING,
  },
  lastName : {
    type: DataTypes.STRING,
  }
}, {
});
(async()=>{
  await User.sync({ force: true});
  try {
    const result = await sequelize.transaction({}, async trn => {
      // your transactions
      const user1 = await User.create({
        firstName : 'alice',
        lastName : 'kirisame'
      });
      const user2 = await User.create({
        firstName : 'marisa',
        lastName : 'kirisame'
      });
      let users;
      console.log('SELECT()------------------');
      users = await User.findAll();
      users.map((user)=>{
        console.log(user.firstName, user.lastName);
      });
      // commitを明示的に実行しなくてもスコープ抜けるとCOMMITされる
      await trn.commit();
    });
  } catch(err) {
    // do something with the err.
    console.log(err);
  }
  try {
    const result = await sequelize.transaction({}, async trn => {
      const user3 = await User.create({
        firstName : 'hakurei',
        lastName : 'reimu'
      });
      console.log('SELECT(ROLLBACK前)------------------');
      users = await User.findAll();
      users.map((user)=>{
        console.log(user.firstName, user.lastName);
      });
      // 自動でロールバックが動作する
      throw new Error('raise error');
    });
  } catch(err) {
    // do something with the err.
    console.log(err);
  }
  console.log('SELECT(ROLLBACK後)------------------');
  users = await User.findAll();
  users.map((user)=>{
    console.log(user.firstName, user.lastName);
  });
})();

sequelizeによるマイグレーションの方法

sequelize-cliをインストールすることにより、DBのマイグレーションが可能になる。
この機能により、本来は困難であるはずのDBスキーマーの更新を容易にすることが可能になる。

下記を参考にすること。
https://sequelize.org/master/manual/migrations.html
https://github.com/sequelize/sequelize/blob/109cdd064a2bc7c442828beafcc04f40524bfacb/docs/manual/other-topics/migrations.md

インストール方法

npm install --save-dev sequelize-cli

以降、sequelize-cliというコマンドが使用可能になる。

初期化処理

コマンド

npx sequelize-cli init

説明
このコマンドにより、次のフォルダが作成される

├── config
│   └── config.json
├── migrations
├── models
│   └── index.js
├── seeders
  • config:CLIにデータベースとの接続方法を指示する設定ファイルが含まれている
  • models:プロジェクトのすべてのモデルが含まれている
  • migrations:マイグレーションのためのファイルが含まれている。
  • seeders:すべてのシードファイルが含まれている

データベースの構成

データベースに接続する方法は「config/config.json」に記録されている。
次のようにtest用、development用、production用の3つの環境があり、デフォルトはdevelopmentとなる。これを変更するには--envオプションを使用する。

作成されたconfig/config.json

config/config.json
{
  "development": {
    "username": "root",
    "password": null,
    "database": "database_development",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

SQLiteの場合は以下のように設定する。

config/config.json
{
  "development": {
    "dialect": "sqlite",
    "storage": "./database.sqlite3"
  },
  "test": {
    "dialect": "sqlite",
    "storage": ":memory"
  },
  "production": {
    "dialect": "sqlite",
    "storage": "./database.sqlite3"
  }
}

モデルの作成

コマンド

npx sequelize-cli model:generate --name User --attributes firstName:string,lastName:string,email:string

説明
以下のファイルを作成する

  • models/user.js
  • migrations/20200925105242-create-user.js
models/user.js
'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class User extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  };
  User.init({
    firstName: DataTypes.STRING,
    lastName: DataTypes.STRING,
    email: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'User',
  });
  return User;
};
migrations/20200925105242-create-user.js
'use strict';
module.exports = {
  up: async (queryInterface, Sequelize) => {
    await queryInterface.createTable('Users', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      firstName: {
        type: Sequelize.STRING
      },
      lastName: {
        type: Sequelize.STRING
      },
      email: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  down: async (queryInterface, Sequelize) => {
    await queryInterface.dropTable('Users');
  }
};

upメソッドにはバージョンアップ時の処理を記述されている。
downメソッドにはバージョンを元に戻すための処理を記述されている。

実際のdbに対する操作は、queryInterfaceを経由して行う。使用できるqueryInterfaceは以下の通り
https://sequelize.org/master/class/lib/dialects/abstract/query-interface.js~QueryInterface.html

よく使用されるメソッド:

  • crateTable: テーブルの作成
  • dropTable: テーブルの削除
  • dropAllTables : 全てのテーブルの削除
  • renameTable: テーブルの名称を変更
  • addColumn:列の追加
  • removeColumn:列の削除
  • changeColumn:列の属性情報を変更する
  • renameColumn:列の命名変更
  • addIndex:インデックスの追加
  • removeIndex: インデックスの削除

マイグレーションの実行

コマンド

npx sequelize-cli db:migrate

説明
実行していないマイグレーションファイルを実行する。
もし複数マイグレーションのファイルが存在がある場合は、一回のコマンドですべて実行する。

このコマンドを実行した際に、DBにSequelizeMetaテーブルを作成する。
このテーブルには、このDBにどのマイグレーションファイルが適用されたかを記述するテーブルである。

マイグレーションの取り消し

コマンド

npx sequelize-cli  db:migrate:undo

説明
最後に実行したマイグレーションの処理を取り消す。
複数回繰り返すことにより、最初の状態まで復帰できる。

作成したモデルの使用例

modelsフォルダをrequireすることでmodels経由で各モデルにアクセスが可能。

const models = require('./models');
(async()=>{
  await models.User.create({
    firstName : 'joe',
    lastName : 'yabuki',
    email: 'joe@co.jp'
  });
  const users = await models.User.findAll();
  users.map((user)=>{
    console.log(user.firstName, user.lastName, user.email);
  })
})();

トラブル

SQLiteでカラムを追加する場合の注意事項

SQLiteには任意の位置にカラムを追加する機能はありません。
そのため、手動で追加してsyncを行うとテーブルを一旦消して再度、テーブルを作り直す機能が自動で実行されます。
この際、外部キーがあると、その作り直し作業の途中でデータが欠損します。
SQLiteを使用する場合は、syncを使用してテーブルの構成を変更するのは避けた方が良さそうです。

LIMITをつけると変なSQLを発行する

いかにあるように、includeとlimitを使用すると予期せぬSQLを作成するようです。

limit with associated include fails #7585
https://github.com/sequelize/sequelize/issues/6073

以下のオプションを使用すると回避できるようです。

subQuery: false
または
duplicating: false

299
285
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
299
285

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?