经验分享 - Node ORM 框架 Sequelize 使用心得分享
一、数据库基础知识
1.1 什么是 ORM?
对象-关系映射(Object-Relational Mapping,简称 ORM),ORM 框架主要的作用就是把数据库中的关系数据映射称为程序中的对象,也就是说你操作数据库不用直接写 SQL,而是直接操作对象就可以。
Java 中主流的 ORM 框架有:Hibernate、Mybatis、iBatis 等。
Node 中主流的 ORM 框架有:Sequelize、TypeORM、LoopBack、Mongoose、Waterline 等。
1.2 关系数据库都有哪些关系?
两个对象 A 和 B 之间只有哪些可能存在的关系?
1:1
1 个 A 对应 1 个 B(一夫一妻)。有 2 张表,A 表中有一个外键关联 B 表;1:N
1 个 A 对应多个 B(一夫多妻)。有 2 张表,B 表中有一个外键关联 A 表;M:N
1 个 A 对应多个 B,一个 B 也可能对应多个 A(多夫多妻)。有 3 张表,C 表中有 2 个外键,分别关联 A 表、B 表;
1.3 关系数据库都有哪些查询?
单表查询
select * from t1;
关联查询
/* * 1、内连接查询 * inner join * 取 2 个表的交集 */ select * from t1 inner join t2 on t1.lid = t2.lid /* * 2、左外连接查询 * left outer join 或简写 left join * 以左表数据量为基准,右表未匹配的字段,用 NULL 代替 */ select * from t1 left join t2 on t1.lid = t2.lid /* * 3、右外连接联查询 * right outer join 或简写 right join * 已右表数据量为基准,左表未匹配的字段,用 NULL 代替 */ select * from t1 right join t2 on t1.lid = t2.lid /* * 4、全外连接查询 * full outer join 或简写 full join * 已右表数据量为基准,左表未匹配的字段,用 NULL 代替 * 注意:Mysql 不支持,使用 uion 代替 */ select * from t1 right join t2 on t1.lid = t2.lid /* * 5、交叉查询 * cross join * N x M 的结果返回 */ select * from t1 cross join t2
结果集操作
/* * 1、合并 * union * 对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序 */ select t1.id,t1.name from t1 union select t2.id,t1.name from t2 /* * 2、合并全部 * union all * 对两个结果集进行并集操作,包括重复行,不进行排序 */ select t1.id,t1.name from t1 union all select t2.id,t1.name from t2 /* * 3、求交集 * intersect * 对两个结果集进行交集操作,不包括重复行,同时进行默认规则的排序 * 注意:Mysql 不支持 */ select t1.id,t1.name from t1 intersect select t2.id,t1.name from t2 /* * 4、求差集 * minus * 对两个结果集进行差操作,不包括重复行,同时进行默认规则的排序 * 注意:Mysql 不支持 */ select t1.id,t1.name from t1 minus select t2.id,t1.name from t2
二、Sequelize 使用心得
2.1 Sequelize 的 ORM 特性
- Sequelize 可以通过 Model 创建数据库、表和字段(对象 -> 库)
- Sequelize 也可以将数据库表通过 CLI 工具,导出生成 Model 对象 (库 -> 对象)
考虑到对代码的侵入性和以后的扩展性,我在使用 Sequelize 的时候,仅用了它的(库 -> 对象)这一层。
2.2 Sequelize 的高级特性
- 支持分库分表、读写分离的配置
- 支持事务、回滚机制
- 支持字段校验,以及自己写校验规则
- 支持各种钩子(Hooks),如:
beforeCreate
afterCreate
等,可以作为全局拦截处理 - 支持作用域(Scopes),以规定全局默认的查询范围,如:所有接口默认携带当前 userId 作为查询参数
- 支持各种关系查询,如:
1:1
、1:N
、1:M
等
2.3 Sequelize 脚本片段
安装
// 使用 NPM $ npm install --save sequelize # 还有以下之一: $ npm install --save pg pg-hstore $ npm install --save mysql2 $ npm install --save sqlite3 $ npm install --save tedious // MSSQL // 使用 Yarn $ yarn add sequelize # 还有以下之一: $ yarn add pg pg-hstore $ yarn add mysql2 $ yarn add sqlite3 $ yarn add tedious // MSSQL
建立连接
const sequelize = new Sequelize('database', 'username', 'password', { host: 'localhost', dialect: 'mysql', pool: { max: 5, min: 0, idle: 10000 }, define:{ timestamps: false //数据库表中可以没有 createdAt、updatedAt 这 2 个字段。 } }); // 或者你可以简单地使用 uri 连接 const sequelize = new Sequelize('mysql://user:pass@example.com:5432/dbname');
测试连接
sequelize .authenticate() .then(() => { console.log('恭喜,数据库已连接!'); }) .catch(err => { console.error('糟糕,数据库连接失败!', err); });
Model 生成表
const User = sequelize.define('user', { firstName: { type: Sequelize.STRING }, lastName: { type: Sequelize.STRING } }); // force: true 如果表已经存在,将会丢弃表 User.sync({force: true}).then(() => { // 表已创建 });
表生成 Model
// 使用 NPM 安装 cli 工具 $ npm install --save mysql2 $ npm install --save sequelize-auto //使用 cli 工具将表结构生成 Model 文件到指定目录下 $ sequelize-auto -h localhost -u username -x password -p 3306 -e mysql -d database -o "dirpath" -t "table" -C
基本的增删改查(CRUD)
//增 User.create({ userName: '张三', userCreateTime : new Date() }).then((user) => { console.log(user); }) //删 User.destory({ where : { userId : 1 } }).then((result) => { console.log(result); }) //改 User.update({ userName : '李四' },{ where : { userId : 1 } }).then((result) => { console.log(result); }) //查全部 User.findAll().then((users) => { console.log(users) }) //查详情 let userId = 1; User.findById(userId).then((user) => { console.log(user) }) //分页查询 User.findAndCountAll({ limit : 10, offset : 0 }).then((result) => { //result 包含:count 和 rows console.log(result) }) //原始查询 sequelize.query("SELECT * FROM `users`", {type: sequelize.QueryTypes.SELECT}) .then(function(users) { console.log(users) })
where 条件
//条件 [Op.or] User.findAll({ where : { userId : 1, [Op.or] : [ {userId: 12}, {userId: 13} ] } }).then((result) => { console.log(result) }) //简写 $or User.findAll({ where : { userId : 1, $or : { userName : { $like : '%张%' }, userNickName : { $like : '%张%' } } } }).then((result) => { console.log(result) })
更多 where
关键词
const Op = Sequelize.Op [Op.and]: {a: 5} // 且 (a = 5) [Op.or]: [{a: 5}, {a: 6}] // (a = 5 或 a = 6) [Op.gt]: 6, // id > 6 [Op.gte]: 6, // id >= 6 [Op.lt]: 10, // id < 10 [Op.lte]: 10, // id <= 10 [Op.ne]: 20, // id != 20 [Op.eq]: 3, // = 3 [Op.not]: true, // 不是 TRUE [Op.between]: [6, 10], // 在 6 和 10 之间 [Op.notBetween]: [11, 15], // 不在 11 和 15 之间 [Op.in]: [1, 2], // 在 [1, 2] 之中 [Op.notIn]: [1, 2], // 不在 [1, 2] 之中 [Op.like]: '%hat', // 包含 '%hat' [Op.notLike]: '%hat' // 不包含 '%hat' [Op.iLike]: '%hat' // 包含 '%hat' (不区分大小写) (仅限 PG) [Op.notILike]: '%hat' // 不包含 '%hat' (仅限 PG) [Op.regexp]: '^[h|a|t]' // 匹配正则表达式/~ '^[h|a|t]' (仅限 MySQL/PG) [Op.notRegexp]: '^[h|a|t]' // 不匹配正则表达式/!~ '^[h|a|t]' (仅限 MySQL/PG) [Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' (仅限 PG) [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (仅限 PG) [Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何数组['cat', 'hat'] - 同样适用于 iLike 和 notLike [Op.overlap]: [1, 2] // && [1, 2] (PG 数组重叠运算符) [Op.contains]: [1, 2] // @> [1, 2] (PG 数组包含运算符) [Op.contained]: [1, 2] // <@ [1, 2] (PG 数组包含于运算符) [Op.any]: [2,3] // 任何数组[2, 3]::INTEGER (仅限 PG) [Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用数据库语言特定的列标识符, 本例使用 PG // 所有上述相等和不相等的操作符加上以下内容: [Op.contains]: 2 // @> '2'::integer (PG range contains element operator) [Op.contains]: [1, 2] // @> [1, 2) (PG range contains range operator) [Op.contained]: [1, 2] // <@ [1, 2) (PG range is contained by operator) [Op.overlap]: [1, 2] // && [1, 2) (PG range overlap (have points in common) operator) [Op.adjacent]: [1, 2] // -|- [1, 2) (PG range is adjacent to operator) [Op.strictLeft]: [1, 2] // << [1, 2) (PG range strictly left of operator) [Op.strictRight]: [1, 2] // >> [1, 2) (PG range strictly right of operator) [Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator) [Op.noExtendLeft]: [1, 2] // &> [1, 2) (PG range does not extend to the left of operator)
order 排序
//按 userUpdateTime 和 userCreateTime 倒序 User.findAll({ order : [['userUpdateTime','DESC'],['userCreateTime','DESC']] }).then((result) => { console.log(result) })
事务
什么时候用到事务?比如:你删除一个 Link,连同 Link 的所有记录一起删掉。删除 Link 和删除 LinkLog,这 2 步要不同时成功,要不同时失败,不存在一个删除,一个保留的情况,这时候就得用到事务。
/** * 不使用事务的写法 * 这种写法的问题:可能删除了 Link,但是由于数据库异常,导致 LinkLog 删除失败。 */ //1. 先删除 Link Link.destory({ where : { linkId : linkId } }).then(() => { //2. 再删除 LinkLog LinkLog.destory({ where : { linkLogLinkId : linkId } }).then((result) => { console.log(result); }) }) /** * 使用事务包裹 */ sequelize.transaction((t) => { //1. 先删除 Link Link.destory({ where : { linkId : linkId }, transaction: t //关联事务 }).then(() => { //2. 再删除 LinkLog LinkLog.destory({ where : { linkLogLinkId : linkId }, transaction: t //关联事务 }).then((result) => { console.log(result); }) }) });
关联关系 belongsTo 1:1
//belongsTo 1:1 //比如:1 个链接对应唯一的 1 个用户 //在 Link 这一侧建立关系 //1. 需要在 links 表中拥有字段 link_user_id,作为外键关联 users 表 //2. 在 Link 模型中建立关系 Link.belongsTo(user,{ as : 'linkUser', foreignKey: 'linkUserId', targetKey:'userId' }); //3. 关联查询当前链接对应的用户信息 linkUser Link.findById(linkId,{ include:[{ model: user, as : 'linkUser', //与上面关系中的 as 名称相同 required: false, //使用 inner 关联,还是 outer 关联 where : {//可以添加关联条件 userStatus : 0 }, attributes: ['userId','userName']//关联的对象只会查询这 2 个字段 }] }) //4.返回结果 { linkId : 12, linkName : '测试链接', linkCreateTime : '2018-11-04 11:22:55', linkUser : { userId : 100, userName : '张三' } }
关联关系 hasMany 1:N
//hasMany 1:N //比如:1 个链接对应多个链接记录 //在 Link 这一侧建立关系 //1. 需要在 link_logs 表中拥有字段 link_log_link_id,作为外键关联 links 表 //2. 在 Link 模型中建立关系 Link.hasMany(linkLog,{ as : 'linkLogList', foreignKey: 'linkLogLinkId' }); //3. 关联查询当前链接对应的所有记录数据 linkLogList Link.findById(linkId,{ include:[{ model: linkLog, as : 'linkLogList', required: false }] }) //4.返回结果 { linkId : 12, linkName : '测试链接', linkCreateTime : '2018-11-04 11:22:55', linkLogList : [{ linkLogId : 1, linkLogName : '记录 1', ...//其它字段 }] }
关联关系 belongsToMany M:N
//belongsToMany M:N //比如:1 个链接对应多个链接规则,1 个链接规则对应多个链接 //在 Link 这一侧建立关系 //1. 需要有 3 张表,links、rules 和 link_rule_refs,在 link_rule_refs 中需要有 2 个字段,1 个字段 link_id 作为外键和 links 表关联,另 1 个字段 rule_id 作为外键和 rules 表关联 //2. 在 Link 模型中建立关系 Link.belongsToMany(rule,{ as : 'linkRuleList', through: ruleLink,//ruleLink 就是第 3 张表的 Model foreignKey: 'linkId', otherKey: 'ruleId' }); //3. 关联查询当前链接对应的所有记录数据 linkRuleList Link.findById(linkId,{ include:[{ model: this.rule, as : 'linkRuleList', required: false, attributes: ['ruleId','ruleName'],//rule 表的字段 through: {//关联表 attributes: []//可以指定关联表中的返回字段 } }] }) //4.返回结果 { linkId : 12, linkName : '测试链接', linkCreateTime : '2018-11-04 11:22:55', linkLogList : [{ linkLogId : 1, linkLogName : '记录 1', ...//其它字段 }] }
三、文档相关
3.1 API 相关文档
3.2 Model 所有查询 API
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论