Sequelize是一个Node.js 的ORM。什么是ORM呢?对象关系映射(Object Relational Mapping)。什么意思?就是在编程语言中,很容易创建对象,如果在面向对象的语言中,它也只有对象,但在关系型数据库中,它只有关系(表)。如果想在程序中操作关系型数据库,就要在对象和表之间做转换,比如,取出对象中的属性值,写一个SQL语句,转化成表的属性的值。ORM就是做这层转换的,对象的属性和值,和表中的属性和值一一对应(映射),操作对象,就可以操作关系型数据库,不用写SQL语句。因此,学习Sequelize 要先有一个数据库,比如MySQL,再创建一个Node.js项目,然在项目中使用Sequelize操作MySQL数据库。打开命令行,登录MySQL,CREATE DATABASE airline; 创建 airline 数据库。mkdir airline && cd airline && npm init -y, npm install express,  新建server.js,

  1. import express from 'express';
  2. const app = express();
  3.  
  4. app.get('/', (req, res) => {
  5. res.send('Hello World')
  6. })
  7. app.listen(3000, () => {
  8. console.log("服务器启动成功");
  9. })

  由于使用ES module,在package.json中,设置"type": "module",node server.js,项目启动成功,最简单的Node.js项目创建完成。npm install sequelize mysql2,就可以在项目中使用Sequelize来操作数据库了。操作数据库要先连接数据库,连接数据库就是创建Sequelize的实例, 在server.js中

  1. import { Sequelize } from 'sequelize';
  2.  
  3. // new Sequelize(数据库名, 登录数据库时的用户名, 登录数据库时的密码, {host: 哪台主机上的数据库, dialect: 使用什么数据库})
  4. const sequelize = new Sequelize('airline', 'root', '123', {
  5. host: 'localhost',
  6. dialect: 'mysql'
  7. });
  8.  
  9. try {
  10. await sequelize.authenticate();
  11. console.log('连接成功');
  12. } catch (error) {
  13. console.error('连接失败', error);
  14. }

   连接成功,怎么操作数据库呢?Sequelize有一个model的概念,它代表数据库中的一张表,操作model就相当于操作数据库中的表,想要操作数据库哪张表,就要为哪张表创建一个model,因此使用Sequelize都是从创建model开始。有两种方式创建model,一种是sequelize.define(),另外一种是继承Model并调用 init()方法,无论哪种方式,创建Model时候,都要提供表名,以及表中的字段和数据类型,因为Model和表相关,代表的是一张表 

  1. // 参数: model名, 属性(对应表中的字段), 可选配置项
  2. sequelize.define('FlightSchedule', {
  3. originAirport: DataTypes.STRING,
  4. destinationAirport: DataTypes.STRING,
  5. departureTime: DataTypes.DATE
  6. });
  7.  
  8. // 或者
  9. class FlightSchedule extends Model { }
  10.  
  11. // int参数:属性(对应表中的字段), 配置项
  12. FlightSchedule.init({
  13. originAirport: DataTypes.STRING,
  14. destinationAirport: DataTypes.STRING,
  15. departureTime: DataTypes.DATE
  16. }, {
  17. sequelize,
  18. });

  你可能已经注意到了,创建FlightSchedule并没有提供表名,这是因为在默认情况下,Sequelize会对model名进行复数化,当作表名。FlightSchedule就会操作flightschedules表。当然,可以提供表名,也可以禁止复数化,还有就是,数据库的字段名也可以不用驼峰命名,而是使用 _连接,这些都可以在可选配置项中进行配置,

  1. {
  2. freezeTableName: true, // 表名是flightschedule
  3. tableName: 'a', // 直接定义表名为a
  4. underscored: true // 数据库中的对应的字段是origin_airport
  5. }

  这里就不配置了,使用默认值就好,可以操作表了,但数据库中表不存在怎么办?Sequelize提供了一个sync()方法,如果数据库中没有对应的表,它就会创建表,如果有表,那就什么都不做,整个index.js如下

  1. import express from 'express';
  2. import { Sequelize, DataTypes } from 'sequelize';
  3.  
  4. const app = express();
  5.  
  6. const sequelize = new Sequelize('airline', 'root', '123', {
  7. host: 'localhost',
  8. dialect: 'mysql'
  9. });
  10.  
  11. sequelize.define('FlightSchedule', {
  12. originAirport: DataTypes.STRING,
  13. destinationAirport: DataTypes.STRING,
  14. departureTime: DataTypes.DATE
  15. });
  16. await sequelize.sync();
  17.  
  18. app.get('/', (req, res) => {
  19. res.send('Hello World')
  20. })
  21. app.listen(3000, () => {
  22. console.log("server start");
  23. })

  重新启动服务器,查看MySQL数据库,airline下面多了flightschedules表,但也会发现flightschedules表多了3个字段,id,createdAt和updatedAt。默认情况下,Sequelize会为创建的model增加这三个属性,id对应表中的主键, createdAt和updatedAt属性来记录表中字段的创建和更新。创建model的时候,写了三个属性,实际上model有六个属性。当然 createdAt和updatedAt 是可以配置的,它们统称为 timestamps, timestamps为false,就不会给model上添加createdAt 和updatedAt字段。也可以添加某一个字段,比如timestamps为true, createdAt 为true,就只为model 增加createdAt属性。还可以给它们重命名,比如,createdAt: 'addAt'。

  作为演示代码,以上使用Sequelize的方法,没有问题。但作为真正的应用程序,它还是有很多问题的,首先,数据库的配置信息写到主文件中,不利于安全,因为都是用户名和密码,至少要写到配置文件中,其次,项目中model肯定很多,至少需要一个目录来维护model。再就是sync()方法,如果修改表,就无能为力了,这需要migiration。我们可以手动创建这些目录和文件,但Sequlize 提供了 sequelize-cli 命令行工具,可以更好地完成任务。npm install --save-dev sequelize-cli, 首先是npx sequelize init ,初始化Sequelize项目,多了4个目录,

  config目录:config.json保存数据库配置信息,可以配置开发环境,测试环境,和生产环境的数据库信息。

  models目录:包含项目中的所有Model。它默认包含index.js文件,从models目录下读取文件(fs.readdirSync(__dirname)),然后循环创建model(require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);),赋值给db对象(db[model.name] = model;),最后把db对象暴露出去。也就是说,可以一个文件定义一个model。在models下创建flightSchedule.js,把FlightSchedule的定义放到里面

  1. export default (sequelize, DataTypes) => {
  2. return sequelize.define('FlightSchedule', {
  3. originAirport: DataTypes.STRING,
  4. destinationAirport: DataTypes.STRING,
  5. departureTime: DataTypes.DATE
  6. });
  7. };

  然后在index.js

  1. import { Sequelize, DataTypes } from 'sequelize';
  2. import { createRequire } from 'module'; // 在esm中使用require,处理JSON
  3.  
  4. import createFlightSchedule from './flightSchedule.js' // 引入model
  5.  
  6. const env = process.env.NODE_ENV || 'development';
  7. const require = createRequire(import.meta.url);
  8. const config = require('../config/config.json')[env]
  9.  
  10. const sequelize = new Sequelize(config.database, config.username, config.password, config);
  11.  
  12. const FlightSchedule = createFlightSchedule(sequelize, DataTypes) // 创建Model
  13.  
  14. export default {
  15. FlightSchedule,
  16. sequelize
  17. }

  可以看到,默认情况下,连接的是development数据库,在config.json中配置development

  1. "development": {
  2. "username": "root",
  3. "password": "123",
  4. "database": "airline",
  5. "host": "127.0.0.1",
  6. "dialect": "mysql"
  7. }

  migrations目录:包含对数据库表的Schema的定义和修改。现在创建了FlightSchedule model,就要对应地创建FlightSchedules表,为此Sequelize提供了Query Interface和migration命令行工具。
