Edited at

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

More than 5 years have passed since last update.


Sequelizeとは

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

http://sequelizejs.com

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

・オブジェクトとDBの関連を取り持ってくれる。これは1テーブルだけの関係ではなく、複数のテーブルの関連を定義することができる。

・入力されたデータが適切かどうかのバリデーションチェックを行う。

・トランザクションのサポートしている。 

・マイグレーションの機能をサポートしている。これにより、データベースのスキーマの更新が容易になる。

・1.7.8ではロックの機能は有していないので、自前でSELECT FOR UPDATEなどをしなければならない。しかし、開発中の2.0.0-devにはロックの機能をサポートしている。


導入方法

SQLiteを操作する場合

npm install --save sequelize

npm install --save sqlite3

Postgresを操作する場合

npm install --save sequelize

npm install --save pg

その他DBについては下記を参照

http://sequelizejs.com/articles/getting-started


実装のサンプル


単純なSQL文の実行

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

var Sequelize = require('sequelize');

var sequelize = new Sequelize('sample','','',{dialect:'sqlite',storage:'./sample.db'});

sequelize.query('select * from test',null,{raw:true}).success(function(rows) {
console.log(rows);
});


モデルを使った例:

defineメソッドでオブジェクトの構造を定義できる。

定義したオブジェクトを経由して、追加、更新、削除が可能だ。

以下の例ではUserモデルを作成し、データを追加後、参照している。

var Sequelize = require('sequelize');

var sequelize = new Sequelize('sample','','',{dialect:'sqlite',storage:'./sample_development.db'});

var User = sequelize.define('User', {
username: Sequelize.STRING,
password: Sequelize.STRING
});

sequelize
.sync({force: true}) // trueだとテーブルを再構築する
.complete(function(err) {
if (err) {
console.log('An error occurred while creating the table:', err);
} else {
console.log('It worked');
// モデル名.buildでインスタンスを新しく作成
var user = User.build({
username: 'username',
password: 'pass'
});
// 作成したインスタンスのsaveで保存をする
user
.save()
.complete(function(err) {
// findまたはfindAllでデータを取得
User.findAll({where:['id>?',0]}).success(function(result) {
console.log(result);
});
}
);
}
}
);


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

バリデーションにより、各フィールドに対する入力制限を指定できる。

これは、フィールド毎だけでなく、パスワードとユーザ名が同じだったらエラーとするというような柔軟な入力規制も記述できる。

また、getter,setterを記述することで、必ず小文字に変換するなどという処理が容易に実装可能だ。

その他詳細は下記を参考のこと。

http://sequelizejs.com/docs/1.7.8/models#validations

var Sequelize = require('sequelize');

var sequelize = new Sequelize('sample','','',{dialect:'sqlite',storage:'./sample_development.db'});
var util = require('util');

var User = sequelize.define('User', {
username: {
type : Sequelize.STRING,
allowNull: false,
get : function() {
// getするときにデータを補正できる
return this.getDataValue('username').toString();
},
set : function(v) {
// setするときにデータを補正できる この例だと常時小文字で保存
return this.setDataValue('username', v.toString().toLowerCase());
},
validate: {
// not の場合 @ を含む場合エラーとなる。配列の値はRegExpに渡す値となる。
// not以外の演算子については下記参照
// http://sequelizejs.com/docs/latest/models#validations
not: ['@','i']
}
},
password: Sequelize.STRING
},{
validate: {
sameValue: function() {
// パスワードとユーザ名が両方がひとしければエラーとする
console.log(this.password);
console.log(this.username);
if (this.password == this.username) {
throw new Error ('sameValue');
}
}
}
});

sequelize
.sync({force: true}) // trueだとテーブルを再構築する
.complete(function(err) {
if (err) {
console.log('An error occurred while creating the table:', err);
} else {
console.log('It worked');
// bulkCreateを使用すると一度についかできる
// その他、一気に操作できるものとして、update,destroyなどがある
// http://sequelizejs.com/docs/latest/instances#bulk
User.bulkCreate([
{username: 'ABC1', password:'xxx1'},
{username: 'abc2', password:'xxx2'},
{username: 'abc3', password:'xxx3'},
{username: 'a@c4', password:'xxx4'}, // @が混ざっている
{username: 'abc5', password:'abc5'}, // パスワードとユーザ名が等しい
],null, {validate:true}).success(function() {
console.log('OK');
}).error(function(err) {
// この例だとa@c4とabc5がエラーになる
console.log(util.inspect(err, true, null));
});
}
}
);


