sequelize
目前有许许多多的ORM,但是目前最为流行的依然是sequelize,所以这里总结写之前自己写自己的博客所涉及到的点,分享给大家,让大家也可以少踩坑,更快入门。
sequelize-cli的基本流程
- sequelize中规定 模型的名称是单数、表的名称是复数
- 总置文件就是用来给数据库mock添加数据的文件 生成文章表模型
sequelize model:generate --name Article --attributes title:string,desc:string,content:text,coverImg:string,status:bigint
生成一个模型名字叫Article
的模型,有title、desc、content、coverImg、status五个字段,sequelize
会自动为每张表添加id
、createdAt
、updetedAt
字段。
执行命令创建数据库 运行迁移
代码语言:javascript复制sequelize db:migrate
这个时候就会通过mysql发现已经建表成功了,并且拥有了这些字段,接下来就是本地如果需要模拟添加数据,需要运行总置文件
新建一个总置文件
代码语言:javascript复制sequelize seed:generate --name article
![image-20200819111548140](/Users/xiaojiu/Library/Application Support/typora-user-images/image-20200819111548140.png)
然后就可以在这里面进行mock自己插入值了,替换掉pepple为表名,替换后面的数组对象为自己需要插入的对象即可
运行迁移
代码语言:javascript复制sequelize db:seed:all //只有一个文件这样 多个的时候要加文件名 不然就全部文件都执行了 db:seed --seed 文件名字
这样就添加了数据,刷新数据库已经可以看到数据了
生成comment评论模型
代码语言:javascript复制sequelize model:generate --name Comment --attributes articleId:integer,content:text
运行迁移命令
代码语言:javascript复制sequelize db:migrate
运行这个命令生成总置文件
代码语言:javascript复制sequelize seed:generate --name comment //生成一个comment的总置文件
有了就可以在seeders文件夹下打开添加数据了
数据模型关联关系
代码语言:javascript复制A.hasOne(B); // A 有一个 B
A.belongsTo(B); // A 属于 B
A.hasMany(B); // A 有多个 B
A.belongsToMany(B, { through: 'C' }); // A 属于多个 B , 通过联结表 C
多种关系在model
模型中定义 通过associate
module.exports = (sequelize, DataTypes) => {
class Article extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// 在此处定义关联 表示文章下面有很多评论
models.Article.hasMany(models.Comment)
models.Article.belongsTo(models.Type)
}
};
Article.init({
title: DataTypes.STRING,
desc: DataTypes.STRING,
content: DataTypes.TEXT,
coverImg: DataTypes.STRING,
status: DataTypes.BIGINT,
typeId: DataTypes.BIGINT,
}, {
sequelize,
modelName: 'Article',
});
return Article;
};
可以添加多个关系,在使用的时候查询需要使用include
例如
router.get('/detail/:id', async (req, res) => {
var article = await models.Article.findOne({
where:{id:req.params.id},
include: [models.Comment,{
model:models.Type,
attributes: ['className'] //限制只取某某字段 exclude排除
//attributes: {exclude:['id']} 排除某个字段
}]
}) //findByPk查找主键id
res.json({data:article})
})
include
是一个数组,可以关联多个模型,也就是多张表的意思,里面的每一项为对象,attribute代表限制只需要目标模型的这几个字段即可,如果不限制,拿到的就是所有值。
查询方法
一般我们在使用*sequelize的方法查询时,一般语法是这样的:
代码语言:javascript复制/** 数据库模型.方法名(各种查询条件) */
User.findOne({where:{username:req.body.username}})
例如上方分为三部分,User为自己定义的数据库模型,其实也就代表用这个模型间接的操作数据库,findOne是查询方法,这里提供了多种方法,后面一一解释,在后面需要给这个方法传递的参数有很多,就是这种搜索的显示条件,where同sql语句里面的where一个意思。
- findOne ===== 查询一条
User.findOne(
{
where: { username: '小九' }
}
)
上面表示用User模型查询一条数据,条件是username字段是小九,这就是查询单条数据
- findAll ===== 查询所有
let result = await Type.findAll()
上面表示通过Type模型查询里面所有的数据,里面可以加条件,和上面一样。
- findByPk ===== 通过主键id查找
let article = await Article.findByPk(99)
上面表示通过Article模型查询主键id为99的这个数据,和查询单条数据区别不大,只是指定了查询字段为id
- create ===== 创建一条数据
let user = await models.User.create(req.body)
上面是创建新增一条数据,拿到前端传来的数据存到数据库,其实就是新增一个用户的意思
- destory ===== 删除数据
article.destroy( where: { username: '小九' })
上面表示删除username为小九的一条数据。根据条件删除。
- Update ==== 修改数据
let article = await models.Article.update(req.body, { where: { name:'小九' } })
上面表述查询到name为小九的这个用户,修改他的名字为前端传来的数据req.body这个对象。
- bulkCreate ==== 批量创建
const user = await User.bulkCreate([ { name: 'Jack Sparrow' }, { name: 'Davy Jones' } ]);
上面表示一次创建多条数据,这样的创建也会带来副作用,例如:性能低、不能对每一条数据都进行验证、
进阶查询
上面就是普通的增删查改,但是实际业务远比这些复杂,学会了上面的就来试试下面的各种业务场景吧。
- findAndCountAll ==== 分页 模糊查询
router.post('/AllArticle', async function (req, res, next) {
let currentPage = parseInt(req.body.currentPage) || 1
let pageSize = parseInt(req.body.pageSize) || 5
let where = {};
if (req.body.typeId) {
where.typeId = req.body.typeId
}
if(req.body.status){
where.status = req.body.status
}
let keyword = req.body.keyword
if (keyword) {
where.keyword = {
[Op.like]: '%' keyword '%'
}
}
let result = await models.Article.findAndCountAll({
order: [['id', 'DESC']], //倒叙的方式输出 对比id 默认为ASC正序
where, //模糊查询的条件
offset: (currentPage - 1) * pageSize,
limit: pageSize,
include: [models.User, models.Type]
})
const data = {
articles: result.rows,
pagination: {
currentPage,
pageSize,
total: result.count
}
}
new Result(data, '获取成功').success(res)
});
上面是我博客的一段真实场景代码,其作用是第一可以分页,第二倒序返回数据(这样可以后发表的博客显示在最前面),第三点可以支持模糊搜索,我们知道,前端一般采用分页,就需要总数,一页多少条,当前在第几页,这几个参数,所以这个方法会直接给你返回一个前端可以做分页的分页格式,那么我们看看查询条件,order为排序,上图表示以id为排序返回,正序倒序可以自己设定,根据场景而来,where是查询条件,模糊搜索需要满足一个条件,那么上面的语法表示,用户属于的这个关键词在我们的数据里面出现就会返回这条数据,这里的模糊搜索建议参考官网文档,场景不同模糊搜索的需求也就不一样了。其他三个字段就不用说了,前端传入的当前页,一页多少条,和总数,自行分析。
- findOrCreate ==== 查询并新增
let res = await User.findOrCreate({where:{name:'小九'}})
上面表示查询username为小九的数据,如果没有就新增一条数据。在实际场景中,我们经常新增数据之前首先需要确认这个用户是否已经新增过了,所以这个方法就适用于这种场景
- findAndDelete ==== 查询并删除
let res = await User.findAndDelete({where:{name:'小九'}})
和上面的一样,查询这个用户是否存在,存在再进行删除,防止出现删除的用户在数据库中并不存在的这种操作,多人操作的情况下可能会出现这种情况,所以可以使用这个方法。
查询条件
上面我们已经知道了基本的查询语法,但是实际业务中的查询可能更为麻烦,我们看看在sequelize中还提供了哪些参数吧:
代码语言:javascript复制something.findOne({
order: [
// 转义 username 并对查询结果按 DESC 方向排序
['username', 'DESC'],
// 按 max(age) 排序
sequelize.fn('max', sequelize.col('age')),
// 按 max(age) DESC 排序
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// 按 otherfunction(`col1`, 12, 'lalala') DESC 排序
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// 按相关联的User 模型的 name 属性排序
[User, 'name', 'DESC'],
// 按相关联的User 模型的 name 属性排序并将模型起别名为 Friend
[{model: User, as: 'Friend'}, 'name', 'DESC'],
// 按相关联的User 模型的嵌套关联的 Company 模型的 name 属性排序
[User, Company, 'name', 'DESC'],
]
// 以下所有声明方式都会视为字面量,应该小心使用
order: 'convert(user_name using gbk)'
order: 'username DESC'
order: sequelize.literal('convert(user_name using gbk)')
})
有时候我们查询出来的数据有很多,但是前端却不需要这么多数据,包括有的数据也不想暴露出去,那么我们如何对数据进行过滤呢?
代码语言:javascript复制router.get('/getDetail/:id', async (req, res) => {
var article = await models.Article.findOne({
where: { id: req.params.id },
attributes: ['title', 'desc', 'status', 'userId', 'typeId', 'coverImg', 'content'],
include: [models.Comment, {
model: models.Type,
// attributes: ['className'] //限制只取某某字段
}]
})
new Result(article, '获取成功').success(res)
})
如上:使用attributes,在里面写入你需要返回的字段即可,其他字段就可以过滤掉了。
有时候我们需要多表联合查询,假设这样的场景,对于我的博客,会出现,一个分类下面有多篇文章,我需要查出这个分类和当前分类下的所有文章应该如何做呢?
代码语言:javascript复制router.get('/getTypeDetail/:id', async (req, res) => {
var types = await models.Type.findOne({
where:{id:req.params.id},
include: [models.Article]
}) //findByPk查找主键id
res.json({data:types})
})
使用include联合查询,后面表示需要一起查询的model,这里在定义的时候需要对齐关联,例如models.Type.**hasMany(models.Article)
在定义model模型的时候进行关联,这句表示type模型的下面有很多的文章模型,翻译成业务就是,分类下面可以包含很多文章
常用操作符
代码语言:javascript复制const { Op } = require("sequelize");
Post.findAll({
where: {
[Op.and]: [{ a: 5 }, { b: 6 }], // (a = 5) AND (b = 6)
[Op.or]: [{ a: 5 }, { b: 6 }], // (a = 5) OR (b = 6)
someAttribute: {
// 基本
[Op.eq]: 3, // = 3
[Op.ne]: 20, // != 20
[Op.is]: null, // IS NULL
[Op.not]: true, // IS NOT TRUE
[Op.or]: [5, 6], // (someAttribute = 5) OR (someAttribute = 6)
// 使用方言特定的列标识符 (以下示例中使用 PG):
[Op.col]: 'user.organization_id', // = "user"."organization_id"
// 数字比较
[Op.gt]: 6, // > 6
[Op.gte]: 6, // >= 6
[Op.lt]: 10, // < 10
[Op.lte]: 10, // <= 10
[Op.between]: [6, 10], // BETWEEN 6 AND 10
[Op.notBetween]: [11, 15], // NOT BETWEEN 11 AND 15
// 其它操作符
[Op.all]: sequelize.literal('SELECT 1'), // > ALL (SELECT 1)
[Op.in]: [1, 2], // IN [1, 2]
[Op.notIn]: [1, 2], // NOT IN [1, 2]
[Op.like]: '%hat', // LIKE '%hat'
[Op.notLike]: '%hat', // NOT LIKE '%hat'
[Op.startsWith]: 'hat', // LIKE 'hat%'
[Op.endsWith]: 'hat', // LIKE '%hat'
[Op.substring]: 'hat', // LIKE '%hat%'
[Op.iLike]: '%hat', // ILIKE '%hat' (不区分大小写) (仅 PG)
[Op.notILike]: '%hat', // NOT ILIKE '%hat' (仅 PG)
[Op.regexp]: '^[h|a|t]', // REGEXP/~ '^[h|a|t]' (仅 MySQL/PG)
[Op.notRegexp]: '^[h|a|t]', // NOT REGEXP/!~ '^[h|a|t]' (仅 MySQL/PG)
[Op.iRegexp]: '^[h|a|t]', // ~* '^[h|a|t]' (仅 PG)
[Op.notIRegexp]: '^[h|a|t]', // !~* '^[h|a|t]' (仅 PG)
[Op.any]: [2, 3], // ANY ARRAY[2, 3]::INTEGER (仅 PG)
[Op.match]: Sequelize.fn('to_tsquery', 'fat & rat') // 匹配文本搜索字符串 'fat' 和 'rat' (仅 PG)
// 在 Postgres 中, Op.like/Op.iLike/Op.notLike 可以结合 Op.any 使用:
[Op.like]: { [Op.any]: ['cat', 'hat'] } // LIKE ANY ARRAY['cat', 'hat']
// 还有更多的仅限 postgres 的范围运算符,请参见下文
}
}
});
运算符的逻辑组合
代码语言:javascript复制const { Op } = require("sequelize");
Foo.findAll({
where: {
rank: {
[Op.or]: {
[Op.lt]: 1000,
[Op.eq]: null
}
},
// rank < 1000 OR rank IS NULL
{
createdAt: {
[Op.lt]: new Date(),
[Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000)
}
},
// createdAt < [timestamp] AND createdAt > [timestamp]
{
[Op.or]: [
{
title: {
[Op.like]: 'Boat%'
}
},
{
description: {
[Op.like]: '%boat%'
}
}
]
}
// title LIKE 'Boat%' OR description LIKE '%boat%'
}
});
范围运算符
代码语言:javascript复制[Op.contains]: 2, // @> '2'::integer (PG range 包含元素运算符)
[Op.contains]: [1, 2], // @> [1, 2) (PG range 包含范围运算符)
[Op.contained]: [1, 2], // <@ [1, 2) (PG range 包含于运算符)
[Op.overlap]: [1, 2], // && [1, 2) (PG range 重叠(有共同点)运算符)
[Op.adjacent]: [1, 2], // -|- [1, 2) (PG range 相邻运算符)
[Op.strictLeft]: [1, 2], // << [1, 2) (PG range 左严格运算符)
[Op.strictRight]: [1, 2], // >> [1, 2) (PG range 右严格运算符)
[Op.noExtendRight]: [1, 2], // &< [1, 2) (PG range 未延伸到右侧运算符)
[Op.noExtendLeft]: [1, 2], // &> [1, 2) (PG range 未延伸到左侧运算符)
排序和分组
Sequelize 提供了 order and group 参数,来与 ORDER BY 和 GROUP BY 一起使用.
代码语言:javascript复制Subtask.findAll({
order: [
// 将转义 title 并针对有效方向列表进行降序排列
['title', 'DESC'],
// 将按最大年龄进行升序排序
sequelize.fn('max', sequelize.col('age')),
// 将按最大年龄进行降序排序
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// 将按 otherfunction(`col1`, 12, 'lalala') 进行降序排序
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// 将使用模型名称作为关联名称按关联模型的 createdAt 排序.
[Task, 'createdAt', 'DESC'],
// 将使用模型名称作为关联名称通过关联模型的 createdAt 排序.
[Task, Project, 'createdAt', 'DESC'],
// 将使用关联名称按关联模型的 createdAt 排序.
['Task', 'createdAt', 'DESC'],
// 将使用关联的名称按嵌套的关联模型的 createdAt 排序.
['Task', 'Project', 'createdAt', 'DESC'],
// 将使用关联对象按关联模型的 createdAt 排序. (首选方法)
[Subtask.associations.Task, 'createdAt', 'DESC'],
// 将使用关联对象按嵌套关联模型的 createdAt 排序. (首选方法)
[Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'],
// 将使用简单的关联对象按关联模型的 createdAt 排序.
[{model: Task, as: 'Task'}, 'createdAt', 'DESC'],
// 将由嵌套关联模型的 createdAt 简单关联对象排序.
[{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC']
],
// 将按最大年龄降序排列
order: sequelize.literal('max(age) DESC'),
// 如果忽略方向,则默认升序,将按最大年龄升序排序
order: sequelize.fn('max', sequelize.col('age')),
// 如果省略方向,则默认升序, 将按年龄升序排列
order: sequelize.col('age'),
// 将根据方言随机排序(但不是 fn('RAND') 或 fn('RANDOM'))
order: sequelize.random()
});
Foo.findOne({
order: [
// 将返回 `name`
['name'],
// 将返回 `username` DESC
['username', 'DESC'],
// 将返回 max(`age`)
sequelize.fn('max', sequelize.col('age')),
// 将返回 max(`age`) DESC
[sequelize.fn('max', sequelize.col('age')), 'DESC'],
// 将返回 otherfunction(`col1`, 12, 'lalala') DESC
[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
// 将返回 otherfunction(awesomefunction(`col`)) DESC, 这种嵌套可能是无限的!
[sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC']
]
});
三个常用快捷方法
代码语言:javascript复制await User.max('age'); // 40
await User.max('age', { where: { age: { [Op.lt]: 20 } } }); // 10
await User.min('age'); // 5
await User.min('age', { where: { age: { [Op.gt]: 5 } } }); // 10
await User.sum('age'); // 55
await User.sum('age', { where: { age: { [Op.gt]: 5 } } }); // 50