使用npx sequelize migration:generate 就可以生成一个migration文件,不过要提供了 --name参数来定义这个migration,比如 npx sequelize migration:generate --name create-flight-schedule。migrations目录下生成了一个文件,文件名的格式是时间戳-action-表名。它有两个方法,up(…) 用来执行migration要做的事情,down(…) 回滚这个migration做的事情,所以要在up方法中创建table,在down方法中,删除table。需要注意的是,创建model的时候是3个属性,但是默认会增加Id,createdAt和updatedAt,所以model有6个属性,创建表的时候,表也要有对应的6个属性

  1. /** @type {import('sequelize-cli').Migration} */
  2. module.exports = {
  3. async up(queryInterface, Sequelize) {
  4. await queryInterface.createTable('FlightSchedules', {
  5. id: {
  6. allowNull: false,
  7. autoIncrement: true,
  8. primaryKey: true,
  9. type: Sequelize.INTEGER
  10. },
  11. originAirport: {
  12. type: Sequelize.STRING
  13. },
  14. destinationAirport: {
  15. type: Sequelize.STRING
  16. },
  17. departureTime: {
  18. type: Sequelize.DATE
  19. },
  20. createdAt: {
  21. allowNull: false,
  22. type: Sequelize.DATE
  23. },
  24. updatedAt: {
  25. allowNull: false,
  26. type: Sequelize.DATE
  27. }
  28. });
  29. },
  30. async down(queryInterface, Sequelize) {
  31. await queryInterface.dropTable('FlightSchedules');
  32. }
  33. };

  npx sequelize db:migrate 执行migrations目录下的数据库的迁移操作,但是报错了,因为sequelize命令并不支持ES module,所以把migration文件的后缀改为.cjs, 再次执行npx sequelize db:migrate ,查看MySQL数据库,有了flightschedules表, 但也多了SequelizeMeta 表(记录执行过的迁移脚本)。执行migrate的时候,Sequelize会按时间戳的顺序遍历整个migrations目录, 然后跳过 SequelizeMeta 表中包含的文件,也就是以前执行过的migration文件,不用再重复执行了。

   seeders目录:快速向数据库中填充数据,方便测试程序。使用npx sequelize seed:generate生成文件,它也是有一个--name,给这个seed操作起个名字,npx sequelize seed:generate --name initial-flight-schedules. 在seeders目录创建了一个js文件,还是把它变成.cjs文件,up(…) 填充数据,down(…) 回滚数据。

  1. /** @type {import('sequelize-cli').Migration} */
  2. module.exports = {
  3. async up(queryInterface, Sequelize) {
  4. await queryInterface.bulkInsert('FlightSchedules', [{
  5. originAirport: "济南",
  6. destinationAirport: "武汉",
  7. departureTime: "2022-01-01 08:00:00",
  8. createdAt: new Date(),
  9. updatedAt: new Date(),
  10. }], {});
  11. },
  12.  
  13. async down(queryInterface, Sequelize) {
  14. await queryInterface.bulkDelete('FlightSchedules', null, {});
  15. }
  16. };

  npx sequelize db:seed:all,执行seeders目录下的所有文件。npx sequelize db:seed --seed=fileName,指定执行seeders目录下哪个文件。无论执行哪一个命令,查看数据库,flightschedules表中都有一条记录。需要注意的是, db:migrate and db:seed 命令使用 NODE_ENV 环境变量来决定执行到哪个数据库,默认是development 配置下的数据库

  再创建一个model,比如Airplane,熟悉一下流程。先在models下新建airplane.js

  1. export default (sequelize, DataTypes) => {
  2. return sequelize.define('Airplane', {
  3. planeModel: DataTypes.STRING,
  4. totalSeats: DataTypes.STRING,
  5. });
  6. };

  再在其index.js 引入并创建Airplane model

  1. import createAirplane from './airplane.js';
  2.  
  3. const Airplane = createAirplane(sequelize, DataTypes)
  4.  
  5. export default {
  6. Airplane,
  7. FlightSchedule,
  8. sequelize
  9. }

  创建migration, npx sequelize migration:generate --name create-airplane, 不要忘了把生成的文件后缀名改为.cjs,

  1. module.exports = {
  2. async up(queryInterface, Sequelize) {
  3. await queryInterface.createTable('Airplanes', {
  4. id: {
  5. allowNull: false,
  6. autoIncrement: true,
  7. primaryKey: true,
  8. type: Sequelize.INTEGER
  9. },
  10. planeModel: {
  11. type: Sequelize.STRING
  12. },
  13. totalSeats: {
  14. type: Sequelize.INTEGER
  15. },
  16. createdAt: {
  17. allowNull: false,
  18. type: Sequelize.DATE
  19. },
  20. updatedAt: {
  21. allowNull: false,
  22. type: Sequelize.DATE
  23. }
  24. });
  25. },
  26. async down(queryInterface, Sequelize) {
  27. await queryInterface.dropTable('Airplanes');
  28. }
  29. };

   npx sequelize db:migrate 执行migration,可以再创建一个seeder文件,填充数据库中的数据。npx sequelize seed:generate --name initial-airplanes

  1. module.exports = {
  2. async up(queryInterface, Sequelize) {
  3. await queryInterface.bulkInsert('Airplanes', [{
  4. planeModel: 'Airbus A220-100',
  5. totalSeats: 110,
  6. createdAt: new Date(),
  7. updatedAt: new Date()
  8. }, {
  9. planeModel: 'Airbus A220-300',
  10. totalSeats: 110,
  11. createdAt: new Date(),
  12. updatedAt: new Date()
  13. }, {
  14. planeModel: 'Airbus A 318',
  15. totalSeats: 115,
  16. createdAt: new Date(),
  17. updatedAt: new Date()
  18. }, {
  19. planeModel: 'Boeing 707-100',
  20. totalSeats: 100,
  21. createdAt: new Date(),
  22. updatedAt: new Date(),
  23. }, {
  24. planeModel: 'Boeing 737-100',
  25. totalSeats: 85,
  26. createdAt: new Date(),
  27. updatedAt: new Date()
  28. }], {});
  29. },
  30.  
  31. async down(queryInterface, Sequelize) {
  32. await queryInterface.bulkDelete('Airplanes', null, {});
  33. }
  34. };

  npx sequelize db:seed --seed=20231017074324-initial-airplanes.cjs 向数据库中插入数据。再创建一个customer,在models目录下,创建customer.js

  1. export default (sequelize, DataTypes) => {
  2. return sequelize.define('Customer', {
  3. name: DataTypes.STRING,
  4. email: DataTypes.STRING,
  5. });
  6. }

  然后index.js中引入

  1. import createCustomer from './customer.js';
  2.  
  3. const Customer = createCustomer(sequelize, DataTypes);
  4.  
  5. export default {
  6. Airplane,
  7. Customer,
  8. FlightSchedule,
  9. sequelize
  10. }

  最后生成一个migration文件,npx sequelize migration:generate --name create-customer

  1. module.exports = {
  2. async up(queryInterface, Sequelize) {
  3. await queryInterface.createTable('Customers', {
  4. id: {
  5. allowNull: false,
  6. autoIncrement: true,
  7. primaryKey: true,
  8. type: Sequelize.INTEGER
  9. },
  10. name: {
  11. type: Sequelize.STRING
  12. },
  13. email: {
  14. type: Sequelize.STRING
  15. },
  16. createdAt: {
  17. allowNull: false,
  18. type: Sequelize.DATE
  19. },
  20. updatedAt: {
  21. allowNull: false,
  22. type: Sequelize.DATE
  23. }
  24. });
  25. },
  26.  
  27. async down(queryInterface, Sequelize) {
  28. queryInterface.dropTable('Customers');
  29. }
  30. };

  npx sequelize db:migrate执行migration。现在server.js

  1. import express from 'express';
  2. import models from './models/index.js' // 引入model
  3.  
  4. const app = express();
  5.  
  6. // 由于执行mirgation,只要成功连接数据库,就能操作数据库,表一定会存在
  7. await models.sequelize.authenticate();
  8.  
  9. app.get('/', (req, res) => {
  10. res.send('Hello World')
  11. })
  12. app.listen(3000, () => {
  13. console.log("server start");
  14. })

  增删改查

  新增一条记录:model名.create(),它接受一个对象作为参数,对象的属性就是model中定义的属性,返回一个model的实例,

  1. app.get('/', async (req, res) => {
  2. var record = await models.Customer.create({name: 'Sam', email: '123@qq.com'});
  3. res.send(JSON.stringify(record))
  4. })

  启动服务器,localhost:3000,数据库中成功插入一条数据,同时页面显示新创建的customer。create()创建model实例的时候,只需要操作创建Model时定义的属性,Sequelize自动添加的属性,它自己会处理好。这时可以npm i nodemon -D, npx nodemon  server.js启动服务器,保证服务器一直处于启动状态。新增多条记录(批量插入):model名.bulkCreate(),它接受的是数组,数组的每一个元素是对象,对象和create()接受的对象一样

  1. app.get('/', async (req, res) => {
  2. var record = await models.Customer.bulkCreate([
  3. {name: '张三', email: '456@qq.com'},
  4. {name: '李四', email: '789@qq.com'},
  5. ])
  6. res.send(JSON.stringify(record))
  7. })

  删除:如果只是删除一条记录,可以先model名.findOne() 查出这个实例, 然后再在实例上调用destroy()方法

  1. app.get('/', async (req, res) => {
  2. var record = await models.Customer.findOne({ where: { id: 3 } });
  3. await record.destroy();
  4. res.send(JSON.stringify(record))
  5. })

  如果想要一次删除多条记录,model名.destroy(),destroy的参数就是用来筛选要删除的记录。

  1. app.get('/', async (req, res) => {
  2. var record = await models.Customer.destroy({ where: { id: 2 } });
  3. res.send(JSON.stringify(record))
  4. })

  删除还有硬删除和软删除之分。在创建model的时候,如果配置了paranoid: true,就表示软删除,删除的时候,不会真正的从数据库中删除,而是model上添加deleteAt字段,当然前提是timestamps 设为true,此时要硬删除,就需要在调用destroy方法时,添加force: true。如果软删除,在migration的时候,要在表中创建deleteAt字段。

  1. await Post.destroy({
  2. where: {
  3. id: 1
  4. },
  5. force: true
  6. });

  更新:更新多条记录,使用model名.update(),第一个参数是要update成什么, 第二个参数是查询条件

  1. app.get('/', async (req, res) => {
  2. var record = await models.Customer.update(
  3. { name: '张三', email: '456@qq.com' },
  4. { where: { id: 1 } }
  5. );
  6. res.send(JSON.stringify(record))
  7. })

  如果只想更新一条记录,先调用model名.findOne()获取到实例,然后修改实例的属性,最后调用实例上的save()

  1. app.get('/', async (req, res) => {
  2. var record = await models.Customer.findOne(
  3. { where: { id: 1 } }
  4. );
  5. record.name = 'Sam'
  6. await record.save()
  7.  
  8. res.send(JSON.stringify(record))
  9. })

  query:modal名.方法名,比如findAll,findone。

  1. app.get('/', async (req, res) => {
  2. const airplances = await models.Airplane.findAll();
  3. res.send("<pre>" + JSON.stringify(airplances, undefined,
  4. 4) + "</pre>");
  5. })

  查询方法中,参数中带有attributes,表示只查询表中的某个或某些字段,而不是全部字段。它是一个数组,把要查询的字段列出来

  1. app.get('/', async (req, res) => {
  2. const airplances = await models.Airplane.findAll({
  3. attributes: ['planeModel', 'totalSeats']
  4. });
  5. res.send("<pre>" + JSON.stringify(airplances, undefined,
  6. 4) + "</pre>");
  7. })

  如果对字段进行重命名,数组中的元素需要是一个数组,它的第一项是原字段名,第二项是新字段名。

  1. const airplances = await models.Airplane.findAll({
  2. attributes: [
  3. 'planeModel',
  4. ['totalSeats', 'seats'], // totalSeats 重命名为 seats
  5. ]
  6. });

  查询方法中,参数中带有where是条件查询,为此Sequelize还专门提供了Op。

  1. import { Op } from 'sequelize';
  2.  
  3. app.get('/', async (req, res) => {
  4. const airplances = await models.Airplane.findAll({
  5. where: {
  6. id: {
  7. [Op.or]: [1, 2]
  8. }
  9. }
  10. });
  11. res.send("<pre>" + JSON.stringify(airplances, undefined,
  12. 4) + "</pre>");
  13. })

  如果where查询语句中调用函数,要用sequelize.fn 来定义函数,sequelize.col来指定对哪一个属性来进行函数操作。sequelize.where 来表示相等性。

  1. const airplances = await models.Airplane.findAll({
  2. where: models.sequelize.where(
  3. models.sequelize.fn('char_length', models.sequelize.col('planeModel')),
  4. 5)
  5. });

  sequelize.where 的第二个参数是基本类型,它就进行相等比较。如果要进行其它比较,可以是个对象,对象的属性,就果定义什么比较

  1. app.get('/', async (req, res) => {
  2. const airplances = await models.Airplane.findAll({
  3. where: models.sequelize.where(
  4. models.sequelize.fn('char_length', models.sequelize.col('planeModel')),
  5. {
  6. [Op.gt]: 12
  7. })
  8. });
  9. res.send("<pre>" + JSON.stringify(airplances, undefined,
  10. 4) + "</pre>");
  11. })

  查询方法中,带有order,就是排序,它是一个数组,数组的每一项也是一个数组,数组的第一项指定按哪个字段进行排序,第二项指定按升序还是降序进行排序

  1. app.get('/', async (req, res) => {
  2. const airplances = await models.Airplane.findAll({
  3. order: [
  4. ['planeModel', 'DESC']
  5. ]
  6. });
  7. res.send("<pre>" + JSON.stringify(airplances, undefined,
  8. 4) + "</pre>");
  9. })

  查询方法中,带有group,就是分组,指定按哪一个字段进行分组。分组之后,通常使用聚合函数,聚合函数的实现是使用sequelize.fn,它的第一个参数是使用哪个聚合函数(字符串),第二个参数是对哪个字段进行聚合。通常聚合函数要进行重命名,所以聚合函数是数组的第一项,新的名字是第二项,然后整个数组放到attributes数组中,

  1. app.get('/', async (req, res) => {
  2. const airplances = await models.Airplane.findAll({
  3. attributes: ['planeModel', [models.sequelize.fn('sum', models.sequelize.col('totalSeats')), 'totalSeatsCount']],
  4. group: 'planeModel'
  5. });
  6. res.send("<pre>" + JSON.stringify(airplances, undefined,
  7. 4) + "</pre>");
  8. })

  查询方法中,带有offset和limit,就是用于分页

  1. app.get('/', async (req, res) => {
  2. const airplances = await models.Airplane.findAll({
  3. offset: 1, limit: 2
  4. });
  5. res.send("<pre>" + JSON.stringify(airplances, undefined,
  6. 4) + "</pre>");
  7. })

  关系

  在关系型数据库中,表与表之间存在1对1,1对多,和多对多的关系,那在Sequelize中,怎么用model来表示这些关系?每一个model都有四个方法,hasOne, BelongsTo, hasMany, BelongsToMany。由于关系是相对的,所以每一种关系都用两个方法实现。 

  实现1对1,用hasOne和BelongsTo。比如飞机(Airplanes)和飞机详情表(AirplaneDetails)

  1. Airplanes.hasOne(AirplaneDetails)
  2. AirplaneDetails.BelongsTo(Airplanes)

  由于表与表之间的关系是通过外键实现的,所以默认情况下,hasOne方法会把外键放到它的参数上,也就是AirplaneDetails model中,外键的列名它所引用的model名+Id,AirplaneId, 除非外键已经存在,AirplaneDetails model 多了一个AirplaneId属性,

  实现1对多,用hasMany 和belongsTo。比如 一架飞机可以执行多次航班(hasMany),但一个航班只能用一架飞机(BelongsTo)。

  1. Airplane.hasMany(FlightSchedule)
  2. FlightSchedule.belongsTo(Airplane)

  1对多的关系,外键放到多的那一边,所以FlightSchedule多了AirplaneId属性。实现多对多要用BelongsToMany,因为多对多的关系,需要中间表,所以它还要一个through参数来指定使用哪张表,如果参数是字符串,Sequelize默认表中字段名是两个关联model的名字+id。比如Customer 和FlightSchedule,一个乘客可以乘多趟航班,一趟航班有多个客人,它们之间的关联是购买的飞机票

  1. Customer.belongsToMany(FlightSchedule, { through: 'BoardingTickets' });
  2. FlightSchedule.belongsToMany(Customer, { through: 'BoardingTickets' });

  默认情况下,Sequelize会操作BoardingTickets表,表中有FlightScheduleId和CustomerId字段。参数还可以一个model,

  1. const BoardingTickets = sequelize.define('BoardingTickets', {
  2. FlightScheduleId: {
  3. type: DataTypes.INTEGER,
  4. references: {
  5. model: FlightSchedule,
  6. key: 'id'
  7. }
  8. },
  9. CustomerId: {
  10. type: DataTypes.INTEGER,
  11. references: {
  12. model: 'Customers', // 字符串是指表名
  13. key: 'id'
  14. }
  15. },
  16. // SomeOtherColumn: { // 可以添加其它字段
  17. // type: DataTypes.STRING
  18. // }
  19. });
  20.  
  21. Customer.belongsToMany(FlightSchedule, { through: 'BoardingTickets' });
  22. FlightSchedule.belongsToMany(Customer, { through: 'BoardingTickets' });

  两种方式是一样的,在models的index.js下面,在 export default前面,

  1. Airplane.hasMany(FlightSchedule)
  2. FlightSchedule.belongsTo(Airplane)
  3.  
  4. Customer.belongsToMany(FlightSchedule, { through: 'BoardingTickets' });
  5. FlightSchedule.belongsToMany(Customer, { through: 'BoardingTickets' });

  为了满足关系,需要做migration,首先创建BoardingTickets,  npx sequelize migration:generate --name create-board-ticket

  1. module.exports = {
  2. async up(queryInterface, Sequelize) {
  3. await queryInterface.createTable('BoardingTickets', {
  4. id: {
  5. allowNull: false,
  6. autoIncrement: true,
  7. primaryKey: true,
  8. type: Sequelize.INTEGER
  9. },
  10. createdAt: {
  11. allowNull: false,
  12. type: Sequelize.DATE
  13. },
  14. updatedAt: {
  15. allowNull: false,
  16. type: Sequelize.DATE
  17. },
  18. CustomerId: {
  19. type: Sequelize.INTEGER,
  20. references: {
  21. model: 'Customers',
  22. key: 'id'
  23. },
  24. onUpdate: 'set null',
  25. onDelete: 'cascade'
  26. },
  27. FlightScheduleId: {
  28. type: Sequelize.INTEGER,
  29. references: {
  30. model: 'FlightSchedules',
  31. field: 'id'
  32. },
  33. onDelete: 'set null',
  34. onUpdate: 'cascade'
  35. }
  36. });
  37. },
  38.  
  39. async down(queryInterface, Sequelize) {
  40. await queryInterface.dropTable('BoardingTickets');
  41. }
  42. };

  然后给FlightSchedules 添加外键,npx sequelize migration:generate --name add-flightschedules-references

  1. module.exports = {
  2. async up (queryInterface, Sequelize) {
  3. await queryInterface.addColumn('FlightSchedules', 'AirplaneId', {
  4. type: Sequelize.INTEGER,
  5. });
  6.  
  7. await queryInterface.addConstraint('FlightSchedules', {
  8. type: 'foreign key',
  9. fields: ['AirplaneId'],
  10. references: {
  11. table: 'Airplanes',
  12. field: 'id'
  13. },
  14. name: 'fkey_flight_schedules_airplane',
  15. onDelete: 'set null',
  16. onUpdate: 'cascade'
  17. });
  18. },
  19.  
  20. async down (queryInterface, Sequelize) {
  21. await queryInterface.removeConstraint(
  22. 'FlightSchedules', 'fkey_flight_schedules_airplane'
  23. );
  24.  
  25. await queryInterface.removeColumn('FlightSchedules', 'AirplaneId');
  26. }
  27. };

  执行迁移

  1. sequelize db:migrate

  当model之间建立联系后,每一个model实例多了许多方法,可以使用一个model实例去操作另外一个model。比如找到一个airplane,就可以找到相对应的flight schedue,所以airplane的实例,就有get fligt shcedule 的方法。当有一个airplane时,add一个flight schedule,两者就建立了联系,甚至,一个airplane 都有create flight shcedule 的功能,创建和建立联系一起作了。

  1. app.get('/', async (req, res) => {
  2. const airplance = await models.Airplane.findOne({
  3. where: { id: 1 }
  4. });
  5.  
  6. const flightSchedule = await models.FlightSchedule.findOne({
  7. where: { id: 1 }
  8. })
  9. // 数据库中 flightSchedule表中,id=1的 AirplaneId 变成了 '1'
  10. await airplance.addFlightSchedule(flightSchedule)
  11. // 创建了一条flightSchedule记录,并且它的AirplaneId也是1
  12. await airplance.createFlightSchedule({
  13. originAirport: "深圳",
  14. destinationAirport: "武汉",
  15. departureTime: "2023-10-01 20:00:00"
  16. })
  17.  
  18. const flightSchedules = await models.FlightSchedule.findAll();
  19. res.send("<pre>" + JSON.stringify(flightSchedules, undefined,
  20. 4) + "</pre>");
  21. })

  当model之间的关系是多对多,在一个model实例上执行get另一个model时,会把中间表也查出来

  1. app.get('/', async (req, res) => {
  2. const customer = await models.Customer.findOne({
  3. where: { id: 1 }
  4. });
  5.  
  6. const flightSchedules = await customer.getFlightSchedules();
  7. res.send("<pre>" + JSON.stringify(flightSchedules, undefined,
  8. 4) + "</pre>");
  9. })

  页面展示:

  1. [
  2. {
  3. "id": 1,
  4. "originAirport": "济南",
  5. "destinationAirport": "武汉",
  6. "departureTime": "2022-01-01T08:00:00.000Z",
  7. "createdAt": "2023-10-21T15:05:37.000Z",
  8. "updatedAt": "2023-10-23T14:44:16.000Z",
  9. "AirplaneId": 1,
  10. "BoardingTickets": {
  11. "createdAt": "2023-10-23T15:00:55.000Z",
  12. "updatedAt": "2023-10-23T15:00:55.000Z",
  13. "CustomerId": 1,
  14. "FlightScheduleId": 1
  15. }
  16. }
  17. ]

  如果不想返回中间表,或只想返回中间表的某些属性,get方法中,可以使用joinTableAttributes来进行指定,

  1. const flightSchedules = await customer.getFlightSchedules({
  2. joinTableAttributes: []
  3. });

  以上的查询称为 Lazy load, 先从一张表中查出数据,再从另外一张表中,查出数据,执行了两次query请求,那能不能一次性地把所有数据都查询出来,那就是Eager load,查询的时候,使用include,包含关联的表,连表查询。

  1. app.get('/', async (req, res) => {
  2. const customer = await models.Customer.findOne({
  3. where: { id: 1 },
  4. include: [{
  5. model: models.FlightSchedule, //连接FlightSchedules 表
  6. through: { attributes: [] } // 不需要中间表的数据
  7. }]
  8. });
  9.  
  10. res.send("<pre>" + JSON.stringify(customer, undefined,
  11. 4) + "</pre>");
  12. })

  一次查出了customer和它关联的 FlightSchedules。如果不使用默认外键,也可以自己定义,甚至外键不引用Id,引用其它字段,hasOne

  1. Actor.hasOne(Role, {
  2.     sourceKey: 'name',
  3.     foreignKey: 'actorName'
  4. });

  Role model多了一个actorName属性 ,因为hasOne定义外键在Role上,它的值引用ActorModel中的name属性,hasMany也是一样

  1. Roles.hasMany(Costumes, {
  2.     sourceKey: 'title',
  3.     foreignKey: 'roleTitle'
  4. });

  Costumes model 多了roleTitle属性, 它的值引用which will be associated with the role’s title。 belonsTo:

  1. Roles.belongsTo(Actors, {
  2.     targetKey: 'name',
  3.     foreignKey: 'actorName'
  4. });

  belongsToMany

  1. Costumes.belongsToMany(Actors, {
  2.     through: 'actor_costumes',
  3.     sourceKey: 'name',
  4.     targetKey: 'wardrobe'
  5. });

  actor_costumes 中间表建立,两个字段CostumesName 和 ActorWardrobe, 分别引用Actor 和 Costume model.

  事务:分为 unmanaged 事务和managed事务。 unmanaged事务,手动创建事务,提交或回滚事物

  1. app.get('/', async (req, res) => {
  2. const tx = await models.sequelize.transaction(); // 创建一个事务
  3. try {
  4. const plane = await models.Airplane.findByPk(2);
  5.  
  6. const schedule = await models.FlightSchedule.create({
  7. originAirport: '上海',
  8. destinationAirport: '北京',
  9. departureTime: '2023-10-24 10:10:00',
  10. }, { transaction: tx });
  11.  
  12. await schedule.setAirplane(plane, { transaction: tx });
  13.  
  14. await tx.commit(); // 提交事务
  15.  
  16. res.send("<pre>" + JSON.stringify(schedule, undefined,
  17. 4) + "</pre>");
  18. } catch (error) {
  19. await tx.rollback(); // 回滚事务
  20. }
  21. })

  managed transactions 就是自动提交和回滚事务。把要做的事性作为回调函数,传递给sequelize.transaction 就是managed transactions

  1. app.get('/', async (req, res) => {
  2. try {
  3. const plane = await models.Airplane.findByPk(3);
  4.  
  5. const flight = await models.sequelize.transaction(async (tx) => {
  6. const schedule = await models.FlightSchedule.create({
  7. originAirport: '上海',
  8. destinationAirport: '深圳',
  9. departureTime: '2023-10-24 10:10:00',
  10. }, { transaction: tx });
  11.  
  12. await schedule.setAirplane(plane, { transaction: tx });
  13.  
  14. return schedule
  15. })
  16.  
  17. // 在这里自动提交事务
  18. res.send("<pre>" + JSON.stringify(flight, undefined,
  19. 4) + "</pre>");
  20. } catch (error) {
  21. //在这里,事务已经回滚了
  22. }
  23. })

  日志: 当执行数据库操作时,默认会把执行的query在控制台打印出来,Sequelize 同时提供了几种不同的日志签名,就是函数签名,来实现自定义日志。

  1. function (msg) {}
  2. function (...msg) {}
  3. msg => someLogger.debug(msg)

  function (...msg) {}, 不仅记录query,还会记录其它元信息

  1. function multiLog(...msgs) {
  2.     msgs.forEach(function(msg) {
  3.         console.log(msg);
  4.     });
  5. }
  6. const sequelize = new Sequelize('sqlite::memory:', {
  7.     logging: multiLog
  8. });

  msg => someLogger.debug(msg) 可以让我们集成第三方日志库,比如Pino

  1. import pino from 'pino'
  2. const logger = pino();
  3.  
  4. const sequelize = new Sequelize('sqlite::memory:', {
  5. logging: (msg) => logger.info(msg)
  6. });

  再比如集成Bunyan

  1. import bunyan from 'bunyan'
  2. const logger = bunyan.createLogger({name: 'app'});
  3.  
  4. const sequelize = new Sequelize('sqlite::memory:', {
  5. logging: (msg) => logger.info(msg)
  6. });

   当然,也可以禁止输出日志,logging: false就可以了。

  收集metrics和数据是使用 OpenTelemetry, 这个暂时记录一下

  1. npm i @opentelemetry/api @opentelemetry/sdk-trace-node @
  2. opentelemetry/instrumentation @opentelemetry/sdk-node @
  3. opentelemetry/auto-instrumentations-node opentelemetry-
  4. instrumentation-sequelize

  在models的index.js中

  1. const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
  2. const { registerInstrumentations } = require('@opentelemetry/instrumentation');
  3. const { SequelizeInstrumentation } = require('opentelemetry-instrumentation-sequelize');
  4.  
  5. const tracerProvider = new NodeTracerProvider({
  6. plugins: {
  7. sequelize: {
  8. // disabling the default/old plugin is required
  9. enabled: false,
  10. path: 'opentelemetry-plugin-sequelize'
  11. }
  12. }
  13. });
  14.  
  15. registerInstrumentations({
  16. tracerProvider,
  17. instrumentations: [
  18. new SequelizeInstrumentation({
  19. // any custom instrument options here
  20. })
  21. ]
  22. });

  在项目根目录下,创建trace.js

  1. /* tracing.js */
  2.  
  3. // Require dependencies
  4. const opentelemetry = require("@opentelemetry/sdk-node");
  5. const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");
  6.  
  7. const sdk = new opentelemetry.NodeSDK({
  8. traceExporter: new opentelemetry.tracing.ConsoleSpanExporter(),
  9. instrumentations: [getNodeAutoInstrumentations()]
  10. });
  11.  
  12. sdk.start();

  node -r "./tracing.js" index.js 启动服务器,访问服务器,就可以看到日志输出。通常会把metrics数据放到一个收集器中,比如Zipkin, 在models 下的index.js  引入

  1. import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
    import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';

  然后在const tracerProvider = new NodeTracerProvider({}) 下面写

  1. tracerProvider.addSpanProcessor(new BatchSpanProcessor(new
  2.     ZipkinExporter()));

  对已经存在的数据库使用Sequelize,由于数据库已经存在,我们需要创建model来适配它的Schema。比如数据库有一张表是foo_bars, 它的属性是

  1. id INTEGER AUTOINCREMENT
  2. first_name VARCHAR(200)
  3. last_name VARCHAR(200)
  4. email VARCHAR(200)
  5. date_created DATETIME
  6. date_updated DATETIME

  没有createdAt 和UpdatedAt, 并且表名和字段名都使用下划线,所以在定义model的时候,都需要自定义

  1. {
  2. // options
  3. sequelize,
  4. modelName: 'FooBar',
  5. tableName: 'foo_bars',
  6. createdAt: 'date_created',
  7. updatedAt: 'date_updated',
  8. underscore: true,
  9. },

  第二个要注意的是如果没有表中没有使用id作为主键,需要在创建的model中删除id,

  1. class FooBar extends Model {}
  2. FooBar.removeAttribute('id');

