若是你以爲Sequelize的文檔有點多、雜,不方便看,能夠看看這篇。javascript
在使用NodeJS
來關係型操做數據庫時,爲了方便,一般都會選擇一個合適的ORM
(Object Relationship Model)框架。畢竟直接操做SQL
比較繁瑣,經過ORM
框架,咱們可使用面向對象的方式來操做表。NodeJS
社區有不少的ORM
框架,我比較喜歡Sequelize
,它功能豐富,能夠很是方便的進行連表查詢。css
這篇文章咱們就來看看,Sequelize
是如何在SQL
之上進行抽象、封裝,從而提升開發效率的。java
這篇文章主要使用MySQL
、Sequelize
、co
來進行介紹。安裝很是簡單:mysql
$ npm install --save co $ npm install --save sequelize $ npm install --save mysql
代碼模板以下:sql
var Sequelize = require('sequelize'); var co = require('co'); co(function* () { // code here }).catch(function(e) { console.log(e); });
基本上,Sequelize
的操做都會返回一個Promise
,在co
的框架裏面能夠直接進行yield
,很是方便。數據庫
var sequelize = new Sequelize( 'sample', // 數據庫名 'root', // 用戶名 'zuki', // 用戶密碼 { 'dialect': 'mysql', // 數據庫使用mysql 'host': 'localhost', // 數據庫服務器ip 'port': 3306, // 數據庫服務器端口 'define': { // 字段如下劃線(_)來分割(默認是駝峯命名風格) 'underscored': true } } );
Sequelize
:npm
var User = sequelize.define( // 默認表名(通常這裏寫單數),生成時會自動轉換成複數形式 // 這個值還會做爲訪問模型相關的模型時的屬性名,因此建議用小寫形式 'user', // 字段定義(主鍵、created_at、updated_at默認包含,不用特殊定義) { 'emp_id': { 'type': Sequelize.CHAR(10), // 字段類型 'allowNull': false, // 是否容許爲NULL 'unique': true // 字段是否UNIQUE }, 'nick': { 'type': Sequelize.CHAR(10), 'allowNull': false }, 'department': { 'type': Sequelize.STRING(64), 'allowNull': true } } );
SQL
:api
CREATE TABLE IF NOT EXISTS `users` ( `id` INTEGER NOT NULL auto_increment , `emp_id` CHAR(10) NOT NULL UNIQUE, `nick` CHAR(10) NOT NULL, `department` VARCHAR(64), `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB;
幾點說明:數組
建表SQL
會自動執行的意思是你主動調用sync
的時候。相似這樣:User.sync({force: true});
(加force:true
,會先刪掉表後再建表)。咱們也能夠先定義好表結構,再來定義Sequelize
模型,這時能夠不用sync
。二者在定義階段沒有什麼關係,直到咱們真正開始操做模型時,纔會觸及到表的操做,可是咱們固然仍是要儘可能保證模型和表的同步(能夠藉助一些migration
工具)。自動建表功能有風險,使用需謹慎。bash
全部數據類型,請參考文檔數據類型。
模型還能夠定義虛擬屬性、類方法、實例方法,請參考文檔:模型定義
其餘一些特殊定義以下所示:
var User = sequelize.define( 'user', { 'emp_id': { 'type': Sequelize.CHAR(10), // 字段類型 'allowNull': false, // 是否容許爲NULL 'unique': true // 字段是否UNIQUE }, 'nick': { 'type': Sequelize.CHAR(10), 'allowNull': false }, 'department': { 'type': Sequelize.STRING(64), 'allowNull': true } }, { // 自定義表名 'freezeTableName': true, 'tableName': 'xyz_users', // 是否須要增長createdAt、updatedAt、deletedAt字段 'timestamps': true, // 不須要createdAt字段 'createdAt': false, // 將updatedAt字段改個名 'updatedAt': 'utime' // 將deletedAt字段更名 // 同時須要設置paranoid爲true(此種模式下,刪除數據時不會進行物理刪除,而是設置deletedAt爲當前時間 'deletedAt': 'dtime', 'paranoid': true } );
經過Sequelize
獲取的模型對象都是一個DAO
(Data Access Object)對象,這些對象會擁有許多操做數據庫表的實例對象方法(好比:save
、update
、destroy
等),須要獲取「乾淨」的JSON
對象能夠調用get({'plain': true})
。
經過模型的類方法能夠獲取模型對象(好比:findById
、findAll
等)。
Sequelize
:
// 方法1:build後對象只存在於內存中,調用save後才操做db var user = User.build({ 'emp_id': '1', 'nick': '小紅', 'department': '技術部' }); user = yield user.save(); console.log(user.get({'plain': true})); // 方法2:直接操做db var user = yield User.create({ 'emp_id': '2', 'nick': '小明', 'department': '技術部' }); console.log(user.get({'plain': true}));
SQL
:
INSERT INTO `users` (`id`, `emp_id`, `nick`, `department`, `updated_at`, `created_at`) VALUES (DEFAULT, '1', '小紅', '技術部', '2015-11-02 14:49:54', '2015-11-02 14:49:54');
Sequelize
會爲主鍵id
設置DEFAULT
值來讓數據庫產生自增值,還將當前時間設置成了created_at
和updated_at
字段,很是方便。
Sequelize
:
// 方法1:操做對象屬性(不會操做db),調用save後操做db user.nick = '小白'; user = yield user.save(); console.log(user.get({'plain': true})); // 方法2:直接update操做db user = yield user.update({ 'nick': '小白白' }); console.log(user.get({'plain': true}));
SQL
:
UPDATE `users` SET `nick` = '小白白', `updated_at` = '2015-11-02 15:00:04' WHERE `id` = 1;
更新操做時,Sequelize
將將當前時間設置成了updated_at
,很是方便。
若是想限制更新屬性的白名單,能夠這樣寫:
// 方法1 user.emp_id = '33'; user.nick = '小白'; user = yield user.save({'fields': ['nick']}); // 方法2 user = yield user.update( {'emp_id': '33', 'nick': '小白'}, {'fields': ['nick']} });
這樣就只會更新nick
字段,而emp_id
會被忽略。這種方法在對錶單提交過來的一大推數據中只更新某些屬性的時候比較有用。
Sequelize
:
yield user.destroy();
SQL
:
DELETE FROM `users` WHERE `id` = 1;
這裏有個特殊的地方是,若是咱們開啓了paranoid
(偏執)模式,destroy
的時候不會執行DELETE
語句,而是執行一個UPDATE
語句將deleted_at
字段設置爲當前時間(一開始此字段值爲NULL
)。咱們可使用user.destroy({force: true})
來強制刪除,從而執行DELETE
語句進行物理刪除。
Sequelize
:
var users = yield User.findAll(); console.log(users);
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users`;
Sequelize
:
var users = yield User.findAll({ 'attributes': ['emp_id', 'nick'] }); console.log(users);
SQL
:
SELECT `emp_id`, `nick` FROM `users`;
Sequelize
:
var users = yield User.findAll({ 'attributes': [ 'emp_id', ['nick', 'user_nick'] ] }); console.log(users);
SQL
:
SELECT `emp_id`, `nick` AS `user_nick` FROM `users`;
Sequelize
的where
配置項基本上徹底支持了SQL
的where
子句的功能,很是強大。咱們一步步來進行介紹。
Sequelize
:
var users = yield User.findAll({ 'where': { 'id': [1, 2, 3], 'nick': 'a', 'department': null } }); console.log(users);
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` WHERE `user`.`id` IN (1, 2, 3) AND `user`.`nick`='a' AND `user`.`department` IS NULL;
能夠看到,k: v
被轉換成了k = v
,同時一個對象的多個k: v
對被轉換成了AND
條件,即:k1: v1, k2: v2
轉換爲k1 = v1 AND k2 = v2
。
這裏有2個要點:
若是v是null
,會轉換爲IS NULL
(由於SQL
沒有= NULL
這種語法)
若是v是數組,會轉換爲IN
條件(由於SQL
沒有=[1,2,3]
這種語法,何況也沒數組這種類型)
操做符是對某個字段的進一步約束,能夠有多個(對同一個字段的多個操做符會被轉化爲AND
)。
Sequelize
:
var users = yield User.findAll({ 'where': { 'id': { '$eq': 1, // id = 1 '$ne': 2, // id != 2 '$gt': 6, // id > 6 '$gte': 6, // id >= 6 '$lt': 10, // id < 10 '$lte': 10, // id <= 10 '$between': [6, 10], // id BETWEEN 6 AND 10 '$notBetween': [11, 15], // id NOT BETWEEN 11 AND 15 '$in': [1, 2], // id IN (1, 2) '$notIn': [3, 4] // id NOT IN (3, 4) }, 'nick': { '$like': '%a%', // nick LIKE '%a%' '$notLike': '%a' // nick NOT LIKE '%a' }, 'updated_at': { '$eq': null, // updated_at IS NULL '$ne': null // created_at IS NOT NULL } } });
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` WHERE ( `user`.`id` = 1 AND `user`.`id` != 2 AND `user`.`id` > 6 AND `user`.`id` >= 6 AND `user`.`id` < 10 AND `user`.`id` <= 10 AND `user`.`id` BETWEEN 6 AND 10 AND `user`.`id` NOT BETWEEN 11 AND 15 AND `user`.`id` IN (1, 2) AND `user`.`id` NOT IN (3, 4) ) AND ( `user`.`nick` LIKE '%a%' AND `user`.`nick` NOT LIKE '%a' ) AND ( `user`.`updated_at` IS NULL AND `user`.`updated_at` IS NOT NULL );
這裏咱們發現,其實相等條件k: v
這種寫法是操做符寫法k: {$eq: v}
的簡寫。而要實現不等條件就必須使用操做符寫法k: {$ne: v}
。
上面咱們說的條件查詢,都是AND
查詢,Sequelize
同時也支持OR
、NOT
、甚至多種條件的聯合查詢。
Sequelize
:
var users = yield User.findAll({ 'where': { '$and': [ {'id': [1, 2]}, {'nick': null} ] } });
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` WHERE ( `user`.`id` IN (1, 2) AND `user`.`nick` IS NULL );
Sequelize
:
var users = yield User.findAll({ 'where': { '$or': [ {'id': [1, 2]}, {'nick': null} ] } });
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` WHERE ( `user`.`id` IN (1, 2) OR `user`.`nick` IS NULL );
Sequelize
:
var users = yield User.findAll({ 'where': { '$not': [ {'id': [1, 2]}, {'nick': null} ] } });
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` WHERE NOT ( `user`.`id` IN (1, 2) AND `user`.`nick` IS NULL );
咱們這裏作個總結。Sequelize
對where
配置的轉換規則的僞代碼大概以下:
function translate(where) { for (k, v of where) { if (k == 表字段) { // 先統一轉爲操做符形式 if (v == 基本值) { // k: 'xxx' v = {'$eq': v}; } if (v == 數組) { // k: [1, 2, 3] v = {'$in': v}; } // 操做符轉換 for (opk, opv of v) { // op將opk轉換對應的SQL表示 => k + op(opk, opv) + AND; } } // 邏輯操做符處理 if (k == '$and') { for (item in v) { => translate(item) + AND; } } if (k == '$or') { for (item in v) { => translate(item) + OR; } } if (k == '$not') { NOT + for (item in v) { => translate(item) + AND; } } } function op(opk, opv) { switch (opk) { case $eq => ('=' + opv) || 'IS NULL'; case $ne => ('!=' + opv) || 'IS NOT NULL'; case $gt => '>' + opv; case $lt => '<' + opv; case $gte => '>=' + opv; case $lte => '<=' + opv; case $between => 'BETWEEN ' + opv[0] + ' AND ' + opv[1]; case $notBetween => 'NOT BETWEEN ' + opv[0] + ' AND ' + opv[1]; case $in => 'IN (' + opv.join(',') + ')'; case $notIn => 'NOT IN (' + opv.join(',') + ')'; case $like => 'LIKE ' + opv; case $notLike => 'NOT LIKE ' + opv; } } }
咱們看一個複雜例子,基本上就是按上述流程來進行轉換。
Sequelize
:
var users = yield User.findAll({ 'where': { 'id': [3, 4], '$not': [ { 'id': { '$in': [1, 2] } }, { '$or': [ {'id': [1, 2]}, {'nick': null} ] } ], '$and': [ {'id': [1, 2]}, {'nick': null} ], '$or': [ {'id': [1, 2]}, {'nick': null} ] } });
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` WHERE `user`.`id` IN (3, 4) AND NOT ( `user`.`id` IN (1, 2) AND (`user`.`id` IN (1, 2) OR `user`.`nick` IS NULL) ) AND ( `user`.`id` IN (1, 2) AND `user`.`nick` IS NULL ) AND ( `user`.`id` IN (1, 2) OR `user`.`nick` IS NULL );
Sequelize
:
var users = yield User.findAll({ 'order': [ ['id', 'DESC'], ['nick'] ] });
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` ORDER BY `user`.`id` DESC, `user`.`nick`;
Sequelize
:
var countPerPage = 20, currentPage = 5; var users = yield User.findAll({ 'limit': countPerPage, // 每頁多少條 'offset': countPerPage * (currentPage - 1) // 跳過多少條 });
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` LIMIT 80, 20;
Sequelize
:
user = yield User.findById(1); user = yield User.findOne({ 'where': {'nick': 'a'} });
SQL
:
SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` WHERE `user`.`id` = 1 LIMIT 1; SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` WHERE `user`.`nick` = 'a' LIMIT 1;
Sequelize
:
var result = yield User.findAndCountAll({ 'limit': 20, 'offset': 0 }); console.log(result);
SQL
:
SELECT count(*) AS `count` FROM `users` AS `user`; SELECT `id`, `emp_id`, `nick`, `department`, `created_at`, `updated_at` FROM `users` AS `user` LIMIT 20;
這個方法會執行2個SQL
,返回的result
對象將包含2個字段:result.count
是數據總數,result.rows
是符合查詢條件的全部數據。
Sequelize
:
var users = yield User.bulkCreate( [ {'emp_id': 'a', 'nick': 'a'}, {'emp_id': 'b', 'nick': 'b'}, {'emp_id': 'c', 'nick': 'c'} ] );
SQL
:
INSERT INTO `users` (`id`,`emp_id`,`nick`,`created_at`,`updated_at`) VALUES (NULL,'a','a','2015-11-03 02:43:30','2015-11-03 02:43:30'), (NULL,'b','b','2015-11-03 02:43:30','2015-11-03 02:43:30'), (NULL,'c','c','2015-11-03 02:43:30','2015-11-03 02:43:30');
這裏須要注意,返回的users
數組裏面每一個對象的id
值會是null
。若是須要id
值,能夠從新取下數據。
Sequelize
:
var affectedRows = yield User.update( {'nick': 'hhhh'}, { 'where': { 'id': [2, 3, 4] } } );
SQL
:
UPDATE `users` SET `nick`='hhhh',`updated_at`='2015-11-03 02:51:05' WHERE `id` IN (2, 3, 4);
這裏返回的affectedRows
實際上是一個數組,裏面只有一個元素,表示更新的數據條數(看起來像是Sequelize
的一個bug
)。
Sequelize
:
var affectedRows = yield User.destroy({ 'where': {'id': [2, 3, 4]} });
SQL
:
DELETE FROM `users` WHERE `id` IN (2, 3, 4);
這裏返回的affectedRows
是一個數字,表示刪除的數據條數。
關係通常有三種:一對1、一對多、多對多。Sequelize
提供了清晰易用的接口來定義關係、進行表間的操做。
當說到關係查詢時,通常會須要獲取多張表的數據。有建議用連表查詢join
的,有不建議的。個人見解是,join
查詢這種黑科技在數據量小的狀況下可使用,基本沒有什麼影響,數據量大的時候,join
的性能可能會是硬傷,應該儘可能避免,能夠分別根據索引取單表數據而後在應用層對數據進行join
、merge
。固然,查詢時必定要分頁,不要findAll
。
Sequelize
:
var User = sequelize.define('user', { 'emp_id': { 'type': Sequelize.CHAR(10), 'allowNull': false, 'unique': true } } ); var Account = sequelize.define('account', { 'email': { 'type': Sequelize.CHAR(20), 'allowNull': false } } ); /* * User的實例對象將擁有getAccount、setAccount、addAccount方法 */ User.hasOne(Account); /* * Account的實例對象將擁有getUser、setUser、addUser方法 */ Account.belongsTo(User);
SQL
:
CREATE TABLE IF NOT EXISTS `users` ( `id` INTEGER NOT NULL auto_increment , `emp_id` CHAR(10) NOT NULL UNIQUE, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS `accounts` ( `id` INTEGER NOT NULL auto_increment , `email` CHAR(20) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `user_id` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINE=InnoDB;
能夠看到,這種關係中外鍵user_id
加在了Account
上。另外,Sequelize
還給咱們生成了外鍵約束。
通常來講,外鍵約束在有些本身定製的數據庫系統裏面是禁止的,由於會帶來一些性能問題。因此,建表的SQL
通常就去掉約束,同時給外鍵加一個索引(加速查詢),數據的一致性就靠應用層來保證了。
Sequelize
:
var user = yield User.create({'emp_id': '1'}); var account = user.createAccount({'email': 'a'}); console.log(account.get({'plain': true}));
SQL
:
INSERT INTO `users` (`id`,`emp_id`,`updated_at`,`created_at`) VALUES (DEFAULT,'1','2015-11-03 06:24:53','2015-11-03 06:24:53'); INSERT INTO `accounts` (`id`,`email`,`user_id`,`updated_at`,`created_at`) VALUES (DEFAULT,'a',1,'2015-11-03 06:24:53','2015-11-03 06:24:53');
SQL
執行邏輯是:
使用對應的的user_id
做爲外鍵在accounts
表裏插入一條數據。
Sequelize
:
var anotherAccount = yield Account.create({'email': 'b'}); console.log(anotherAccount); anotherAccount = yield user.setAccount(anotherAccount); console.log(anotherAccount);
SQL
:
INSERT INTO `accounts` (`id`,`email`,`updated_at`,`created_at`) VALUES (DEFAULT,'b','2015-11-03 06:37:14','2015-11-03 06:37:14'); SELECT `id`, `email`, `created_at`, `updated_at`, `user_id` FROM `accounts` AS `account` WHERE (`account`.`user_id` = 1); UPDATE `accounts` SET `user_id`=NULL,`updated_at`='2015-11-03 06:37:14' WHERE `id` = 1; UPDATE `accounts` SET `user_id`=1,`updated_at`='2015-11-03 06:37:14' WHERE `id` = 2;
SQL
執行邏輯是:
插入一條account
數據,此時外鍵user_id
是空的,尚未關聯user
找出當前user
所關聯的account
並將其user_id
置爲`NUL(爲了保證一對一關係)
設置新的acount
的外鍵user_id
爲user
的屬性id
,生成關係
Sequelize
:
yield user.setAccount(null);
SQL
:
SELECT `id`, `email`, `created_at`, `updated_at`, `user_id` FROM `accounts` AS `account` WHERE (`account`.`user_id` = 1); UPDATE `accounts` SET `user_id`=NULL,`updated_at`='2015-11-04 00:11:35' WHERE `id` = 1;
這裏的刪除實際上只是「切斷」關係,並不會真正的物理刪除記錄。
SQL
執行邏輯是:
找出user
所關聯的account
數據
將其外鍵user_id
設置爲NULL
,完成關係的「切斷」
Sequelize
:
var account = yield user.getAccount(); console.log(account);
SQL
:
SELECT `id`, `email`, `created_at`, `updated_at`, `user_id` FROM `accounts` AS `account` WHERE (`account`.`user_id` = 1);
這裏就是調用user
的getAccount
方法,根據外鍵來獲取對應的account
。
可是其實咱們用面向對象的思惟來思考應該是獲取user
的時候就能經過user.account
的方式來訪問account
對象。這能夠經過Sequelize
的eager loading
(急加載,和懶加載相反)來實現。
eager loading
的含義是說,取一個模型的時候,同時也把相關的模型數據也給我取過來(我很着急,不能按默認那種取一個模型就取一個模型的方式,我還要更多)。方法以下:
Sequelize
:
var user = yield User.findById(1, { 'include': [Account] }); console.log(user.get({'plain': true})); /* * 輸出相似: { id: 1, emp_id: '1', created_at: Tue Nov 03 2015 15:25:27 GMT+0800 (CST), updated_at: Tue Nov 03 2015 15:25:27 GMT+0800 (CST), account: { id: 2, email: 'b', created_at: Tue Nov 03 2015 15:25:27 GMT+0800 (CST), updated_at: Tue Nov 03 2015 15:25:27 GMT+0800 (CST), user_id: 1 } } */
SQL
:
SELECT `user`.`id`, `user`.`emp_id`, `user`.`created_at`, `user`.`updated_at`, `account`.`id` AS `account.id`, `account`.`email` AS `account.email`, `account`.`created_at` AS `account.created_at`, `account`.`updated_at` AS `account.updated_at`, `account`.`user_id` AS `account.user_id` FROM `users` AS `user` LEFT OUTER JOIN `accounts` AS `account` ON `user`.`id` = `account`.`user_id` WHERE `user`.`id` = 1 LIMIT 1;
能夠看到,咱們對2個表進行了一個外聯接,從而在取user
的同時也獲取到了account
。
若是咱們重複調用user.createAccount
方法,實際上會在數據庫裏面生成多條user_id
同樣的數據,並非真正的一對一。
因此,在應用層保證一致性時,就須要咱們遵循良好的編碼約定。新增就用user.createAccount
,更改就用user.setAccount
。
也能夠給user_id
加一個UNIQUE
約束,在數據庫層面保證一致性,這時就須要作好try/catch
,發生插入異常的時候可以知道是由於插入了多個account
。
另外,咱們上面都是使用user
來對account
進行操做。實際上反向操做也是能夠的,這是由於咱們定義了Account.belongsTo(User)
。在Sequelize
裏面定義關係時,關係的調用方會得到相關的「關係」方法,通常爲了兩邊都能操做,會同時定義雙向關係(這裏雙向關係指的是模型層面,並不會在數據庫表中出現兩個表都加上外鍵的狀況,請放心)。
Sequelize
:
var User = sequelize.define('user', { 'emp_id': { 'type': Sequelize.CHAR(10), 'allowNull': false, 'unique': true } } ); var Note = sequelize.define('note', { 'title': { 'type': Sequelize.CHAR(64), 'allowNull': false } } ); /* * User的實例對象將擁有getNotes、setNotes、addNote、createNote、removeNote、hasNote方法 */ User.hasMany(Note); /* * Note的實例對象將擁有getUser、setUser、createUser方法 */ Note.belongsTo(User);
SQL
:
CREATE TABLE IF NOT EXISTS `users` ( `id` INTEGER NOT NULL auto_increment , `emp_id` CHAR(10) NOT NULL UNIQUE, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS `notes` ( `id` INTEGER NOT NULL auto_increment , `title` CHAR(64) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `user_id` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE ) ENGINE=InnoDB;
能夠看到這種關係中,外鍵user_id
加在了多的一端(notes
表)。同時相關的模型也自動得到了一些方法。
Sequelize
:
var user = yield User.create({'emp_id': '1'}); var note = yield user.createNote({'title': 'a'}); console.log(note);
SQL
:
NSERT INTO `users` (`id`,`emp_id`,`updated_at`,`created_at`) VALUES (DEFAULT,'1','2015-11-03 23:52:05','2015-11-03 23:52:05'); INSERT INTO `notes` (`id`,`title`,`user_id`,`updated_at`,`created_at`) VALUES (DEFAULT,'a',1,'2015-11-03 23:52:05','2015-11-03 23:52:05');
SQL
執行邏輯:
使用user
的主鍵id
值做爲外鍵直接在notes
表裏插入一條數據。
Sequelize
:
var user = yield User.create({'emp_id': '1'}); var note = yield Note.create({'title': 'b'}); yield user.addNote(note);
SQL
:
INSERT INTO `users` (`id`,`emp_id`,`updated_at`,`created_at`) VALUES (DEFAULT,'1','2015-11-04 00:02:56','2015-11-04 00:02:56'); INSERT INTO `notes` (`id`,`title`,`updated_at`,`created_at`) VALUES (DEFAULT,'b','2015-11-04 00:02:56','2015-11-04 00:02:56'); UPDATE `notes` SET `user_id`=1,`updated_at`='2015-11-04 00:02:56' WHERE `id` IN (1);
SQL
執行邏輯:
插入一條note
數據,此時該條數據的外鍵user_id
爲空
使用user
的屬性id
值再更新該條note
數據,設置好外鍵,完成關係創建
Sequelize
:
// 爲user增長note一、note2 var user = yield User.create({'emp_id': '1'}); var note1 = yield user.createNote({'title': 'a'}); var note2 = yield user.createNote({'title': 'b'}); // 先建立note三、note4 var note3 = yield Note.create({'title': 'c'}); var note4 = yield Note.create({'title': 'd'}); // user擁有的note更改成note三、note4 yield user.setNotes([note3, note4]);
SQL
:
/* 省去了建立語句 */ SELECT `id`, `title`, `created_at`, `updated_at`, `user_id` FROM `notes` AS `note` WHERE `note`.`user_id` = 1; UPDATE `notes` SET `user_id`=NULL,`updated_at`='2015-11-04 12:45:12' WHERE `id` IN (1, 2); UPDATE `notes` SET `user_id`=1,`updated_at`='2015-11-04 12:45:12' WHERE `id` IN (3, 4);
SQL
執行邏輯:
根據user
的屬性id
查詢全部相關的note
數據
將note1
、note2
的外鍵user_id
置爲NULL
,切斷關係
將note3
、note4
的外鍵user_id
置爲user
的屬性id
,完成關係創建
這裏爲啥還要查出全部的note
數據呢?由於咱們須要根據傳人setNotes
的數組來計算出哪些note
要切斷關係、哪些要新增關係,因此就須要查出來進行一個計算集合的「交集」運算。
Sequelize
:
var user = yield User.create({'emp_id': '1'}); var note1 = yield user.createNote({'title': 'a'}); var note2 = yield user.createNote({'title': 'b'}); yield user.setNotes([]);
SQL
:
SELECT `id`, `title`, `created_at`, `updated_at`, `user_id` FROM `notes` AS `note` WHERE `note`.`user_id` = 1; UPDATE `notes` SET `user_id`=NULL,`updated_at`='2015-11-04 12:50:08' WHERE `id` IN (1, 2);
實際上,上面說到的「改」已經有「刪」的操做了(去掉note1
、note2
的關係)。這裏的操做是刪掉用戶的全部note
數據,直接執行user.setNotes([])
便可。
SQL
執行邏輯:
根據user
的屬性id
查出全部相關的note
數據
將其外鍵user_id
置爲NULL
,切斷關係
還有一個真正的刪除方法,就是removeNote
。以下所示:
Sequelize
:
yield user.removeNote(note);
SQL
:
UPDATE `notes` SET `user_id`=NULL,`updated_at`='2015-11-06 01:40:12' WHERE `user_id` = 1 AND `id` IN (1);
查詢user
的全部知足條件的note
數據。
Sequelize
:
var notes = yield user.getNotes({ 'where': { 'title': { '$like': '%css%' } } }); notes.forEach(function(note) { console.log(note); });
SQL
:
SELECT `id`, `title`, `created_at`, `updated_at`, `user_id` FROM `notes` AS `note` WHERE (`note`.`user_id` = 1 AND `note`.`title` LIKE '%a%');
這種方法的SQL
很簡單,直接根據user
的id
值來查詢知足條件的note
便可。
查詢全部知足條件的note
,同時獲取note
屬於哪一個user
。
Sequelize
:
var notes = yield Note.findAll({ 'include': [User], 'where': { 'title': { '$like': '%css%' } } }); notes.forEach(function(note) { // note屬於哪一個user能夠經過note.user訪問 console.log(note); });
SQL
:
SELECT `note`.`id`, `note`.`title`, `note`.`created_at`, `note`.`updated_at`, `note`.`user_id`, `user`.`id` AS `user.id`, `user`.`emp_id` AS `user.emp_id`, `user`.`created_at` AS `user.created_at`, `user`.`updated_at` AS `user.updated_at` FROM `notes` AS `note` LEFT OUTER JOIN `users` AS `user` ON `note`.`user_id` = `user`.`id` WHERE `note`.`title` LIKE '%css%';
這種方法,由於獲取的主體是note
,因此將notes
去left join
了users
。
查詢全部知足條件的user
,同時獲取該user
全部知足條件的note
。
Sequelize
:
var users = yield User.findAll({ 'include': [Note], 'where': { 'created_at': { '$lt': new Date() } } }); users.forEach(function(user) { // user的notes能夠經過user.notes訪問 console.log(user); });
SQL
:
SELECT `user`.`id`, `user`.`emp_id`, `user`.`created_at`, `user`.`updated_at`, `notes`.`id` AS `notes.id`, `notes`.`title` AS `notes.title`, `notes`.`created_at` AS `notes.created_at`, `notes`.`updated_at` AS `notes.updated_at`, `notes`.`user_id` AS `notes.user_id` FROM `users` AS `user` LEFT OUTER JOIN `notes` AS `notes` ON `user`.`id` = `notes`.`user_id` WHERE `user`.`created_at` < '2015-11-05 01:51:35';
這種方法獲取的主體是user
,因此將users
去left join
了notes
。
關於各類join的區別,能夠參考:http://blog.codinghorror.com/a-visual-explanation-of-sql-joins/。
關於eager loading
我想再囉嗦幾句。include
裏面傳遞的是去取相關模型,默認是取所有,咱們也能夠再對這個模型進行一層過濾。像下面這樣:
Sequelize
:
// 查詢建立時間在今天以前的全部user,同時獲取他們note的標題中含有關鍵字css的全部note var users = yield User.findAll({ 'include': [ { 'model': Note, 'where': { 'title': { '$like': '%css%' } } } ], 'where': { 'created_at': { '$lt': new Date() } } });
SQL
:
SELECT `user`.`id`, `user`.`emp_id`, `user`.`created_at`, `user`.`updated_at`, `notes`.`id` AS `notes.id`, `notes`.`title` AS `notes.title`, `notes`.`created_at` AS `notes.created_at`, `notes`.`updated_at` AS `notes.updated_at`, `notes`.`user_id` AS `notes.user_id` FROM `users` AS `user` INNER JOIN `notes` AS `notes` ON `user`.`id` = `notes`.`user_id` AND `notes`.`title` LIKE '%css%' WHERE `user`.`created_at` < '2015-11-05 01:58:31';
注意:當咱們對include
的模型加了where
過濾時,會使用inner join
來進行查詢,這樣保證只有那些擁有標題含有css
關鍵詞note
的用戶纔會返回。
在多對多關係中,必需要額外一張關係表來將2個表進行關聯,這張表能夠是單純的一個關係表,也能夠是一個實際的模型(含有本身的額外屬性來描述關係)。我比較喜歡用一個模型的方式,這樣方便之後作擴展。
Sequelize
:
var Note = sequelize.define('note', { 'title': { 'type': Sequelize.CHAR(64), 'allowNull': false } } ); var Tag = sequelize.define('tag', { 'name': { 'type': Sequelize.CHAR(64), 'allowNull': false, 'unique': true } } ); var Tagging = sequelize.define('tagging', { 'type': { 'type': Sequelize.INTEGER(), 'allowNull': false } } ); // Note的實例擁有getTags、setTags、addTag、addTags、createTag、removeTag、hasTag方法 Note.belongsToMany(Tag, {'through': Tagging}); // Tag的實例擁有getNotes、setNotes、addNote、addNotes、createNote、removeNote、hasNote方法 Tag.belongsToMany(Note, {'through': Tagging});
SQL
:
CREATE TABLE IF NOT EXISTS `notes` ( `id` INTEGER NOT NULL auto_increment , `title` CHAR(64) NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS `tags` ( `id` INTEGER NOT NULL auto_increment , `name` CHAR(64) NOT NULL UNIQUE, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS `taggings` ( `type` INTEGER NOT NULL, `created_at` DATETIME NOT NULL, `updated_at` DATETIME NOT NULL, `tag_id` INTEGER , `note_id` INTEGER , PRIMARY KEY (`tag_id`, `note_id`), FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (`note_id`) REFERENCES `notes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB;
能夠看到,多對多關係中單獨生成了一張關係表,並設置了2個外鍵tag_id
和note_id
來和tags
和notes
進行關聯。關於關係表的命名,我比較喜歡使用動詞,由於這張表是用來表示兩張表的一種聯繫,並且這種聯繫多數時候伴隨着一種動做。好比:用戶收藏商品(collecting
)、用戶購買商品(buying
)、用戶加入項目(joining
)等等。
Sequelize
:
var note = yield Note.create({'title': 'note'}); yield note.createTag({'name': 'tag'}, {'type': 0});
SQL
:
INSERT INTO `notes` (`id`,`title`,`updated_at`,`created_at`) VALUES (DEFAULT,'note','2015-11-06 02:14:38','2015-11-06 02:14:38'); INSERT INTO `tags` (`id`,`name`,`updated_at`,`created_at`) VALUES (DEFAULT,'tag','2015-11-06 02:14:38','2015-11-06 02:14:38'); INSERT INTO `taggings` (`tag_id`,`note_id`,`type`,`created_at`,`updated_at`) VALUES (1,1,0,'2015-11-06 02:14:38','2015-11-06 02:14:38');
SQL
執行邏輯:
在notes
表插入記錄
在tags
表中插入記錄
使用對應的值設置外鍵tag_id
和note_id
以及關係模型自己須要的屬性(type: 0
)在關係表tagging
中插入記錄
關係表自己須要的屬性,經過傳遞一個額外的對象給設置方法來實現。
Sequelize
:
var note = yield Note.create({'title': 'note'}); var tag = yield Tag.create({'name': 'tag'}); yield note.addTag(tag, {'type': 1});
SQL
:
INSERT INTO `notes` (`id`,`title`,`updated_at`,`created_at`) VALUES (DEFAULT,'note','2015-11-06 02:20:52','2015-11-06 02:20:52'); INSERT INTO `tags` (`id`,`name`,`updated_at`,`created_at`) VALUES (DEFAULT,'tag','2015-11-06 02:20:52','2015-11-06 02:20:52'); INSERT INTO `taggings` (`tag_id`,`note_id`,`type`,`created_at`,`updated_at`) VALUES (1,1,1,'2015-11-06 02:20:52','2015-11-06 02:20:52');
這種方法和上面的方法其實是同樣的。只是咱們先手動create
了一個Tag
模型。
Sequelize
:
var note = yield Note.create({'title': 'note'}); var tag1 = yield Tag.create({'name': 'tag1'}); var tag2 = yield Tag.create({'name': 'tag2'}); yield note.addTags([tag1, tag2], {'type': 2});
SQL
:
INSERT INTO `notes` (`id`,`title`,`updated_at`,`created_at`) VALUES (DEFAULT,'note','2015-11-06 02:25:18','2015-11-06 02:25:18'); INSERT INTO `tags` (`id`,`name`,`updated_at`,`created_at`) VALUES (DEFAULT,'tag1','2015-11-06 02:25:18','2015-11-06 02:25:18'); INSERT INTO `tags` (`id`,`name`,`updated_at`,`created_at`) VALUES (DEFAULT,'tag2','2015-11-06 02:25:18','2015-11-06 02:25:18'); INSERT INTO `taggings` (`tag_id`,`note_id`,`type`,`created_at`,`updated_at`) VALUES (1,1,2,'2015-11-06 02:25:18','2015-11-06 02:25:18'), (2,1,2,'2015-11-06 02:25:18','2015-11-06 02:25:18');
這種方法能夠進行批量添加。當執行addTags
時,實際上就是設置好對應的外鍵及關係模型自己的屬性,而後在關係表中批量的插入數據。
Sequelize
:
// 先添加幾個tag var note = yield Note.create({'title': 'note'}); var tag1 = yield Tag.create({'name': 'tag1'}); var tag2 = yield Tag.create({'name': 'tag2'}); yield note.addTags([tag1, tag2], {'type': 2}); // 將tag改掉 var tag3 = yield Tag.create({'name': 'tag3'}); var tag4 = yield Tag.create({'name': 'tag4'}); yield note.setTags([tag3, tag4], {'type': 3});
SQL
:
/* 前面添加部分的sql,和上面同樣*/ INSERT INTO `notes` (`id`,`title`,`updated_at`,`created_at`) VALUES (DEFAULT,'note','2015-11-06 02:25:18','2015-11-06 02:25:18'); INSERT INTO `tags` (`id`,`name`,`updated_at`,`created_at`) VALUES (DEFAULT,'tag1','2015-11-06 02:25:18','2015-11-06 02:25:18'); INSERT INTO `tags` (`id`,`name`,`updated_at`,`created_at`) VALUES (DEFAULT,'tag2','2015-11-06 02:25:18','2015-11-06 02:25:18'); INSERT INTO `taggings` (`tag_id`,`note_id`,`type`,`created_at`,`updated_at`) VALUES (1,1,2,'2015-11-06 02:25:18','2015-11-06 02:25:18'), (2,1,2,'2015-11-06 02:25:18','2015-11-06 02:25:18'); /* 更改部分的sql */ INSERT INTO `tags` (`id`,`name`,`updated_at`,`created_at`) VALUES (DEFAULT,'tag3','2015-11-06 02:29:55','2015-11-06 02:29:55'); INSERT INTO `tags` (`id`,`name`,`updated_at`,`created_at`) VALUES (DEFAULT,'tag4','2015-11-06 02:29:55','2015-11-06 02:29:55'); /* 先刪除關係 */ DELETE FROM `taggings` WHERE `note_id` = 1 AND `tag_id` IN (1, 2); /* 插入新關係 */ INSERT INTO `taggings` (`tag_id`,`note_id`,`type`,`created_at`,`updated_at`) VALUES (3,1,3,'2015-11-06 02:29:55','2015-11-06 02:29:55'), (4,1,3,'2015-11-06 02:29:55','2015-11-06 02:29:55');
執行邏輯是,先將tag1
、tag2
在關係表中的關係刪除,而後再將tag3
、tag4
對應的關係插入關係表。
Sequelize
:
// 先添加幾個tag var note = yield Note.create({'title': 'note'}); var tag1 = yield Tag.create({'name': 'tag1'}); var tag2 = yield Tag.create({'name': 'tag2'}); var tag3 = yield Tag.create({'name': 'tag2'}); yield note.addTags([tag1, tag2, tag3], {'type': 2}); // 刪除一個 yield note.removeTag(tag1); // 所有刪除 yield note.setTags([]);
SQL
:
/* 刪除一個 */ DELETE FROM `taggings` WHERE `note_id` = 1 AND `tag_id` IN (1); /* 刪除所有 */ SELECT `type`, `created_at`, `updated_at`, `tag_id`, `note_id` FROM `taggings` AS `tagging` WHERE `tagging`.`note_id` = 1; DELETE FROM `taggings` WHERE `note_id` = 1 AND `tag_id` IN (2, 3);
刪除一個很簡單,直接將關係表中的數據刪除。
所有刪除時,首先須要查出關係表中note_id
對應的全部數據,而後一次刪掉。
查詢note
全部知足條件的tag
。
Sequelize
:
var tags = yield note.getTags({ //這裏能夠對tags進行where }); tags.forEach(function(tag) { // 關係模型能夠經過tag.tagging來訪問 console.log(tag); });
SQL
:
SELECT `tag`.`id`, `tag`.`name`, `tag`.`created_at`, `tag`.`updated_at`, `tagging`.`type` AS `tagging.type`, `tagging`.`created_at` AS `tagging.created_at`, `tagging`.`updated_at` AS `tagging.updated_at`, `tagging`.`tag_id` AS `tagging.tag_id`, `tagging`.`note_id` AS `tagging.note_id` FROM `tags` AS `tag` INNER JOIN `taggings` AS `tagging` ON `tag`.`id` = `tagging`.`tag_id` AND `tagging`.`note_id` = 1;
能夠看到這種查詢,就是執行一個inner join
。
查詢全部知足條件的tag
,同時獲取每一個tag
所在的note
。
Sequelize
:
var tags = yield Tag.findAll({ 'include': [ { 'model': Note // 這裏能夠對notes進行where } ] // 這裏能夠對tags進行where }); tags.forEach(function(tag) { // tag的notes能夠經過tag.notes訪問,關係模型能夠經過tag.notes[0].tagging訪問 console.log(tag); });
SQL
:
SELECT `tag`.`id`, `tag`.`name`, `tag`.`created_at`, `tag`.`updated_at`, `notes`.`id` AS `notes.id`, `notes`.`title` AS `notes.title`, `notes`.`created_at` AS `notes.created_at`, `notes`.`updated_at` AS `notes.updated_at`, `notes.tagging`.`type` AS `notes.tagging.type`, `notes.tagging`.`created_at` AS `notes.tagging.created_at`, `notes.tagging`.`updated_at` AS `notes.tagging.updated_at`, `notes.tagging`.`tag_id` AS `notes.tagging.tag_id`, `notes.tagging`.`note_id` AS `notes.tagging.note_id` FROM `tags` AS `tag` LEFT OUTER JOIN ( `taggings` AS `notes.tagging` INNER JOIN `notes` AS `notes` ON `notes`.`id` = `notes.tagging`.`note_id` ) ON `tag`.`id` = `notes.tagging`.`tag_id`;
這個查詢就稍微有點複雜。首先是notes
和taggings
進行了一個inner join
,選出notes
;而後tags
和剛join
出的集合再作一次left join
,獲得結果。
查詢全部知足條件的note
,同時獲取每一個note
全部知足條件的tag
。
Sequelize
:
var notes = yield Note.findAll({ 'include': [ { 'model': Tag // 這裏能夠對tags進行where } ] // 這裏能夠對notes進行where }); notes.forEach(function(note) { // note的tags能夠經過note.tags訪問,關係模型經過note.tags[0].tagging訪問 console.log(note); });
SQL
:
SELECT `note`.`id`, `note`.`title`, `note`.`created_at`, `note`.`updated_at`, `tags`.`id` AS `tags.id`, `tags`.`name` AS `tags.name`, `tags`.`created_at` AS `tags.created_at`, `tags`.`updated_at` AS `tags.updated_at`, `tags.tagging`.`type` AS `tags.tagging.type`, `tags.tagging`.`created_at` AS `tags.tagging.created_at`, `tags.tagging`.`updated_at` AS `tags.tagging.updated_at`, `tags.tagging`.`tag_id` AS `tags.tagging.tag_id`, `tags.tagging`.`note_id` AS `tags.tagging.note_id` FROM `notes` AS `note` LEFT OUTER JOIN ( `taggings` AS `tags.tagging` INNER JOIN `tags` AS `tags` ON `tags`.`id` = `tags.tagging`.`tag_id` ) ON `note`.`id` = `tags.tagging`.`note_id`;
這個查詢和上面的查詢相似。首先是tags
和taggins
進行了一個inner join
,選出tags
;而後notes
和剛join
出的集合再作一次left join
,獲得結果。
這篇文章已經夠長了,可是其實咱們還有不少沒有涉及的東西,好比:聚合函數及查詢(having
、group by
)、模型的驗證(validate
)、定義鉤子(hooks
)、索引等等。
這些主題下次再來寫寫。