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

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

http://sequelizejs.com


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

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

# 導入方法

**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を発行する例を以下に示す

```js
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メソッド](https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html#instance-method-define)でモデルの構造を定義できる。
定義したモデルは[syncメソッド](https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html#instance-method-sync)を使用してデータベースと同期する。モデルに対応するテーブルが存在しない場合はテーブルを自動的に作成することになる。もし、存在する場合はパラメータによって挙動が変わる。

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

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

```js
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


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

https://sequelize.org/master/manual/getters-setters-virtuals.html

```js
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対１の関係を構築するには以下のメソッドを利用する。

 - A.hasOne(B)
 - A.belongsTo(B)

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

#### hasOne

```js
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が実行される。

```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 `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を作成するには以下のようにする。

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

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

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

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

```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に置き換えただけである。

```js
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が実行される。

```sql
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

```js
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文が実行される。

```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 `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は以下の通りである。

```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

```js
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が作成される。

```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が発行される。

```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が実行される。

```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が発行される。

```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が等しい場合はユニーク制約によりエラーとなる。


```js
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

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

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

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

```js
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);
  }
})();

```

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

```js
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用の３つの環境があり、デフォルトはdevelopmentとなる。これを変更するには--envオプションを使用する。

__作成されたconfig/config.json__

```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の場合は以下のように設定する。

```json: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

```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;
};
```

```js: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経由で各モデルにアクセスが可能。

```js
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