Node.js 的ORM(Sequelize) 的使用的更多相关文章

  1. 用node.js实现ORM的一种思路

    ORM是O和R的映射.O代表面向对象,R代表关系型数据库.二者有相似之处同时也各有特色.就是因为这种即是又非的情况,才需要做映射的. 理想情况是,根据关系型数据库(含业务需求)的特点来设计数据库.同时 ...

  2. 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...

  3. [转]在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查

    本文转自:https://www.cnblogs.com/kongxianghai/p/5582661.html Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用Ja ...

  4. node+pm2+express+mysql+sequelize来搭建网站和写接口

    前面的话:在这里已经提到了安装node的方法,node是自带npm的.我在技术中会用es6去编写,然后下面会分别介绍node.pm2.express.mysql.sequelize.有少部分是摘抄大佬 ...

  5. Node.js ORM 框架 sequelize 实践

    最近在做团队的一个内部系统,这次使用的nodejs web框架是团队统一的hapi.js,而数据库依然是mysql,ORM 框架选用有着6000+ stars 的 sequelize.js,hapi- ...

  6. [转]Node.JS使用Sequelize操作MySQL

    Sequelize官方文档  https://sequelize.readthedocs.io/en/latest/ 本文转自:https://www.jianshu.com/p/797e10fe23 ...

  7. node.js使用Sequelize 操作mysql

    Sequelize就是Node上的ORM框架 ,相当于java端的Hibernate 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, SQLite ...

  8. node.js & ORM & ODM

    node.js & ORM & ODM ODM & NoSQL Object Data Modeling 对象数据模型 Object Document Mapping 对象文档 ...

  9. Node.js中的ORM

    ORM2是一款基于Node.js实现的ORM框架,名字相当的霸气,算是同类框架中非常出色的一款,具体介绍请猛击:https://github.com/dresende/node-orm2 刚接触Nod ...

  10. Node.js: What is the best "full stack web framework" (with scaffolding, MVC, ORM, etc.) based on Node.js / server-side JavaScript? - Quora

    Node.js: What is the best "full stack web framework" (with scaffolding, MVC, ORM, etc.) ba ...

