モデルのデータベースへの反映
定義したモデルをデータベースに反映させるにはsequelize.sync()を呼びます。sequelize.sync()はデータベースを新しく作成するときや、モデルを変更した時にだけ呼びます。モデルに変更がない限りsequelize.sync()を再度呼ぶ必要はありません。
sequelize.sync()
sequelize.sync()を実行した時点ですでにデータベースに存在しているテーブルについては再作成されません。ただし、{ force: true }を引数として渡すと、既存のテーブルも削除して新たにテーブルを作成し直します。
sequelize.sync force: true
カラムの詳細オプション
モデルを定義するとき、カラムの型指定に詳細なオプションを指定できます。詳細なオプションは、カラムごとにオブジェクトで指定します。
Person = sequelize.define 'Person',
firstName: { type: Sequelize.STRING, allowNull: false }
lastName: { type: Sequelize.STRING, allowNull: false }
dateOfBirth: Sequelize.DATE
employeeId: { type: Sequelize.INTEGER, unique: true }
指定できるオプションは次の通りです。大文字はデフォルト値を表します。
- allowNull(TRUE/false):値としてNULLを許可するか。
- defaultValue(値):デフォルト値。
- unique(true/FALSE):ユニークキー制約を設定するか。
- primaryKey(true/FALSE):プライマリキーとして使用するか。
- autoIncrement(true/FALSE):オートインクリメントを使用するか。
モデルのどのカラムにもprimaryKeyオプションを指定していない場合は、idというプライマリキーのカラムが自動的に追加されます。
インスタンスとレコードの作成
データベースにレコードを挿入するには、モデルのbuild()メソッドを呼んでインスタンスを作成し、save()を実行します。
person = Person.build firstName: "賢治" lastName: "宮沢" dateOfBirth: new Date 1896, 7, 27 person.save().success -> console.log "保存成功"
保存に成功すると、success()に渡したコールバック関数が呼ばれます。
カラムの値はインスタンスのプロパティとして参照できます。
person.firstName # 賢治 person.lastName # 宮沢 person.dateOfBirth # Wed, 26 Aug 1896 15:00:00 GMT
レコードの検索
モデルのfind()メソッドを呼ぶとレコードをインスタンスとして取得します(リスト4)。
# SELECT * FROM `Persons` WHERE `id`=1 LIMIT 1;
Person.find(1).success (person) ->
# クエリが完了するとこの部分が実行される。見つからなかった場合はpersonがnullとなる。
console.log "id: #{person.id}"
console.log "firstName: #{person.firstName}"
console.log "lastName: #{person.lastName}"
console.log "dateOfBirth: #{person.dateOfBirth}"
find()メソッドの引数で様々な検索条件を指定できます(リスト5)。複数のレコードが検索条件にマッチした場合は、最初にマッチしたレコードだけを取得します。
# lastNameが「宮沢」のレコードを探し、最初に見つかったものを取得する。
# SELECT * FROM `Persons` WHERE `lastName`='宮沢' LIMIT 1;
Person.find({ where:{ lastName:'宮沢' } }).success (person) ->
# firstNameが「賢治」かつlastNameが「宮沢」のレコードを探し、最初に見つかったものを取得する。
# SELECT * FROM `Persons` WHERE `firstName`='賢治' AND `lastName`='宮沢' LIMIT 1;
Person.find({ where:{ firstName:'賢治', lastName:'宮沢' } }).success (person) ->
# lastNameが「宮」で始まるレコードを探し、最初に見つかったものを取得する。
# SELECT * FROM `Persons` WHERE lastName LIKE '宮%' LIMIT 1;
Person.find({ where:[ 'lastName LIKE ?', '宮%' ] }).success (person) ->
# firstNameが「りえ」またはlastNameが「宮沢」のレコードを探し、最初に見つかったものを取得する。
# SELECT * FROM `Persons` WHERE firstName = 'りえ' OR lastName = '宮沢' LIMIT 1;
Person.find({ where: ["firstName = ? OR lastName = ?", 'りえ', '宮沢'] }).success (person) ->
findAll()を呼ぶと、検索条件にマッチしたすべてのレコードをインスタンスに変換し、配列として取得します(リスト6)。
# Personのすべてのレコードを配列で取得する。
# SELECT * FROM `Persons`;
Person.findAll().success (persons) ->
console.log persons.length
# Personのレコードのうちidが1,2,3のいずれかであるものをすべて取得する。
# SELECT * FROM `Persons` WHERE `id` IN (1,2,3);
Person.findAll({ where:{ id:[1,2,3] } }).success (persons) ->
# Personのすべてのレコードをidの降順でソートして取得する。
# SELECT * FROM `Persons` ORDER BY id DESC;
Person.findAll({ order:'id DESC' }).success (persons) ->
# Personの3番目から7番目までの5個のレコードを取得する。
# SELECT * FROM `Persons` LIMIT 2, 5;
Person.findAll({ offset: 2, limit: 5 }).success (persons) ->
# Personのレコード数を取得する。
# count()の引数にもfind()と同様の条件指定が可能。
# SELECT count(*) as `count` FROM `Persons`;
Person.count().success (count) ->
console.log "#{count}人"
レコードの削除
レコードを削除するにはインスタンスのdestroy()メソッドを呼びます。
person.destroy()
エラー処理
指定されたカラムが見つからない場合やSQLの構文に問題がある場合など、データベースがエラーを返したときはerror()のコールバックが実行されます。この場合success()のコールバックは実行されません。
Person.find({ where: middleName: 'John' }).success (person) ->
console.log person.firstName
.error (error) ->
console.log "db error: #{error}"
MySQLのテーブルにmiddleNameというカラムが存在しない状態でリスト7を実行すると、次のように出力されます。
db error: Error: Unknown column 'middleName' in 'where clause'
error()を呼ばないままデータベース側でエラーが起きると、success()のコールバックは呼ばれず、エラーを受け取ることもできません。成功することを見越してエラーを無視することも可能ですが、万が一エラーが起きた時にアプリケーションが止まらないように、最低限のエラー処理は入れておくようにしましょう。
モデルメソッドの定義
モデルの定義と同時にクラスメソッドやインスタンスメソッドを定義できます。それぞれリスト8のようにdefine()の第3引数で指定します。定義したクラスメソッドはPerson.メソッド名()で、インスタンスメソッドはインスタンス.メソッド名()で呼び出せます。
Person = sequelize.define 'Person',
firstName: Sequelize.STRING
lastName: Sequelize.STRING
,
# クラスメソッドの定義
classMethods:
# lastNameが「宮沢」のレコードをすべて取得する
getAllMiyazawa: (callback) ->
@findAll({ where: { lastName: '宮沢' } }).success (persons) ->
callback null, persons
.error (error) ->
callback error
# インスタンスメソッドの定義
instanceMethods:
# フルネームを返す
getFullName: ->
"#{@lastName} #{@firstName}"
リスト8で定義したPersonモデルの使用例はリスト9のようになります。非同期型関数であるPerson.getAllMiyazawa()の実行中にエラーが発生する可能性があるため、実際にエラーが発生したかどうかをコールバックの第1引数で伝えています。このように、エラーが発生する可能性のある非同期型関数では、エラーを知らせるためのスタイルを統一しておくとよいでしょう。
Person.getAllMiyazawa (err, persons) ->
if err # エラーが発生した
console.log "db error: #{err}"
return
# 成功時
for person in persons
console.log person.getFullName()
SQLの実行
SequelizeでSQLを直接実行するにはリスト10のようにquery()メソッドを呼びます。
# 単純なオブジェクトの配列として受け取る場合
sequelize.query('SELECT * FROM Persons', null, {raw: true}).success (rows) ->
console.log rows.length
console.log rows[0].firstName
# Personインスタンスの配列として受け取る場合
sequelize.query('SELECT * FROM Persons', Person).success (persons) ->
console.log persons.length
console.log persons[0].getFullName()
# 結果を受け取らない場合
sequelize.query("UPDATE Persons SET updatedAt=NOW()").success ->
console.log "updated"
O/Rマッパーは開発初期段階で大幅に工数を短縮できるため便利ですが、O/Rマッパーが背後で発行するSQLの種類には限りがあるため、自分で直接SQLを記述する場合と比べて多少余分な操作が入ってしまうことがあります。とくに大量の操作を一度に行うような場合、O/Rマッパーのモデルを使うよりもSQLを直接書いた方が遥かに高速に処理できます。一通りO/Rマッパーでの実装が終わりアプリケーションの高速化が必要な段階になったら、ボトルネックになっている部分をSQLで置き換えていくとよいでしょう。
トランザクション
執筆時点での最新版であるSequelize 1.3.7ではトランザクション機能がサポートされていません。トランザクションを使うにはSQLを直接書く必要があります(リスト11)。
doAtomicInsert = (cb) ->
sequelize.query("BEGIN").error(cb).success ->
sequelize.query("INSERT INTO Persons (firstName, lastName) " +
"VALUES ('賢治', '宮沢')").error(cb).success ->
sequelize.query("INSERT INTO Persons (firstName, lastName) " +
"VALUES ('白秋', '北原')").error(cb).success ->
sequelize.query("COMMIT").error(cb).success ->
cb null
# 実行
doAtomicInsert (err) ->
if err # エラーが発生した
console.log "db error: #{err.number}: #{err}"
else
console.log "commit ok"
長いトランザクションが頻繁に実行されるとデッドロックが発生する可能性が高まります。トランザクションを頻繁にコミットするなどしてデッドロックを発生させないように、また万が一デッドロックが発生してもトランザクションを再実行するなどして適切に対処できるように、開発時に注意しましょう。
その他のAPI
その他のAPIについてはドキュメント(英語)を参照してください。