テーブルの関連付けとEager loadingの例:

外部キーなどで接続してあるテーブルを取得する例を示す。

http://sequelizejs.com/docs/latest/models#eager-loading

以下のように記述することで、User,Task,TaskGroupの3テーブルをJOINして取得してくれる。

var Sequelize = require('sequelize');

var sequelize = new Sequelize('sample','','',{dialect:'sqlite',storage:'./sample_development.db'});
var async = require('async');

var User = sequelize.define('User', { name: Sequelize.STRING })
, Task = sequelize.define('Task', { name: Sequelize.STRING })
, TaskGroup = sequelize.define('TaskGroup', { name: Sequelize.STRING });

Task.belongsTo(User);
User.hasMany(Task);

Task.belongsTo(TaskGroup);
TaskGroup.hasMany(Task);

var tasks = [];

// DBの構築
tasks.push(function(next) {
sequelize.sync({force: true}).done(function() {
next(null);
});
});

// ユーザの追加
tasks.push(function(next) {
User.bulkCreate([
{name: 'user1'},
{name: 'user2'}
],null, {validate:true}).success(function() {
next(null);
}).error(function(err) {
next(err);
});
});

// TaskGroupの追加
tasks.push(function(next) {
TaskGroup.bulkCreate([
{name: 'gp1'},
{name: 'gp2'},
{name: 'gp3'}
],null, {validate:true}).success(function() {
next(null);
}).error(function(err) {
next(err);
});
});

// Taskの追加
tasks.push(function(next) {
Task.bulkCreate([
{name: 'task1', UserId:1, TaskGroupId:1},
{name: 'task2', UserId:1, TaskGroupId:2},
{name: 'task3', UserId:1, TaskGroupId:3},
{name: 'task4', UserId:2, TaskGroupId:1}
],null, {validate:true}).success(function() {
next(null);
}).error(function(err) {
next(err);
});
});
async.series(tasks, function(err, results) {
if (err) {
console.log(err);
return;
}
User.findAll({ include: [
{
model:Task,
as: 'Tasks',
attributes: ['id','name'],
include: [TaskGroup]
}]}).success(function(users) {
console.log('User------------------------------------');
console.log(JSON.stringify(users, null, ' '));
});
});


Sequelizeでキーを組み合わせてユニーク制約を使う場合

Sequelizeでキーを組み合わせてユニーク制約を使う場合、次のようにする。

uniqueプロパティはboolean型ならば、その列にユニーク制約を与える。

文字列の場合は、同じ文字列の列と組み合わせてユニーク制約となる。

もし、別のテーブルと関連づけがあり、自動で生成される列の場合でも、制約をつけたい場合は、明示的に列を指定しなければならない。

明示的に列を作成した場合は、hasManyなどを行う場合、asでどの列が外部キーであるか指定する必要がある。

module.exports = function(sequelize, DataTypes) {

var RootPath = sequelize.define('RootPath', {
path: {
type: DataTypes.STRING,
unique: 'projectPathIndex'
},
ProjectId: {
type: DataTypes.INTEGER,
unique: 'projectPathIndex',
allowNull: false
}
}, {
classMethods: {
associate: function(models) {
RootPath.hasMany(models.Project, {as:'ProjectId'});
}
}
});

return RootPath;
};


トランザクションの例:

Sequelizeではトランザクションを使用することができる。

http://sequelizejs.com/docs/latest/transactions

以下の例では2つのトランザクションが同時に動いていることが確認できる。

var Sequelize = require('sequelize');

var sequelize = new Sequelize('testdb',
'postgres',
'postgres',
{
pool: { maxConnections: 10, maxIdleTime: 10000},
host:'127.0.0.1',
port:5432,
dialect:'postgres'
});
var async = require('async');

var User = sequelize.define('User', {
username: Sequelize.STRING,
password: Sequelize.STRING
});

var tasks = [];

for(var i = 0 ; i < 1; ++i) {
tasks.push(function(next) {
doTransaction(10, next);
});
tasks.push(function(next) {
doTransaction(15, next);
});
}

async.parallel(tasks, function(err, result) {
User.count().success(function(max) {
console.log(max);
}).error(function(error) {
console.log(error);
});
});