随机推荐

  1. blazor优雅的方式导入组件相关的js脚本

    基本的组件导入方式为: 1 await JsRuntime.InvokeVoidAsync("import", $"XXXXX.js"); 优雅的组件导入方式: ...

  2. Ubuntu 20.04 安装和配置MySql5.7的详细教程

    Ubuntu 20.04 安装和配置MySql5.7的详细教程 https://www.jb51.net/article/202399.htm

  3. MindSpore反向传播配置关键字参数

    技术背景 在MindSpore深度学习框架中,我们可以向construct函数传输必备参数或者关键字参数,这跟普通的Python函数没有什么区别.但是对于MindSpore中的自定义反向传播bprop ...

  4. Vue3 项目

    创建 Vue3 项目的步骤如下: 安装 Node.js Vue3 需要依赖 Node.js 环境,因此需要先安装 Node.js.可以从官网下载 Node.js 的安装包并安装,也可以使用包管理器安装 ...

  5. C 语言编程 — 编程实践

    目录 文章目录 目录 前文列表 程序示例 前文列表 <程序编译流程与 GCC 编译器> <C 语言编程 - 基本语法> <C 语言编程 - 基本数据类型> < ...

  6. java学习之旅(day.01)

    Markdown学习 标题 一级标题:#空格+标题名字 二级标题:##空格+标题名字 三级标题:###空格+标题名字 字体 粗体:两边都加两个** Hello,world 斜体:两边都加一个* Hel ...

  7. js RGB转HSV

    function rgb2hsv (r,g,b) { var computedH = 0; var computedS = 0; var computedV = 0; //remove spaces ...

  8. Http 代理工具 实战 支持网页与QQ代理

    前言: 有些公司不让员工上Q或封掉某些网站,这时候,干着急没办法,只能鄱墙.如果上网搜代理IP,很少能用,用HTTP-Tunnel Client代理软件,免费的也是经常性的掉线.正好手头上有N台服务器 ...

  9. PaddleOCR在 Linux下的webAPI部署方案

    很多小伙伴在使用OCR时都希望能过采用API的方式调用,这样就可以跨端跨平台了.本文将介绍一种基于python的PaddleOCR识方案.喜欢的可以关注公众号,获取更多内容. 一. Linux环境下部 ...

  10. 《剑指offer3- 从末尾到头打印链表》

    题目描述 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList.   本质上是逆转链表   /** * struct ListNode { * int val; * struct ListN ...