Sequelize是一个基于promise的Node.js ORM,目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。
什么是ORM?
ORM(Oject-Relational Mapping对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射。这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
优势:
- 开发效率高,更容易维护
- 数据访问更抽象,轻便
- 支持面向对象封装
缺点:
- 降低程序的执行效率,消耗系统性能
- 多表查询等复杂语句语法更复杂
文章目录
基本使用
安装
npm install --save sequelize
npm install --sage mysql2
连接数据库
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize({
dislect: 'mysql', // 数据库类型
host: 'localhost', // 数据库地址
port: 3306, // 端口号
username: 'root', // 用户名
password: '123456', // 密码
database: 'test', // 数据库名称
})
更多参数配置可参考sequelize API参数
连接
async function test() {
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
} catch (error) {
console.error('Unable to connect to the database:', error);
}
}
test();
模型(model)
模型是 Sequelize 的本质. 模型是代表数据库中表的抽象. 在 Sequelize 中,它是一个 Model 的扩展类.
模型告诉 Sequelize 有关它代表的实体的几件事,例如数据库中表的名称以及它具有的列(及其数据类型).
模型定义
模型有两种定义方式:
1.调用sequelize.define(modelName, attributes, options)
2. 扩展Model并调用init(attributes, options)
两种效果基本等同,这里主要介绍第一种。
sequelize.define(modelName, attributes, options)
直接上例子,后面介绍详细参数:
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const User = sequelize.define('User', {
// 在这里定义模型属性
firstName: {
type: DataTypes.STRING,
allowNull: false
},
age: {
type: DataTypes.STRING,
defaultValue: 18
// allowNull 默认为 true
}
}, {
// 这是其他模型参数
});
modelName(表名): 主要是代码库表名,默认情况下会进行类型推断,自动将模型名复数并将其用作表名(User=>Users)。也可通过options设置freezeTableName: true来停止复数化,或通过options设置tableName: 'tableName’来直接指定表名。
sequelize.define('User', {
// ... (属性)
}, {
tableName: 'userTable' // 指定操作数据名为'userTable'
});
attributes(属性):
这里主要指定表的列字段名和类型等属性,Sequelize提供了内置的数据类型,可以通过导入DataTypes类来使用
更多使用直接参考model数据类型
配置(options):
modelName参数的时候以提起过,主要用于表相关配置。例如修改连接表的名字,使用需要模型默认添加的createAt和updateAt字段等。
const User = sequelize.define('User', {
// ... (属性)
}, {
// 启用时间戳(默认启用)
timestamps: true,
// 不想要 createdAt
createdAt: false,
// 想要 updatedAt 但是希望名称叫做 updateTimestamp
updatedAt: 'updateTimestamp'
});
模型同步
模型同步可以通过调用一个异步函数(返回一个Promise)model.sync(options). 通过此调用,Sequelize 将自动对数据库执行 SQL 查询. 请注意,这仅更改数据库中的表,而不更改 JavaScript 端的模型.
- User.sync() - 如果表不存在,则创建该表(如果已经存在,则不执行任何操作)
- User.sync({ force: true }) - 将创建表,如果表已经存在,则将其首先删除
- User.sync({ alter: true }) - 这将检查数据库中表的当前状态(它具有哪些列,它们的数据类型等),然后在表中进行必要的更改以使其与模型匹配.
sync和drop操作是破坏性的. Sequelize 使用 match 参数作为附加的安全检查,该检查将接受 RegExp
// 仅当数据库名称以 '_test' 结尾时,它才会运行.sync()
sequelize.sync({ force: true, match: /_test$/ });
模型实例操作
新增实例: user.create()
Model.create() 方法是使用 Model.build() 构建未保存实例并使用 instance.save() 保存实例的简写形式。
const jane = await User.create({ name: "Jane" });
// 在user表中新增一条name: jane数据
console.log(jane instanceof User); // true
console.log(jane.name); // "Jane"
console.log(jane.toJSON()); // Sequelize 实例具有很多附加条件,使用toJSON()更简便
更新实例: user.save()
const jane = await User.create({ name: "Jane", ages: 18 });
console.log(jane.name); // "Jane"
jane.name = "Ada";
jane.name = 20;
// 数据库中的名称仍然是 "Jane"
await jane.save();
// 现在该名称已在数据库中更新为 "Ada"!
如果仅保存部分修改,可以通过jane.save({ filelds: [‘name’] })
重载实例: user.reload()
const jane = await User.create({ name: "Jane" });
console.log(jane.name); // "Jane"
jane.name = "Ada";
// 数据库中的名称依然是 "Jane"
await jane.reload();
console.log(jane.name); // "Jane"
删除实例: user.destroy()
const jane = await User.create({ name: "Jane" });
console.log(jane.name); // "Jane"
jane.name = "Ada";
// 数据库中的名称依然是 "Jane"
await jane.reload();
console.log(jane.name); // "Jane"
递增和递减整数值: user.increment() / user.decrement()
为了避免并发问题,增减最好使用自带的方法。
await jane.increment(''age);
// age自增1
await jane.increment(['age', 'cash'], { by: 2 });
// age和cash都各自增加2
await jane.increment({
'age': 2,
'cash': 10
});
// age自增2, cash自增10
模型查询
简单查询
选择某些特定属性,可以使用 attributes 参数:
await.findAll();
// 查询所有用户
await.findAll({
attributes: ['foo', ['bar', 'baz'], 'que', [sequelize.fn('COUNT', sequelize.col('hats')), 'n_hats']]
})
// 查询foo、bar as baz、que、count(hats) as n_hats
await.findAll({
attributes: { exclude: ['baz'] }
})
// 查询所有排除某些字段和include(查询所有还包括)相反
条件查询
sequelize的Op封装了很多常用的条件判断方法。
const { Op } = require("sequelize");
...
Post.findAll({
where: {
name: 'binguo',
ages: {
[Op.qt]: 2
},
[Op.or]: [
{ authorId: 12 },
{ authorId: 13 }
]
}
});
查询name为binguo,ages大于2,authorId为12或13的所有数据
更多的用法如下
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']
[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 未延伸到左侧运算符)
}
}
});
高级查询:
类似获得where char_length(“content”) = 7,获取某字段名值的长度等于7
Post.findAll({
where: sequelize.where(sequelize.fn('char_length', sequelize.col('content')), 7)
});
// SELECT ... FROM "posts" AS "post" WHERE char_length("content") = 7
排序
User.findAll({
order: [
['title'],
[sequelize.fn('max', sequelize.col('age')), 'DESC']
]
})
按照title升序,最大年龄降序排序
order的规则:
- 一个字符串 (它将被自动引用)
- 一个数组, 其第一个元素将被引用,第二个将被逐字追加
- 一个具有 raw 字段的对象:
raw 内容将不加引用地逐字添加
其他所有内容都将被忽略,如果未设置 raw,查询将失败 - 调用 Sequelize.fn (这将在 SQL 中生成一个函数调用)
- 调用 Sequelize.col (这将引用列名)
分组
和排序语法类型
Project.findAll({ group: 'name' });
// 生成 'GROUP BY name'
限制和分页
使用 limit 和 offset 参数可以进行 限制/分页:
Project.findAll({ offset: 5, limit: 5 });
// 跳过5个实例,然后获取5个实例
实用方法:count/max/min/sum
await User.max('age'); // 40
await User.max('age', { where: { age: { [Op.lt]: 20 } } }); // 10
// 其他三个方法使用类似
查找器
findAll
查询将从表中检索所有条目
findByPK
findByPk 方法使用提供的主键从表中仅获得一个条目.
findOne
方法获得它找到的第一个条目(它可以满足提供的可选查询参数).
findOrCreate
除非找到一个满足查询参数的结果,否则方法 findOrCreate 将在表中创建一个条目. 在这两种情况下,它将返回一个实例(找到的实例或创建的实例)和一个布尔值,指示该实例是已创建还是已经存在.
const [user, created] = await User.findOrCreate({...});
// user返回找到或创建的实例,created是否创建
findAndCountAll
findAndCountAll 方法是结合了 findAll 和 count 的便捷方法. 在处理与分页有关的查询时非常有用,在分页中,你想检索带有 limit 和 offset 的数据,但又需要知道与查询匹配的记录总数.
返回两个对象count(符合条件记录的总数)和rows(获得记录的数组对象)
const { count, rows } = await User.findOrCreate({...});
//count - 一个整数 - 符合查询条件的记录总数
// rows - 一个数组对象 - 获得的记录
设置/获取器、虚拟字段
获取器get() / 设置器set()
有点类似于js中的拦截器,获取或设置的时候触发相应的方法。
在定义模型的时候添加get()和set()方法。
const User = sequelize.define('user', {
username: {
type: DataTypes.STRING,
get() {
const rawValue = this.getDataValue('username');
return rawValue ? rawValue.toUpperCase() : null;
}
set(value) {
this.setDataValue('username', value.toLowerCase());
}
}
});
上面的实现的就是输入数据库的是小写,拿出数据的数据是大写。结合get和set可以进行一些格式化或者是数据加密的操作。
虚拟字段
通过设置type:DataTypes.VIRTUAL将该字段设置为虚拟字段,不存在于数据库中(不可设置)。
作用:结合get可以实现便捷拿出复合信息,类似compute属性的效果。
const User = sequelize.define('user', {
firstName: DataTypes.TEXT,
lastName: DataTypes.TEXT,
fullName: {
type: DataTypes.VIRTUAL,
get() {
return `${this.firstName} ${this.lastName}`;
},
set(value) {
throw new Error('不要尝试设置 `fullName` 的值!');
}
}
});
验证&约束
验证是在纯 JavaScript 中在 Sequelize 级别执行的检查. 如果你提供自定义验证器功能,它们可能会非常复杂,也可能是 Sequelize 提供的内置验证器之一. 如果验证失败,则根本不会将 SQL 查询发送到数据库.
另一方面,约束是在 SQL 级别定义的规则. 约束的最基本示例是唯一约束. 如果约束检查失败,则数据库将引发错误,并且 Sequelize 会将错误转发给 JavaScript(在此示例中,抛出 SequelizeUniqueConstraintError). 请注意,在这种情况下,与验证不同,它执行了 SQL 查询.
约束
/* ... */ {
username: {
type: DataTypes.TEXT,
allowNull: false, // 非空校验
unique: true,// 唯一性约束
},
} /* ... */
校验
校验可以是用sequelize内置的校验器validator.js
/* ... */ {
ages: {
type: DataTypes.TEXT,
allowNull: false, // 非空校验
validate: {
len: [5, 10],
notNull: {
msg: '请输入年龄!'
}
}
},
size: {
type: DataTypes.STRING,
validate: {
isIn: [['L', 'XL']], //校验是否是L、XL码
}
}
} /* ... */
也可以自定义校验条件
name: {
type: DataTypes.STRING,
allowNull: true,
validate: {
customValidator(value) {
if (value === null && this.age !== 10) {
throw new Error("除非年龄为10,否则名称不能为 null");
}
}
}
}
原始查询
sequelize.query(‘sql语句’)
默认情况下,函数将返回两个参数 - 一个结果数组,以及一个包含元数据(例如受影响的行数等)的对象.
const [results, metadata] = await sequelize.query("UPDATE users SET y = 42 WHERE x = 12");
await sequelize.query("SELECT * FROM `users`", { type: QueryTypes.SELECT }); // 查询格式化
如果需要格式化,可以在后续传递一个查询类型参数{type: QueryType.SELECT}还可以通过传递模型实例来格式化{mode: xxx, mapToModel: true}
替换
- 如果传递一个数组, ? 将按照它们在数组中出现的顺序被替换
- 如果传递一个对象, :key 将替换为该对象的键. 如果对象包含在查询中找不到的键,则会抛出异常,反之亦然.
await sequelize.query("SELECT * FROM `users` where size = ? or size = ?",
// await sequelize.query("SELECT * FROM `users` where size = :key1 or size = key2",
{
replacements: ['foo', 'bar'],
// replacements: { key1: 'foo', key2: 'bar' },
type: QueryTypes.SELECT
});
// => SELECT * FROM `users` where size = 'foo' or size = 'bar'
注: 替换是整个value的替换,包括其’ '、[]、%等符号
绑定参数
绑定和替换类似,仅是绑定是用$number和$key标识, 替换是?和:key标识
- 如果传递一个数组, $1 被绑定到数组中的第一个元素 (bind[0]).
- 如果传递一个对象, $key 绑定到 object[‘key’]. 每个键必须以非数字字符开始. $1 不是一个有效的键,即使 object[‘1’] 存在.
- 在这两种情况下 $$ 可以用来转义一个 $ 字符符号.
偏执表
偏执表会执行软删除,一个 paranoid 表是一个被告知删除记录时不会真正删除它的表.反而一个名为 deletedAt 的特殊列会将其值设置为该删除请求的时间戳。
定义
定义模型的时候设置options: paranoid: true
注: Paranoid 需要时间戳才能起作用(即,如果你传递 timestamps: false 了,paranoid 将不起作用)。自定义列名可通过deleteAt设置。
sequelize.define('User', {
// ... (属性)
}, {
tparanoid: true,
// 如果要为 deletedAt 列指定自定义名称
deletedAt: 'destroyTime'
});
这样你在调用destroy方法删除的时候仅执行软删除,可以通过restore方法恢复。
// 展示实例 `restore` 方法的示例
// 我们创建一个帖子,对其进行软删除,然后将其还原
const post = await Post.create({ title: 'test' });
console.log(post instanceof Post); // true
await post.destroy();
console.log('soft-deleted!');
await post.restore();
console.log('restored!');
注:如果想要永久删除的可以设置force为true即可 await post.destroy({ force: true });