资源:
Sequelize 中文文档
sequelize API
sequelize 小技巧
sequelize 菜鸟教程
核心概念:
连接数据库:
const { Sequelize } = require('sequelize');
// 方法 1: 传递一个连接 URI
const sequelize = new Sequelize('sqlite::memory:') // Sqlite 示例
const sequelize = new Sequelize('postgres://user:[email protected]:5432/dbname') // Postgres 示例
// 方法 2: 分别传递参数 (sqlite)
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'path/to/database.sqlite'
});
// 方法 2: 分别传递参数 (其它数据库)
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: /* 选择 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 */
});
模型定义:
sequelize.define('model_name',{filed:value})
创建表:
首先定义模型: model
然后同步:model.sync()
创建表会自动创建主键,默认为 id
增删改查:
-
新增数据
:model.create 相当于 build save两步合并; -
批量新增
:model.bulkCreate([model,...],{...}) ;
但是默认不会运行验证器,需要手动开启
User.bulkCreate([
{ username: 'foo' },
{ username: 'bar', admin: true }
], { validate: true,//手动开启验证器
fields: ['username']//限制字段
});
// 因为限制了字段只存username,foo 和 bar 都不会是管理员.
-
更新
model.update
相当于 set, save两步合并,通常就直接修改实例属性,然后save()更新; -
部分更新
:
通过传递一个列名数组,可以定义在调用 save 时应该保存哪些属性
save({fields:[ 'name',... ]}) 只更新数组里面的字段 -
删除
model.destroy -
重载实例
:model.reload -
查询
:
include参数 对应sql的 join连接操作
findAll 查找所有的
findByPk 根据主键查找
findOne 找到第一个实例
findOrCreate 查找到或创建实例
findAndCountAll 分页查找
查询的选项参数
:
Model.findAll({
//查询指定字段
attributes: ['foo', 'bar',
[sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats']//函数聚合
] ,
where: {//对应where子句,过滤
authorId: 2,
authorId: {
[Sequelize.Op.eq]: 2 //操作符运算
}
},
order:[], //排序
group:'name',//分组
limit:10,//限制
offset:1//页
});
实用方法:
count,max, min 和 sum
原始查询:
const { QueryTypes } = require('sequelize');
const users = await sequelize.query("SELECT * FROM `users`");//参数是sql语句
偏执表:
Sequelize 支持 paranoid 表的概念
这意味着删除记录时不会真的删除,而是给字段deletedAt值设置为时间戳
删除的时候默认是软删除,而不是硬删除
class Post extends Model {}
Post.init({ /* 这是属性 */ }, {
sequelize,
paranoid: true,// 传递该参数,创建偏执表
// 如果要为 deletedAt 列指定自定义名称
deletedAt: 'destroyTime'
});
强制删除:
await Post.destroy({
where: {
id: 1
},
force: true //硬删除
});
软删除的实例,恢复:
post.restore();
Post.restore({
where: {
likes: {
[Op.gt]: 100
}
}
});
查询包含软删除的记录:
await Post.findAll({
where: { foo: 'bar' },
paranoid: false
});
关联类型:
对应 sql语句的 foreign key 进行表关联
HasOne BelongsTo HasMany BelongsToMany
const A = sequelize.define('A', /* ... */);
const B = sequelize.define('B', /* ... */);
A.hasOne(B); // A 有一个 B ,外键在目标模型(B)中定义
A.belongsTo(B); // A 属于 B ,外键在目标模型(A)中定义
A.hasMany(B); // A 有多个 B 外键在目标模型(B)中定义
A.belongsToMany(B, { through: 'C' }); // A 属于多个 B , 通过联结表 C
A.belongsToMany(B, { through: 'C' })
关联意味着将表 C
用作联结表,在 A
和 B
之间存在多对多关系. 具有外键(例如,aId
和 bId
). Sequelize 将自动创建此模型 C
(除非已经存在),并在其上定义适当的外键.
创建标准关系:
- 创建一个 一对一 关系,
hasOne
和belongsTo
关联一起使用; - 创建一个 一对多 关系,
hasMany
hebelongsTo
关联一起使用; - 创建一个 多对多 关系, 两个
belongsToMany
调用一起使用.- 注意: 还有一个 超级多对多 关系,一次使用六个关联,将在高级多对多关系指南中进行讨论.
添加到实例的特殊方法:
创建关联关系后,这些模型的实例会获得特殊方法
例如:有两个模型 Foo
和 Bar
拥有关联关系,则根据关联类型拥有以下可用方法;
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()
多对多关系:
代码分析:
- 创建表Foo,Bar ,设置为多对多,中间表为Foo_Bar
- sequelize.sync();同步到数据库,就是说如果模型对应的表不存在就创建
插入数据 foo, bar - foo.addBar(bar) ,foo 关联了一个bar,反映到数据库上面,则是中间表Foo_Bar插入一条数据 INSERT INTO Foo_Bar (FooId,BarId) VALUES(1,1)
- Foo.findOne({ include: Bar });数据查询,根据模型,查出Foo表的第一条数据,
并带上关联表数据,字段是Bars(因为是多对多,所以这里是复数形式,每一条bar包含中间表的数据字段是 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这条数据关联了一条bar,反映到表上则是在中间表Foo_Bar上插入一条数据
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
}
}
]
}
高级关联概念
预先加载:
查询方法中使用 include 参数完成预先加载,翻译成sql其实就是 通过join关联子句;
创建关联:
可以一次性创建带关联关系的数据
高级M:N关联:
超级多对多:
超级多对多创建出来的表跟多对多一样,没什么区别,区别就是一次使用6个关联,然后就可以进行各种预先加载
//模型:
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 });
// 超级多对多关系
User.belongsToMany(Profile, { through: Grant });
Profile.belongsToMany(User, { through: Grant });
User.hasMany(Grant);
Grant.belongsTo(User);
Profile.hasMany(Grant);
Grant.belongsTo(Profile);
这样,我们可以进行各种预先加载:
// 全部可以使用:
User.findAll({ include: Profile });
Profile.findAll({ include: User });
User.findAll({ include: Grant });
Profile.findAll({ include: Grant });
Grant.findAll({ include: User });
Grant.findAll({ include: Profile });
多态关联:
多态关联,就是说一个联结表的外键关联多个表
由于外键引用了多个表,无法添加REFERENCES约束,需要禁用约束constraints: false
一对多的多态关联:
考虑模型 Image Video Comment ,
图片,视频都可以有多个评论,
但是一个评论只能是图片跟视频中的其中一种类型;
// 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) {//获取评论关联类型的那个实例
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: {//关联作用域 commentableType = 'image'
commentableType: 'image'
}
});
Comment.belongsTo(Image, { foreignKey: 'commentableId', constraints: false });
Video.hasMany(Comment, {
foreignKey: 'commentableId',
constraints: false,
scope: {//关联作用域 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;
}
// 防止错误:
delete instance.image;
delete instance.dataValues.image;
delete instance.video;
delete instance.dataValues.video;
}
});
多对多多态关联:
class Tag extends Model {
getTaggables(options) {
const images = await this.getImages(options);
const videos = await this.getVideos(options);
// 在单个 taggables 数组中合并 images 和 videos
return images.concat(videos);
}
}
Tag.init({
name: DataTypes.STRING
}, { sequelize, modelName: 'tag' });
// 在这里,我们明确定义联结模型
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: {//注意这里的作用域用于关联模型,因为这scope参数在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: {//注意这里的作用域用于关联模型,因为这scope参数在through下
taggableType: 'video'
}
},
foreignKey: 'taggableId',
constraints: false
});
Tag.belongsToMany(Video, {
through: {
model: Tag_Taggable,
unique: false
},
foreignKey: 'tagId',
constraints: false
});
在目标模型上应用作用域
我们还可以在目标模型上应用关联作用域. 我们甚至可以同时进行,以下实例:
Image.belongsToMany(Tag, {
through: {
model: Tag_Taggable,
unique: false,
scope: {
taggableType: 'image'
}
},
scope: {
status: 'pending'
},
as: 'pendingTags',
foreignKey: 'taggableId',
constraints: false
});
其他主题
事务:
Sequelize 支持两种使用事务的方式:
- 非托管事务: 提交和回滚事务应由用户手动完成(通过调用适当的 Sequelize 方法).
- 托管事务: 如果引发任何错误,Sequelize 将自动回滚事务,否则将提交事务. 另外,如果启用了CLS(连续本地存储),则事务回调中的所有查询将自动接收事务对象.
非托管事务:
// 首先,我们开始一个事务并将其保存到变量中
const t = await sequelize.transaction();
try {
// 然后,我们进行一些调用以将此事务作为参数传递:
const user = await User.create({
firstName: 'Bart',
lastName: 'Simpson'
}, { transaction: t });
await user.addSibling({
firstName: 'Lisa',
lastName: 'Simpson'
}, { transaction: t });
// 如果执行到此行,且没有引发任何错误.
// 我们手动提交事务.
await t.commit();
} catch (error) {
// 如果执行到达此行,则抛出错误.
// 我们回滚事务.
await t.rollback();
}
托管事务:
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;
});
// 如果执行到此行,则表示事务已成功提交,`result`是事务返回的结果
// `result` 就是从事务回调中返回的结果(在这种情况下为 `user`)
} catch (error) {
// 如果执行到此,则发生错误.
// 该事务已由 Sequelize 自动回滚!
}
作用域:
不同于关联作用域, 作用域定义在模型中,帮助重用代码
作用域在模型定义中定义,可以是查找器对象,也可以是返回查找器对象的函数 - 默认作用域除外,该作用域只能是一个对象
class Project extends Model {}
Project.init({
// 属性
}, {
defaultScope: {//默认作用域
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(); //用法就是调用scope方法传入字符串,返回一个查询对象
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