内容简介:数据模型中的表关系一般有三种:一对一、一对多、多对多。我们首先从一个基本概念开始,你将会在大多数关联中使用
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框架从入门到精通之模型关系(十七)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Writing Windows VxDs and Device Drivers, Second Edition
Karen Hazzah / CMP / 1996-01-12 / USD 54.95
Software developer and author Karen Hazzah expands her original treatise on device drivers in the second edition of "Writing Windows VxDs and Device Drivers." The book and companion disk include the a......一起来看看 《Writing Windows VxDs and Device Drivers, Second Edition》 这本书的介绍吧!