#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
{
"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の場合は以下のように設定する。
{
"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
'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;
};
'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