function doTransaction(cnt, callback) {
var tasks = [];
var trn = null;

tasks.push(function(next) {
sequelize.transaction(function(t) {
console.log(t.constructor );
console.log(t.constructor.LOCK );
trn = t;
next(null);
}).error(function(error) {
console.log('---------------------------------');
console.log(error);
next(error);
});
});
tasks.push(function(next) {
User.findAll({where: ['id=?',1]}, {transaction:trn}).success(function(data) {
console.log(data);
next(null);
}).error(function(error) {
console.log(error);
next(error);
});
});
tasks.push(function(next) {
User.count({transaction:trn}).success(function(max) {
console.log(max);
next(null);
}).error(function(error) {
console.log(error);
next(error);
});
});

for(var i = 0; i < cnt;++i) {
tasks.push(function(next) {
User.create({username:'foo'},{transaction:trn}).complete(function(err) {
next(err);
});
});
}

tasks.push(function(next) {
User.count({transaction:trn}).success(function(max) {
console.log(max);
next(null);
}).error(function(error) {
console.log(error);
next(error);
});
});

async.series(tasks, function(err, results) {
console.log(err);
if (err) {
callback(err);
return;
}
if(trn) {
trn.commit().success(function() {
console.log('OK');
callback(null);
}).error(function(error) {
console.log(error);
callback(error);
});
}
});
}


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

sequelize-cliをインストールすることにより、DBのマイグレーションが可能になる。

この機能により、本来は困難であるはずのDBスキーマーの更新を容易にすることが可能になる。

下記を参考にすること。

http://sequelizejs.com/docs/latest/migrations


インストール方法

npm install -g --save sequelize-cli

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

※使用するDBのライブラリもインストールすること


初期化処理

コマンド

sequelize init

説明

このコマンドにより、カレントフォルダの初期化を行う。

configディレクトリとmigrationフォルダを作成する。

configディレクトリはDBへの接続情報を記述するconfigである。

test用、development用、production用の3つが作成できる。デフォルトはdevelopmentとなり、これを変更するには--envオプションを使用する。

migrationディレクトリには、バージョンアップ用、バージョンダウン用の実装を行うためのマイグレーション用のファイルを格納する。


スケルトンの作成

コマンド

sequelize migration:create

説明

migrationフォルダに新規のマイグレーション用のマイグレーションのファイルを作成する。

作成されるテンプレートは以下のようになる。

module.exports = {

up: function(migration, DataTypes, done) {
// add altering commands here, calling 'done' when finished
done()
},
down: function(migration, DataTypes, done) {
// add reverting commands here, calling 'done' when finished
done()
}
}

upメソッドにはバージョンアップ時の処理を記述する。

downメソッドにはバージョンを元に戻すための処理を記述する。

この際、処理がすべて終わったらdone()を行うこと。

実際のdbに対する操作は、migrationの下記のメソッドを用いて操作を行う

crateTable: テーブルの作成

dropTable: テーブルの削除

dropAllTables : 全てのテーブルの削除

renameTable: テーブルの名称を変更

addColumn:列の追加

removeColumn:列の削除

changeColumn:列の属性情報を変更する

renameColumn:列の命名変更

addIndex:インデックスの追加

removeIndex: インデックスの削除

以下はバージョンアップでテーブルを追加する例になる

module.exports = {

up: function(migration, DataTypes, done) {
// add altering commands here, calling 'done' when finished
migration.createTable(
'nameOfTheNewTable',
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
createdAt: {
type: DataTypes.DATE
},
updatedAt: {
type: DataTypes.DATE
},
attr1: DataTypes.STRING,
attr2: DataTypes.INTEGER,
attr3: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
}
}
);
done()
},
down: function(migration, DataTypes, done) {
// add reverting commands here, calling 'done' when finished
migration.dropTable('nameOfTheNewTable')
done()
}
}


マイグレーションの実行

コマンド

sequelize db:migrate

説明

実行していないマイグレーションファイルを実行する。

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

このコマンドを実行した際に、DBにSequelizeMetaテーブルを作成する。

このテーブルには、このDBにどのマイグレーションファイルが適用されたかを記述するテーブルである。


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

コマンド

sequelize db:migrate:undo

説明

最後に実行したマイグレーションの処理を取り消す。

複数回繰り返すことにより、最初の状態まで復帰できる。