資源:
Sequelize 中文文檔
sequelize API
sequelize 小技巧
sequelize 菜鳥教程
核心概念:
連接數(shù)據(jù)庫(kù):
const { Sequelize } = require('sequelize');
// 方法 1: 傳遞一個(gè)連接 URI
const sequelize = new Sequelize('sqlite::memory:') // Sqlite 示例
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Postgres 示例
// 方法 2: 分別傳遞參數(shù) (sqlite)
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'path/to/database.sqlite'
});
// 方法 2: 分別傳遞參數(shù) (其它數(shù)據(jù)庫(kù))
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: /* 選擇 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 */
});
模型定義:
sequelize.define('model_name',{filed:value})
創(chuàng)建表:
首先定義模型: model
然后同步:model.sync()
創(chuàng)建表會(huì)自動(dòng)創(chuàng)建主鍵,默認(rèn)為 id
增刪改查:
-
新增數(shù)據(jù):model.create 相當(dāng)于 build save兩步合并; -
批量新增:model.bulkCreate([model,...],{...}) ;
但是默認(rèn)不會(huì)運(yùn)行驗(yàn)證器,需要手動(dòng)開啟
User.bulkCreate([
{ username: 'foo' },
{ username: 'bar', admin: true }
], { validate: true,//手動(dòng)開啟驗(yàn)證器
fields: ['username']//限制字段
});
// 因?yàn)橄拗屏俗侄沃淮鎢sername,foo 和 bar 都不會(huì)是管理員.
-
更新model.update
相當(dāng)于 set, save兩步合并,通常就直接修改實(shí)例屬性,然后save()更新; -
部分更新:
通過傳遞一個(gè)列名數(shù)組,可以定義在調(diào)用 save 時(shí)應(yīng)該保存哪些屬性
save({fields:[ 'name',... ]}) 只更新數(shù)組里面的字段 -
刪除model.destroy -
重載實(shí)例:model.reload -
查詢:
include參數(shù) 對(duì)應(yīng)sql的 join連接操作
findAll 查找所有的
findByPk 根據(jù)主鍵查找
findOne 找到第一個(gè)實(shí)例
findOrCreate 查找到或創(chuàng)建實(shí)例
findAndCountAll 分頁(yè)查找
查詢的選項(xiàng)參數(shù):
Model.findAll({
//查詢指定字段
attributes: ['foo', 'bar',
[sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats']//函數(shù)聚合
] ,
where: {//對(duì)應(yīng)where子句,過濾
authorId: 2,
authorId: {
[Sequelize.Op.eq]: 2 //操作符運(yùn)算
}
},
order:[], //排序
group:'name',//分組
limit:10,//限制
offset:1//頁(yè)
});
實(shí)用方法:
count,max, min 和 sum
原始查詢:
const { QueryTypes } = require('sequelize');
const users = await sequelize.query("SELECT * FROM `users`");//參數(shù)是sql語(yǔ)句
偏執(zhí)表:
Sequelize 支持 paranoid 表的概念
這意味著刪除記錄時(shí)不會(huì)真的刪除,而是給字段deletedAt值設(shè)置為時(shí)間戳
刪除的時(shí)候默認(rèn)是軟刪除,而不是硬刪除
class Post extends Model {}
Post.init({ /* 這是屬性 */ }, {
sequelize,
paranoid: true,// 傳遞該參數(shù),創(chuàng)建偏執(zhí)表
// 如果要為 deletedAt 列指定自定義名稱
deletedAt: 'destroyTime'
});
強(qiáng)制刪除:
await Post.destroy({
where: {
id: 1
},
force: true //硬刪除
});
軟刪除的實(shí)例,恢復(fù):
post.restore();
Post.restore({
where: {
likes: {
[Op.gt]: 100
}
}
});
查詢包含軟刪除的記錄:
await Post.findAll({
where: { foo: 'bar' },
paranoid: false
});
關(guān)聯(lián)類型:
對(duì)應(yīng) sql語(yǔ)句的 foreign key 進(jìn)行表關(guān)聯(lián)
HasOne BelongsTo HasMany BelongsToMany
const A = sequelize.define('A', /* ... */);
const B = sequelize.define('B', /* ... */);
A.hasOne(B); // A 有一個(gè) B ,外鍵在目標(biāo)模型(B)中定義
A.belongsTo(B); // A 屬于 B ,外鍵在目標(biāo)模型(A)中定義
A.hasMany(B); // A 有多個(gè) B 外鍵在目標(biāo)模型(B)中定義
A.belongsToMany(B, { through: 'C' }); // A 屬于多個(gè) B , 通過聯(lián)結(jié)表 C
A.belongsToMany(B, { through: 'C' }) 關(guān)聯(lián)意味著將表 C 用作聯(lián)結(jié)表,在 A 和 B 之間存在多對(duì)多關(guān)系. 具有外鍵(例如,aId 和 bId). Sequelize 將自動(dòng)創(chuàng)建此模型 C(除非已經(jīng)存在),并在其上定義適當(dāng)?shù)耐怄I.
創(chuàng)建標(biāo)準(zhǔn)關(guān)系:
- 創(chuàng)建一個(gè) 一對(duì)一 關(guān)系,
hasOne和belongsTo關(guān)聯(lián)一起使用; - 創(chuàng)建一個(gè) 一對(duì)多 關(guān)系,
hasManyhebelongsTo關(guān)聯(lián)一起使用; - 創(chuàng)建一個(gè) 多對(duì)多 關(guān)系, 兩個(gè)
belongsToMany調(diào)用一起使用.- 注意: 還有一個(gè) 超級(jí)多對(duì)多 關(guān)系,一次使用六個(gè)關(guān)聯(lián),將在高級(jí)多對(duì)多關(guān)系指南中進(jìn)行討論.
添加到實(shí)例的特殊方法:
創(chuàng)建關(guān)聯(lián)關(guān)系后,這些模型的實(shí)例會(huì)獲得特殊方法
例如:有兩個(gè)模型 Foo 和 Bar 擁有關(guān)聯(lián)關(guān)系,則根據(jù)關(guān)聯(lián)類型擁有以下可用方法;
Foo.hasOne(Bar) 和 Foo.belongsTo(Bar)#
fooInstance.getBar()fooInstance.setBar()fooInstance.createBar()
Foo.hasMany(Bar) 和 Foo.belongsToMany(Bar, { through: Baz })#
fooInstance.getBars()fooInstance.countBars()fooInstance.hasBar()fooInstance.hasBars()fooInstance.setBars()fooInstance.addBar()fooInstance.addBars()fooInstance.removeBar()fooInstance.removeBars()fooInstance.createBar()
多對(duì)多關(guān)系:
代碼分析:
- 創(chuàng)建表Foo,Bar ,設(shè)置為多對(duì)多,中間表為Foo_Bar
- sequelize.sync();同步到數(shù)據(jù)庫(kù),就是說如果模型對(duì)應(yīng)的表不存在就創(chuàng)建
插入數(shù)據(jù) foo, bar - foo.addBar(bar) ,foo 關(guān)聯(lián)了一個(gè)bar,反映到數(shù)據(jù)庫(kù)上面,則是中間表Foo_Bar插入一條數(shù)據(jù) INSERT INTO Foo_Bar (FooId,BarId) VALUES(1,1)
- Foo.findOne({ include: Bar });數(shù)據(jù)查詢,根據(jù)模型,查出Foo表的第一條數(shù)據(jù),
并帶上關(guān)聯(lián)表數(shù)據(jù),字段是Bars(因?yàn)槭嵌鄬?duì)多,所以這里是復(fù)數(shù)形式,每一條bar包含中間表的數(shù)據(jù)字段是 Foo_Bar
const Foo = sequelize.define('Foo', { name: DataTypes.TEXT });
const Bar = sequelize.define('Bar', { name: DataTypes.TEXT });
Foo.belongsToMany(Bar, { through: 'Foo_Bar' });
Bar.belongsToMany(Foo, { through: 'Foo_Bar' });
await sequelize.sync();
const foo = await Foo.create({ name: 'foo' });
const bar = await Bar.create({ name: 'bar' });
await foo.addBar(bar);// foo這條數(shù)據(jù)關(guān)聯(lián)了一條bar,反映到表上則是在中間表Foo_Bar上插入一條數(shù)據(jù)
const fetchedFoo =await Foo.findOne({ include: Bar });
console.log(JSON.stringify(fetchedFoo, null, 2));
輸出:
{
"id": 1,
"name": "foo",
"Bars": [
{
"id": 1,
"name": "bar",
"Foo_Bar": {
"FooId": 1,
"BarId": 1
}
}
]
}
高級(jí)關(guān)聯(lián)概念
預(yù)先加載:
查詢方法中使用 include 參數(shù)完成預(yù)先加載,翻譯成sql其實(shí)就是 通過join關(guān)聯(lián)子句;
創(chuàng)建關(guān)聯(lián):
可以一次性創(chuàng)建帶關(guān)聯(lián)關(guān)系的數(shù)據(jù)
高級(jí)M:N關(guān)聯(lián):
超級(jí)多對(duì)多:
超級(jí)多對(duì)多創(chuàng)建出來(lái)的表跟多對(duì)多一樣,沒什么區(qū)別,區(qū)別就是一次使用6個(gè)關(guān)聯(lián),然后就可以進(jìn)行各種預(yù)先加載
//模型:
const User = sequelize.define('user', {
username: DataTypes.STRING,
points: DataTypes.INTEGER
}, { timestamps: false });
const Profile = sequelize.define('profile', {
name: DataTypes.STRING
}, { timestamps: false });
//自定義中間表
const Grant = sequelize.define('grant', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false
},
selfGranted: DataTypes.BOOLEAN
}, { timestamps: false });
// 超級(jí)多對(duì)多關(guān)系
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
User.hasMany(Grant);
Grant.belongsTo(User);
Profile.hasMany(Grant);
Grant.belongsTo(Profile);
這樣,我們可以進(jìn)行各種預(yù)先加載:
// 全部可以使用:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });
多態(tài)關(guān)聯(lián):
多態(tài)關(guān)聯(lián),就是說一個(gè)聯(lián)結(jié)表的外鍵關(guān)聯(lián)多個(gè)表
由于外鍵引用了多個(gè)表,無(wú)法添加REFERENCES約束,需要禁用約束constraints: false
一對(duì)多的多態(tài)關(guān)聯(lián):
考慮模型 Image Video Comment ,
圖片,視頻都可以有多個(gè)評(píng)論,
但是一個(gè)評(píng)論只能是圖片跟視頻中的其中一種類型;
// Helper 方法
const uppercaseFirst = str => `${str[0].toUpperCase()}${str.substr(1)}`;
class Image extends Model {}
Image.init({
title: DataTypes.STRING,
url: DataTypes.STRING
}, { sequelize, modelName: 'image' });
class Video extends Model {}
Video.init({
title: DataTypes.STRING,
text: DataTypes.STRING
}, { sequelize, modelName: 'video' });
class Comment extends Model {
getCommentable(options) {//獲取評(píng)論關(guān)聯(lián)類型的那個(gè)實(shí)例
if (!this.commentableType) return Promise.resolve(null);
const mixinMethodName = `get${uppercaseFirst(this.commentableType)}`;
return this[mixinMethodName](options);
}
}
Comment.init({
title: DataTypes.STRING,
commentableId: DataTypes.INTEGER,
commentableType: DataTypes.STRING
}, { sequelize, modelName: 'comment' });
Image.hasMany(Comment, {
foreignKey: 'commentableId',
constraints: false,
scope: {//關(guān)聯(lián)作用域 commentableType = 'image'
commentableType: 'image'
}
});
Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false });
Video.hasMany(Comment, {
foreignKey: 'commentableId',
constraints: false,
scope: {//關(guān)聯(lián)作用域 commentableType = 'video'
commentableType: 'video'
}
});
Comment.belongsTo(Video, { foreignKey: 'commentableId', constraints: false });
Comment.addHook("afterFind", findResult => {
console.log('afterFind,findResult=====',findResult);
if (!Array.isArray(findResult)) findResult = [findResult];
for (const instance of findResult) {
if (instance.commentableType === "image" && instance.image !== undefined) {
instance.commentable = instance.image;
} else if (instance.commentableType === "video" && instance.video !== undefined) {
instance.commentable = instance.video;
}
// 防止錯(cuò)誤:
delete instance.image;
delete instance.dataValues.image;
delete instance.video;
delete instance.dataValues.video;
}
});
多對(duì)多多態(tài)關(guān)聯(lián):
class Tag extends Model {
getTaggables(options) {
const images = await this.getImages(options);
const videos = await this.getVideos(options);
// 在單個(gè) taggables 數(shù)組中合并 images 和 videos
return images.concat(videos);
}
}
Tag.init({
name: DataTypes.STRING
}, { sequelize, modelName: 'tag' });
// 在這里,我們明確定義聯(lián)結(jié)模型
class Tag_Taggable extends Model {}
Tag_Taggable.init({
tagId: {
type: DataTypes.INTEGER,
unique: 'tt_unique_constraint'
},
taggableId: {
type: DataTypes.INTEGER,
unique: 'tt_unique_constraint',
references: null
},
taggableType: {
type: DataTypes.STRING,
unique: 'tt_unique_constraint'
}
}, { sequelize, modelName: 'tag_taggable' });
Image.belongsToMany(Tag, {
through: {
model: Tag_Taggable,
unique: false,
scope: {//注意這里的作用域用于關(guān)聯(lián)模型,因?yàn)檫@scope參數(shù)在through下
taggableType: 'image'
}
},
foreignKey: 'taggableId',
constraints: false
});
Tag.belongsToMany(Image, {
through: {
model: Tag_Taggable,
unique: false
},
foreignKey: 'tagId',
constraints: false
});
Video.belongsToMany(Tag, {
through: {
model: Tag_Taggable,
unique: false,
scope: {//注意這里的作用域用于關(guān)聯(lián)模型,因?yàn)檫@scope參數(shù)在through下
taggableType: 'video'
}
},
foreignKey: 'taggableId',
constraints: false
});
Tag.belongsToMany(Video, {
through: {
model: Tag_Taggable,
unique: false
},
foreignKey: 'tagId',
constraints: false
});
在目標(biāo)模型上應(yīng)用作用域
我們還可以在目標(biāo)模型上應(yīng)用關(guān)聯(lián)作用域. 我們甚至可以同時(shí)進(jìn)行,以下實(shí)例:
Image.belongsToMany(Tag, {
through: {
model: Tag_Taggable,
unique: false,
scope: {
taggableType: 'image'
}
},
scope: {
status: 'pending'
},
as: 'pendingTags',
foreignKey: 'taggableId',
constraints: false
});
其他主題
事務(wù):
Sequelize 支持兩種使用事務(wù)的方式:
- 非托管事務(wù): 提交和回滾事務(wù)應(yīng)由用戶手動(dòng)完成(通過調(diào)用適當(dāng)?shù)?Sequelize 方法).
- 托管事務(wù): 如果引發(fā)任何錯(cuò)誤,Sequelize 將自動(dòng)回滾事務(wù),否則將提交事務(wù). 另外,如果啟用了CLS(連續(xù)本地存儲(chǔ)),則事務(wù)回調(diào)中的所有查詢將自動(dòng)接收事務(wù)對(duì)象.
非托管事務(wù):
// 首先,我們開始一個(gè)事務(wù)并將其保存到變量中
const t = await sequelize.transaction();
try {
// 然后,我們進(jìn)行一些調(diào)用以將此事務(wù)作為參數(shù)傳遞:
const user = await User.create({
firstName: 'Bart',
lastName: 'Simpson'
}, { transaction: t });
await user.addSibling({
firstName: 'Lisa',
lastName: 'Simpson'
}, { transaction: t });
// 如果執(zhí)行到此行,且沒有引發(fā)任何錯(cuò)誤.
// 我們手動(dòng)提交事務(wù).
await t.commit();
} catch (error) {
// 如果執(zhí)行到達(dá)此行,則拋出錯(cuò)誤.
// 我們回滾事務(wù).
await t.rollback();
}
托管事務(wù):
try {
const result = await sequelize.transaction(async (t) => {
const user = await User.create({
firstName: 'Abraham',
lastName: 'Lincoln'
}, { transaction: t });
await user.setShooter({
firstName: 'John',
lastName: 'Boothe'
}, { transaction: t });
return user;
});
// 如果執(zhí)行到此行,則表示事務(wù)已成功提交,`result`是事務(wù)返回的結(jié)果
// `result` 就是從事務(wù)回調(diào)中返回的結(jié)果(在這種情況下為 `user`)
} catch (error) {
// 如果執(zhí)行到此,則發(fā)生錯(cuò)誤.
// 該事務(wù)已由 Sequelize 自動(dòng)回滾!
}
作用域:
不同于關(guān)聯(lián)作用域, 作用域定義在模型中,幫助重用代碼
作用域在模型定義中定義,可以是查找器對(duì)象,也可以是返回查找器對(duì)象的函數(shù) - 默認(rèn)作用域除外,該作用域只能是一個(gè)對(duì)象
class Project extends Model {}
Project.init({
// 屬性
}, {
defaultScope: {//默認(rèn)作用域
where: {
active: true
}
},
scopes: {
deleted: {
where: {
deleted: true
}
},
activeUsers: {
include: [
{ model: User, where: { active: true } }
]
},
random() {
return {
where: {
someNumber: Math.random()
}
}
}
},
sequelize,
modelName: 'project'
});
await Project.scope('deleted').findAll(); //用法就是調(diào)用scope方法傳入字符串,返回一個(gè)查詢對(duì)象
SELECT * FROM projects WHERE deleted = true // sql
await Project.scope('random', { method: ['accessLevel', 19] }).findAll();
SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19// sql