内容简介:数据模型中的表关系一般有三种:一对一、一对多、多对多。我们首先从一个基本概念开始,你将会在大多数关联中使用
Sequelize 是一个基于 Promise 的 Node.js ORM,目前支持 Postgres、 MySQL 、 SQLite 和 Microsoft SQL Server。它具有强大的事务支持,关联关系、读取和复制等功能。在阅读本文前,如果你对 Sequelize 还不了解,建议先阅读Sequelize 快速入门 这篇文章。
数据模型中的表关系一般有三种:一对一、一对多、多对多。 Sequelize 为开发者提供了清晰易用的接口来定义关系、进行表之间的操作。本文我们将介绍在 Sequelize 中如何定义一对多的表关系。
基本概念
Source & Target
我们首先从一个基本概念开始,你将会在大多数关联中使用 source
和 target
模型。 假设您正试图在两个模型之间添加关联。 这里我们在 User
和 Project
之间添加一个 hasOne
关联。
const User = sequelize.define('User', { name: Sequelize.STRING, email: Sequelize.STRING }); const Project = sequelize.define('Project', { name: Sequelize.STRING }); User.hasOne(Project);
User
模型(函数被调用的模型)是 source
。 Project
模型(作为参数传递的模型)是 target
。
HasMany
一对多关联将一个来源与多个目标连接起来。 而多个目标接到同一个特定的源。
const User = sequelize.define('user', {/* ... */}) const Project = sequelize.define('project', {/* ... */}) // 首先我们来定义一个 hasMany 关联 Project.hasMany(User, {as: 'Workers'})
这会将 projectId
属性添加到 User。 根据当前的设置,表中的列将被称为 projectId
或 project_id
。 Project 的实例将获得访问器 getWorkers
和 setWorkers
。
有时你可能需要在不同的列上关联记录,这时候你可以使用 sourceKey
选项:
const City = sequelize.define('city', { countryCode: Sequelize.STRING }); const Country = sequelize.define('country', { isoCode: Sequelize.STRING }); // 在这里,我们可以根据国家代码连接国家和城市 Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'}); City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'});
一对多关系
模型定义
model/user.js
const Sequelize = require("sequelize"); module.exports = sequelize => { const User = sequelize.define("user", { empId: { type: Sequelize.STRING, allowNull: false, unique: true } }); return User; };
model/note.js
const Sequelize = require("sequelize"); module.exports = sequelize => { const Note = sequelize.define("note", { title: { type: Sequelize.CHAR(64), allowNull: false } }); return Note; };
数据库连接及关系定义
db.js
const Sequelize = require('sequelize'); const sequelize = new Sequelize( 'exe', // 数据库名称 'root', // 用户名 '', // 密码 { host: 'localhost', dialect: 'mysql', operatorsAliases: false, pool: { max: 5, min: 0, acquire: 30000, idle: 10000 } }); sequelize .authenticate() .then(async () => { console.log('Connection has been established successfully.'); const User = require('./model/user')(sequelize); const Note = require('./model/note')(sequelize); // User的实例对象将拥有:getNotes、setNotes、addNote、createNote、 // removeNote、hasNote方法 User.hasMany(Note); // Note的实例对象将拥有getUser、setUser、createUser方法 Note.belongsTo(User); sequelize.sync({ // force: true }) .then(async () => { console.log(`Database & tables created!`); }) }) .catch(err => { console.error('Unable to connect to the database:', err); });
以上代码运行后,终端将会输出以下信息:
- 新建 users 表:
CREATE TABLE IF NOT EXISTS `users` ( `id` INTEGER NOT NULL auto_increment , `empId` VARCHAR(255) NOT NULL UNIQUE, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
- 新建 notes 表:
CREATE TABLE IF NOT EXISTS `notes` ( `id` INTEGER NOT NULL auto_increment , `title` CHAR(64) NOT NULL, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL, `userId` INTEGER, PRIMARY KEY (`id`), FOREIGN KEY (`userId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE) ENGINE=InnoDB;
通过观察上面的 notes 建表语句,我们发现 Sequelize 自动为 notes 表新增了 userId 字段,同时生成了相应的外键约束。
一般来说,外键约束可能会导致一些性能问题。所以,建表时我们一般会去掉约束,同时给外键加一个索引(加速查询),但之后的数据的一致性就需要应用层来保证了。
关系操作
- 新增
方式一
const user = await User.create({ empId: '1' }); // (1) const note = await user.createNote({ title: 'learn sequelize' }); // (2)
步骤一:新建用户,对应的 SQL 语句如下:
INSERT INTO `users` (`id`,`empId`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'1','2018-10-10 07:42:26','2018-10-10 07:42:26');
步骤二:创建 Note,对应的 SQL 语句如下:
INSERT INTO `notes` (`id`,`title`,`createdAt`,`updatedAt`,`userId`) VALUES (DEFAULT,'learn sequelize','2018-10-10 07:42:26','2018-10-10 07:42:26',1);
可以看出,当调用 user.createNote 方法时,会使用新建用户的 userId 作为外键在 notes 表中插入一条新的数据。
方式二
const user = await User.create({ empId: '1' }); // (1) const note = await Note.create({ title: 'learn sequelize' }); // (2) await user.addNote(note); // (3)
步骤一:新建用户,对应的 SQL 语句如下:
INSERT INTO `users` (`id`,`empId`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'1','2018-10-10 07:53:26','2018-10-10 07:53:26');
步骤二:创建 Note,对应的 SQL 语句如下:
INSERT INTO `notes` (`id`,`title`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'learn sequelize','2018-10-10 07:53:26','2018-10-10 07:53:26');
以上 SQL 执行后,会插入一条 note 数据,但此时该条记录的外键 userId
为空。
步骤三:使用已创建用户的 id 值,设置步骤二 note 记录的外键 userId
的值,对应的 SQL 语句如下:
UPDATE `notes` SET `userId`=1,`updatedAt`='2018-10-10 07:53:26' WHERE `id` IN (1)
- 修改
const user = await User.create({ empId: '1' }); // (1) const note1 = await user.createNote({ title: 'learn node.js' }); // (2) const note2 = await user.createNote({ title: 'learn rx.js' }); // (3) const note3 = await Note.create({ title: 'learn angular.js' }); // (4) const note4 = await Note.create({ title: 'learn typescript.js' }); // (5) await user.setNotes([note3, note4]); // (6)
步骤一:新建用户,对应的 SQL 语句如下:
INSERT INTO `users` (`id`,`empId`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'1','2018-10-10 08:09:13','2018-10-10 08:09:13');
步骤二与步骤三:创建 Note1 和 Note2,对应的 SQL 语句如下:
INSERT INTO `notes` (`id`,`title`,`createdAt`,`updatedAt`,`userId`) VALUES (DEFAULT,'learn node.js','2018-10-10 08:12:49','2018-10-10 08:12:49',1); INSERT INTO `notes` (`id`,`title`,`createdAt`,`updatedAt`,`userId`) VALUES (DEFAULT,'learn rx.js','2018-10-10 08:12:49','2018-10-10 08:12:49',1);
步骤四与步骤五:创建 Note3 和 Note4,对应的 SQL 语句如下:
INSERT INTO `notes` (`id`,`title`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'learn angular.js','2018-10-10 08:12:49','2018-10-10 08:12:49'); INSERT INTO `notes` (`id`,`title`,`createdAt`,`updatedAt`) VALUES (DEFAULT,'learn typescript.js','2018-10-10 08:12:49','2018-10-10 08:12:49');
步骤六:设置关联关系,执行流程及对应的 SQL 语句如下:
- 查询 userId 为 1 的用户的所有 note 记录:
SELECT `id`, `title`, `createdAt`, `updatedAt`, `userId` FROM `notes` AS `note` WHERE `note`.`userId` = 1;
- 将 note1、note2 记录的外键 userId 的值置为 NULL,切断之间的关系:
UPDATE `notes` SET `userId`=NULL,`updatedAt`='2018-10-10 08:12:49' WHERE `id` IN (1, 2)
- 将 note3、note4 记录的外键 userId 的值置为当前用户的 id,完成关系的建立:
UPDATE `notes` SET `userId`=1,`updatedAt`='2018-10-10 08:12:49' WHERE `id` IN (3, 4)
因为我们需要根据传人 setNotes
的数组来计算出哪些 note
要切断关系、哪些要新增关系,所以就需要查出来进行一个计算集合的 “交集” 运算。
- 删除
方式一
const user = await User.create({ empId: '1' }); // (1) const note1 = await user.createNote({ title: 'learn node.js' }); // (2) const note2 = await user.createNote({ title: 'learn rx.js' }); // (3) await user.setNotes([]); // (4)
步骤一至三的执行流程及对应 SQL 语句请参考修改环节,这里不再介绍。
步骤四:调用 user.setNotes([])
方法,删除当前用户下的所有 note 记录,执行流程及对应的 SQL 语句如下:
- 查询 userId 为 1 的用户的所有 note 记录:
SELECT `id`, `title`, `createdAt`, `updatedAt`, `userId` FROM `notes` AS `note` WHERE `note`.`userId` = 1;
- userId 为 1 的用户的所有 note 记录的外键 userId 置为 NULL,切断关系:
UPDATE `notes` SET `userId`=NULL,`updatedAt`='2018-10-10 08:25:04' WHERE `id` IN (1, 2)
通过以上的 SQL 语句,我们知道调用 user.setNotes([])
会删除当前用户下所关联的所有 note 记录,若需要删除指定 note 记录,则可以调用 user.removeNote
方法。
方式二
const user = await User.create({ empId: '1' }); // (1) const note1 = await user.createNote({ title: 'learn node.js' }); // (2) const note2 = await user.createNote({ title: 'learn rx.js' }); // (3) user.removeNote(note2);
步骤一至三的执行流程及对应 SQL 语句请参考修改环节,这里不再介绍。
步骤四:调用 user.removeNote(note2)
方法,将删除当前用户下指定的 note2 记录,对应的 SQL 语句如下:
UPDATE `notes` SET `userId`=NULL,`updatedAt`='2018-10-10 08:38:40' WHERE `userId` = 1 AND `id` IN (2)
- 查询
- 查询当前用户下所有满足条件的 note 数据:
const Op = Sequelize.Op const user = await User.findById(1); // (1) const notes = await user.getNotes({ // (2) where: { title: { [Op.like]: '%node%' } } }); console.log(`User ${user.id}: has ${notes.length} notes`);
步骤一:查询 id 为 1 的用户,对应的 SQL 语句如下:
SELECT `id`, `empId`, `createdAt`, `updatedAt` FROM `users` AS `user` WHERE `user`.`id` = 1;
步骤二:根据查询条件,获取 id 为 1 的用户下的所有满足条件的 note 记录,对应的 SQL 语句如下:
SELECT `id`, `title`, `createdAt`, `updatedAt`, `userId` FROM `notes` AS `note` WHERE (`note`.`userId` = 1 AND `note`.`title` LIKE '%node%');
- 查询所有满足条件的 note,同时获取 note 所属的 user:
const Op = Sequelize.Op const notes = await Note.findAll({ include: [User], where: { title: { [Op.like]: '%node%' } } }); // 当前note属于哪个user可以通过note.user访问 console.log(`Has found ${notes.length} notes`);
以上操作对应的 SQL 语句如下:
SELECT `note`.`id`, `note`.`title`, `note`.`createdAt`, `note`.`updatedAt`, `note`.`userId`, `user`.`id` AS `user.id`, `user`.`empId` AS `user.empId`, `user`.`createdAt` AS `user.createdAt`, `user`.`updatedAt` AS `user.updatedAt` FROM `notes` AS `note` LEFT OUTER JOIN `users` AS `user` ON `note`.`userId` = `user`.`id` WHERE `note`.`title` LIKE '%node1%';
- 查询所有满足条件的 user,同时获取该 user 所有满足条件的 note:
const Op = Sequelize.Op const users = await User.findAll({ include: [Note], where: { createdAt: { [Op.lt]: new Date() } } }); // user的notes可以通过user.notes访问 console.log(`Has found ${users.length} users`);
以上操作对应的 SQL 语句如下:
SELECT `user`.`id`, `user`.`empId`, `user`.`createdAt`, `user`.`updatedAt`, `notes`.`id` AS `notes.id`, `notes`.`title` AS `notes.title`, `notes`.`createdAt` AS `notes.createdAt`, `notes`.`updatedAt` AS `notes.updatedAt`, `notes`.`userId` AS `notes.userId` FROM `users` AS `user` LEFT OUTER JOIN `notes` AS `notes` ON `user`.`id` = `notes`.`userId` WHERE `user`.`createdAt` < '2018-10-10 09:21:15';
这里需要注意的是, eager loading
中 include
传递的是需获取的相关模型,默认是获取全部,我们也可以根据实际需求再对这个模型进行一层过滤。比如:
const Op = Sequelize.Op const users = await User.findAll({ include: [{ model: Note, where: { title: { [Op.like]: '%node%' } } }], where: { createdAt: { [Op.lt]: new Date() } } });
以上操作对应的 SQL 语句如下:
SELECT `user`.`id`, `user`.`empId`, `user`.`createdAt`, `user`.`updatedAt`, `notes`.`id` AS `notes.id`, `notes`.`title` AS `notes.title`, `notes`.`createdAt` AS `notes.createdAt`, `notes`.`updatedAt` AS `notes.updatedAt`, `notes`.`userId` AS `notes.userId` FROM `users` AS `user` INNER JOIN `notes` AS `notes` ON `user`.`id` = `notes`.`userId` AND `notes`.`title` LIKE '%node%' WHERE `user`.`createdAt` < '2018-10-10 09:42:26';
当我们对 include
的模型加了 where
过滤条件时,会使用 inner join
来进行查询,这样保证只有那些拥有标题含有 node
关键词 note
的用户才会返回。关于各种 join 的区别,可以参考: a-visual-explanation-of-sql-joins
。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Akka与Java内存模型的关系
- Akka与Java内存模型的关系
- Sequelize 系列教程之一对一模型关系
- 传统机器学习和前沿深度学习推荐模型演化关系
- Sequelize 系列教程之多对多模型关系
- Flask框架从入门到精通之模型关系(十七)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
程序员面试金典(第5版)
[美] Gayle Laakmann McDowell / 李琳骁、漆 犇 / 人民邮电出版社 / 2013-11 / 59.00
本书是原谷歌资深面试官的经验之作,层层紧扣程序员面试的每一个环节,全面而详尽地介绍了程序员应当如何应对面试,才能在面试中脱颖而出。第1~7 章主要涉及面试流程解析、面试官的幕后决策及可能提出的问题、面试前的准备工作、对面试结果的处理等内容;第8~9 章从数据结构、概念与算法、知识类问题和附加面试题4 个方面,为读者呈现了出自微软、苹果、谷歌等多家知名公司的150 道编程面试题,并针对每一道面试题目......一起来看看 《程序员面试金典(第5版)》 这本书的介绍吧!