Node.js 的ORM(Sequelize) 的使用
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,
- import express from 'express';
- const app = express();
- app.get('/', (req, res) => {
- res.send('Hello World')
- })
- app.listen(3000, () => {
- console.log("服务器启动成功");
- })
由于使用ES module,在package.json中,设置"type": "module",node server.js,项目启动成功,最简单的Node.js项目创建完成。npm install sequelize mysql2,就可以在项目中使用Sequelize来操作数据库了。操作数据库要先连接数据库,连接数据库就是创建Sequelize的实例, 在server.js中
- import { Sequelize } from 'sequelize';
- // new Sequelize(数据库名, 登录数据库时的用户名, 登录数据库时的密码, {host: 哪台主机上的数据库, dialect: 使用什么数据库})
- const sequelize = new Sequelize('airline', 'root', '123', {
- host: 'localhost',
- dialect: 'mysql'
- });
- try {
- await sequelize.authenticate();
- console.log('连接成功');
- } catch (error) {
- console.error('连接失败', error);
- }
连接成功,怎么操作数据库呢?Sequelize有一个model的概念,它代表数据库中的一张表,操作model就相当于操作数据库中的表,想要操作数据库哪张表,就要为哪张表创建一个model,因此使用Sequelize都是从创建model开始。有两种方式创建model,一种是sequelize.define(),另外一种是继承Model并调用 init()方法,无论哪种方式,创建Model时候,都要提供表名,以及表中的字段和数据类型,因为Model和表相关,代表的是一张表
- // 参数: model名, 属性(对应表中的字段), 可选配置项
- sequelize.define('FlightSchedule', {
- originAirport: DataTypes.STRING,
- destinationAirport: DataTypes.STRING,
- departureTime: DataTypes.DATE
- });
- // 或者
- class FlightSchedule extends Model { }
- // int参数:属性(对应表中的字段), 配置项
- FlightSchedule.init({
- originAirport: DataTypes.STRING,
- destinationAirport: DataTypes.STRING,
- departureTime: DataTypes.DATE
- }, {
- sequelize,
- });
你可能已经注意到了,创建FlightSchedule并没有提供表名,这是因为在默认情况下,Sequelize会对model名进行复数化,当作表名。FlightSchedule就会操作flightschedules表。当然,可以提供表名,也可以禁止复数化,还有就是,数据库的字段名也可以不用驼峰命名,而是使用 _连接,这些都可以在可选配置项中进行配置,
- {
- freezeTableName: true, // 表名是flightschedule
- tableName: 'a', // 直接定义表名为a
- underscored: true // 数据库中的对应的字段是origin_airport
- }
这里就不配置了,使用默认值就好,可以操作表了,但数据库中表不存在怎么办?Sequelize提供了一个sync()方法,如果数据库中没有对应的表,它就会创建表,如果有表,那就什么都不做,整个index.js如下
- import express from 'express';
- import { Sequelize, DataTypes } from 'sequelize';
- const app = express();
- const sequelize = new Sequelize('airline', 'root', '123', {
- host: 'localhost',
- dialect: 'mysql'
- });
- sequelize.define('FlightSchedule', {
- originAirport: DataTypes.STRING,
- destinationAirport: DataTypes.STRING,
- departureTime: DataTypes.DATE
- });
- await sequelize.sync();
- app.get('/', (req, res) => {
- res.send('Hello World')
- })
- app.listen(3000, () => {
- console.log("server start");
- })
重新启动服务器,查看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的定义放到里面
- export default (sequelize, DataTypes) => {
- return sequelize.define('FlightSchedule', {
- originAirport: DataTypes.STRING,
- destinationAirport: DataTypes.STRING,
- departureTime: DataTypes.DATE
- });
- };
然后在index.js
- import { Sequelize, DataTypes } from 'sequelize';
- import { createRequire } from 'module'; // 在esm中使用require,处理JSON
- import createFlightSchedule from './flightSchedule.js' // 引入model
- const env = process.env.NODE_ENV || 'development';
- const require = createRequire(import.meta.url);
- const config = require('../config/config.json')[env]
- const sequelize = new Sequelize(config.database, config.username, config.password, config);
- const FlightSchedule = createFlightSchedule(sequelize, DataTypes) // 创建Model
- export default {
- FlightSchedule,
- sequelize
- }
可以看到,默认情况下,连接的是development数据库,在config.json中配置development
- "development": {
- "username": "root",
- "password": "123",
- "database": "airline",
- "host": "127.0.0.1",
- "dialect": "mysql"
- }
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个属性
- /** @type {import('sequelize-cli').Migration} */
- module.exports = {
- async up(queryInterface, Sequelize) {
- await queryInterface.createTable('FlightSchedules', {
- id: {
- allowNull: false,
- autoIncrement: true,
- primaryKey: true,
- type: Sequelize.INTEGER
- },
- originAirport: {
- type: Sequelize.STRING
- },
- destinationAirport: {
- type: Sequelize.STRING
- },
- departureTime: {
- type: Sequelize.DATE
- },
- createdAt: {
- allowNull: false,
- type: Sequelize.DATE
- },
- updatedAt: {
- allowNull: false,
- type: Sequelize.DATE
- }
- });
- },
- async down(queryInterface, Sequelize) {
- await queryInterface.dropTable('FlightSchedules');
- }
- };
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(…) 回滚数据。
- /** @type {import('sequelize-cli').Migration} */
- module.exports = {
- async up(queryInterface, Sequelize) {
- await queryInterface.bulkInsert('FlightSchedules', [{
- originAirport: "济南",
- destinationAirport: "武汉",
- departureTime: "2022-01-01 08:00:00",
- createdAt: new Date(),
- updatedAt: new Date(),
- }], {});
- },
- async down(queryInterface, Sequelize) {
- await queryInterface.bulkDelete('FlightSchedules', null, {});
- }
- };
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
- export default (sequelize, DataTypes) => {
- return sequelize.define('Airplane', {
- planeModel: DataTypes.STRING,
- totalSeats: DataTypes.STRING,
- });
- };
再在其index.js 引入并创建Airplane model
- import createAirplane from './airplane.js';
- const Airplane = createAirplane(sequelize, DataTypes)
- export default {
- Airplane,
- FlightSchedule,
- sequelize
- }
创建migration, npx sequelize migration:generate --name create-airplane, 不要忘了把生成的文件后缀名改为.cjs,
- module.exports = {
- async up(queryInterface, Sequelize) {
- await queryInterface.createTable('Airplanes', {
- id: {
- allowNull: false,
- autoIncrement: true,
- primaryKey: true,
- type: Sequelize.INTEGER
- },
- planeModel: {
- type: Sequelize.STRING
- },
- totalSeats: {
- type: Sequelize.INTEGER
- },
- createdAt: {
- allowNull: false,
- type: Sequelize.DATE
- },
- updatedAt: {
- allowNull: false,
- type: Sequelize.DATE
- }
- });
- },
- async down(queryInterface, Sequelize) {
- await queryInterface.dropTable('Airplanes');
- }
- };
npx sequelize db:migrate 执行migration,可以再创建一个seeder文件,填充数据库中的数据。npx sequelize seed:generate --name initial-airplanes
- module.exports = {
- async up(queryInterface, Sequelize) {
- await queryInterface.bulkInsert('Airplanes', [{
- planeModel: 'Airbus A220-100',
- totalSeats: 110,
- createdAt: new Date(),
- updatedAt: new Date()
- }, {
- planeModel: 'Airbus A220-300',
- totalSeats: 110,
- createdAt: new Date(),
- updatedAt: new Date()
- }, {
- planeModel: 'Airbus A 318',
- totalSeats: 115,
- createdAt: new Date(),
- updatedAt: new Date()
- }, {
- planeModel: 'Boeing 707-100',
- totalSeats: 100,
- createdAt: new Date(),
- updatedAt: new Date(),
- }, {
- planeModel: 'Boeing 737-100',
- totalSeats: 85,
- createdAt: new Date(),
- updatedAt: new Date()
- }], {});
- },
- async down(queryInterface, Sequelize) {
- await queryInterface.bulkDelete('Airplanes', null, {});
- }
- };
npx sequelize db:seed --seed=20231017074324-initial-airplanes.cjs 向数据库中插入数据。再创建一个customer,在models目录下,创建customer.js
- export default (sequelize, DataTypes) => {
- return sequelize.define('Customer', {
- name: DataTypes.STRING,
- email: DataTypes.STRING,
- });
- }
然后index.js中引入
- import createCustomer from './customer.js';
- const Customer = createCustomer(sequelize, DataTypes);
- export default {
- Airplane,
- Customer,
- FlightSchedule,
- sequelize
- }
最后生成一个migration文件,npx sequelize migration:generate --name create-customer
- module.exports = {
- async up(queryInterface, Sequelize) {
- await queryInterface.createTable('Customers', {
- id: {
- allowNull: false,
- autoIncrement: true,
- primaryKey: true,
- type: Sequelize.INTEGER
- },
- name: {
- type: Sequelize.STRING
- },
- email: {
- type: Sequelize.STRING
- },
- createdAt: {
- allowNull: false,
- type: Sequelize.DATE
- },
- updatedAt: {
- allowNull: false,
- type: Sequelize.DATE
- }
- });
- },
- async down(queryInterface, Sequelize) {
- queryInterface.dropTable('Customers');
- }
- };
npx sequelize db:migrate执行migration。现在server.js
- import express from 'express';
- import models from './models/index.js' // 引入model
- const app = express();
- // 由于执行mirgation,只要成功连接数据库,就能操作数据库,表一定会存在
- await models.sequelize.authenticate();
- app.get('/', (req, res) => {
- res.send('Hello World')
- })
- app.listen(3000, () => {
- console.log("server start");
- })
增删改查
新增一条记录:model名.create(),它接受一个对象作为参数,对象的属性就是model中定义的属性,返回一个model的实例,
- app.get('/', async (req, res) => {
- var record = await models.Customer.create({name: 'Sam', email: '123@qq.com'});
- res.send(JSON.stringify(record))
- })
启动服务器,localhost:3000,数据库中成功插入一条数据,同时页面显示新创建的customer。create()创建model实例的时候,只需要操作创建Model时定义的属性,Sequelize自动添加的属性,它自己会处理好。这时可以npm i nodemon -D, npx nodemon server.js启动服务器,保证服务器一直处于启动状态。新增多条记录(批量插入):model名.bulkCreate(),它接受的是数组,数组的每一个元素是对象,对象和create()接受的对象一样
- app.get('/', async (req, res) => {
- var record = await models.Customer.bulkCreate([
- {name: '张三', email: '456@qq.com'},
- {name: '李四', email: '789@qq.com'},
- ])
- res.send(JSON.stringify(record))
- })
删除:如果只是删除一条记录,可以先model名.findOne() 查出这个实例, 然后再在实例上调用destroy()方法
- app.get('/', async (req, res) => {
- var record = await models.Customer.findOne({ where: { id: 3 } });
- await record.destroy();
- res.send(JSON.stringify(record))
- })
如果想要一次删除多条记录,model名.destroy(),destroy的参数就是用来筛选要删除的记录。
- app.get('/', async (req, res) => {
- var record = await models.Customer.destroy({ where: { id: 2 } });
- res.send(JSON.stringify(record))
- })
删除还有硬删除和软删除之分。在创建model的时候,如果配置了paranoid: true,就表示软删除,删除的时候,不会真正的从数据库中删除,而是model上添加deleteAt字段,当然前提是timestamps 设为true,此时要硬删除,就需要在调用destroy方法时,添加force: true。如果软删除,在migration的时候,要在表中创建deleteAt字段。
- await Post.destroy({
- where: {
- id: 1
- },
- force: true
- });
更新:更新多条记录,使用model名.update(),第一个参数是要update成什么, 第二个参数是查询条件
- app.get('/', async (req, res) => {
- var record = await models.Customer.update(
- { name: '张三', email: '456@qq.com' },
- { where: { id: 1 } }
- );
- res.send(JSON.stringify(record))
- })
如果只想更新一条记录,先调用model名.findOne()获取到实例,然后修改实例的属性,最后调用实例上的save()
- app.get('/', async (req, res) => {
- var record = await models.Customer.findOne(
- { where: { id: 1 } }
- );
- record.name = 'Sam'
- await record.save()
- res.send(JSON.stringify(record))
- })
query:modal名.方法名,比如findAll,findone。
- app.get('/', async (req, res) => {
- const airplances = await models.Airplane.findAll();
- res.send("<pre>" + JSON.stringify(airplances, undefined,
- 4) + "</pre>");
- })
查询方法中,参数中带有attributes,表示只查询表中的某个或某些字段,而不是全部字段。它是一个数组,把要查询的字段列出来
- app.get('/', async (req, res) => {
- const airplances = await models.Airplane.findAll({
- attributes: ['planeModel', 'totalSeats']
- });
- res.send("<pre>" + JSON.stringify(airplances, undefined,
- 4) + "</pre>");
- })
如果对字段进行重命名,数组中的元素需要是一个数组,它的第一项是原字段名,第二项是新字段名。
- const airplances = await models.Airplane.findAll({
- attributes: [
- 'planeModel',
- ['totalSeats', 'seats'], // totalSeats 重命名为 seats
- ]
- });
查询方法中,参数中带有where是条件查询,为此Sequelize还专门提供了Op。
- import { Op } from 'sequelize';
- app.get('/', async (req, res) => {
- const airplances = await models.Airplane.findAll({
- where: {
- id: {
- [Op.or]: [1, 2]
- }
- }
- });
- res.send("<pre>" + JSON.stringify(airplances, undefined,
- 4) + "</pre>");
- })
如果where查询语句中调用函数,要用sequelize.fn 来定义函数,sequelize.col来指定对哪一个属性来进行函数操作。sequelize.where 来表示相等性。
- const airplances = await models.Airplane.findAll({
- where: models.sequelize.where(
- models.sequelize.fn('char_length', models.sequelize.col('planeModel')),
- 5)
- });
sequelize.where 的第二个参数是基本类型,它就进行相等比较。如果要进行其它比较,可以是个对象,对象的属性,就果定义什么比较
- app.get('/', async (req, res) => {
- const airplances = await models.Airplane.findAll({
- where: models.sequelize.where(
- models.sequelize.fn('char_length', models.sequelize.col('planeModel')),
- {
- [Op.gt]: 12
- })
- });
- res.send("<pre>" + JSON.stringify(airplances, undefined,
- 4) + "</pre>");
- })
查询方法中,带有order,就是排序,它是一个数组,数组的每一项也是一个数组,数组的第一项指定按哪个字段进行排序,第二项指定按升序还是降序进行排序
- app.get('/', async (req, res) => {
- const airplances = await models.Airplane.findAll({
- order: [
- ['planeModel', 'DESC']
- ]
- });
- res.send("<pre>" + JSON.stringify(airplances, undefined,
- 4) + "</pre>");
- })
查询方法中,带有group,就是分组,指定按哪一个字段进行分组。分组之后,通常使用聚合函数,聚合函数的实现是使用sequelize.fn,它的第一个参数是使用哪个聚合函数(字符串),第二个参数是对哪个字段进行聚合。通常聚合函数要进行重命名,所以聚合函数是数组的第一项,新的名字是第二项,然后整个数组放到attributes数组中,
- app.get('/', async (req, res) => {
- const airplances = await models.Airplane.findAll({
- attributes: ['planeModel', [models.sequelize.fn('sum', models.sequelize.col('totalSeats')), 'totalSeatsCount']],
- group: 'planeModel'
- });
- res.send("<pre>" + JSON.stringify(airplances, undefined,
- 4) + "</pre>");
- })
查询方法中,带有offset和limit,就是用于分页
- app.get('/', async (req, res) => {
- const airplances = await models.Airplane.findAll({
- offset: 1, limit: 2
- });
- res.send("<pre>" + JSON.stringify(airplances, undefined,
- 4) + "</pre>");
- })
关系
在关系型数据库中,表与表之间存在1对1,1对多,和多对多的关系,那在Sequelize中,怎么用model来表示这些关系?每一个model都有四个方法,hasOne, BelongsTo, hasMany, BelongsToMany。由于关系是相对的,所以每一种关系都用两个方法实现。
实现1对1,用hasOne和BelongsTo。比如飞机(Airplanes)和飞机详情表(AirplaneDetails)
- Airplanes.hasOne(AirplaneDetails)
- AirplaneDetails.BelongsTo(Airplanes)
由于表与表之间的关系是通过外键实现的,所以默认情况下,hasOne方法会把外键放到它的参数上,也就是AirplaneDetails model中,外键的列名它所引用的model名+Id,AirplaneId, 除非外键已经存在,AirplaneDetails model 多了一个AirplaneId属性,
实现1对多,用hasMany 和belongsTo。比如 一架飞机可以执行多次航班(hasMany),但一个航班只能用一架飞机(BelongsTo)。
- Airplane.hasMany(FlightSchedule)
- FlightSchedule.belongsTo(Airplane)
1对多的关系,外键放到多的那一边,所以FlightSchedule多了AirplaneId属性。实现多对多要用BelongsToMany,因为多对多的关系,需要中间表,所以它还要一个through参数来指定使用哪张表,如果参数是字符串,Sequelize默认表中字段名是两个关联model的名字+id。比如Customer 和FlightSchedule,一个乘客可以乘多趟航班,一趟航班有多个客人,它们之间的关联是购买的飞机票
- Customer.belongsToMany(FlightSchedule, { through: 'BoardingTickets' });
- FlightSchedule.belongsToMany(Customer, { through: 'BoardingTickets' });
默认情况下,Sequelize会操作BoardingTickets表,表中有FlightScheduleId和CustomerId字段。参数还可以一个model,
- const BoardingTickets = sequelize.define('BoardingTickets', {
- FlightScheduleId: {
- type: DataTypes.INTEGER,
- references: {
- model: FlightSchedule,
- key: 'id'
- }
- },
- CustomerId: {
- type: DataTypes.INTEGER,
- references: {
- model: 'Customers', // 字符串是指表名
- key: 'id'
- }
- },
- // SomeOtherColumn: { // 可以添加其它字段
- // type: DataTypes.STRING
- // }
- });
- Customer.belongsToMany(FlightSchedule, { through: 'BoardingTickets' });
- FlightSchedule.belongsToMany(Customer, { through: 'BoardingTickets' });
两种方式是一样的,在models的index.js下面,在 export default前面,
- Airplane.hasMany(FlightSchedule)
- FlightSchedule.belongsTo(Airplane)
- Customer.belongsToMany(FlightSchedule, { through: 'BoardingTickets' });
- FlightSchedule.belongsToMany(Customer, { through: 'BoardingTickets' });
为了满足关系,需要做migration,首先创建BoardingTickets, npx sequelize migration:generate --name create-board-ticket
- module.exports = {
- async up(queryInterface, Sequelize) {
- await queryInterface.createTable('BoardingTickets', {
- id: {
- allowNull: false,
- autoIncrement: true,
- primaryKey: true,
- type: Sequelize.INTEGER
- },
- createdAt: {
- allowNull: false,
- type: Sequelize.DATE
- },
- updatedAt: {
- allowNull: false,
- type: Sequelize.DATE
- },
- CustomerId: {
- type: Sequelize.INTEGER,
- references: {
- model: 'Customers',
- key: 'id'
- },
- onUpdate: 'set null',
- onDelete: 'cascade'
- },
- FlightScheduleId: {
- type: Sequelize.INTEGER,
- references: {
- model: 'FlightSchedules',
- field: 'id'
- },
- onDelete: 'set null',
- onUpdate: 'cascade'
- }
- });
- },
- async down(queryInterface, Sequelize) {
- await queryInterface.dropTable('BoardingTickets');
- }
- };
然后给FlightSchedules 添加外键,npx sequelize migration:generate --name add-flightschedules-references
- module.exports = {
- async up (queryInterface, Sequelize) {
- await queryInterface.addColumn('FlightSchedules', 'AirplaneId', {
- type: Sequelize.INTEGER,
- });
- await queryInterface.addConstraint('FlightSchedules', {
- type: 'foreign key',
- fields: ['AirplaneId'],
- references: {
- table: 'Airplanes',
- field: 'id'
- },
- name: 'fkey_flight_schedules_airplane',
- onDelete: 'set null',
- onUpdate: 'cascade'
- });
- },
- async down (queryInterface, Sequelize) {
- await queryInterface.removeConstraint(
- 'FlightSchedules', 'fkey_flight_schedules_airplane'
- );
- await queryInterface.removeColumn('FlightSchedules', 'AirplaneId');
- }
- };
执行迁移
- sequelize db:migrate
当model之间建立联系后,每一个model实例多了许多方法,可以使用一个model实例去操作另外一个model。比如找到一个airplane,就可以找到相对应的flight schedue,所以airplane的实例,就有get fligt shcedule 的方法。当有一个airplane时,add一个flight schedule,两者就建立了联系,甚至,一个airplane 都有create flight shcedule 的功能,创建和建立联系一起作了。
- app.get('/', async (req, res) => {
- const airplance = await models.Airplane.findOne({
- where: { id: 1 }
- });
- const flightSchedule = await models.FlightSchedule.findOne({
- where: { id: 1 }
- })
- // 数据库中 flightSchedule表中,id=1的 AirplaneId 变成了 '1'
- await airplance.addFlightSchedule(flightSchedule)
- // 创建了一条flightSchedule记录,并且它的AirplaneId也是1
- await airplance.createFlightSchedule({
- originAirport: "深圳",
- destinationAirport: "武汉",
- departureTime: "2023-10-01 20:00:00"
- })
- const flightSchedules = await models.FlightSchedule.findAll();
- res.send("<pre>" + JSON.stringify(flightSchedules, undefined,
- 4) + "</pre>");
- })
当model之间的关系是多对多,在一个model实例上执行get另一个model时,会把中间表也查出来
- app.get('/', async (req, res) => {
- const customer = await models.Customer.findOne({
- where: { id: 1 }
- });
- const flightSchedules = await customer.getFlightSchedules();
- res.send("<pre>" + JSON.stringify(flightSchedules, undefined,
- 4) + "</pre>");
- })
页面展示:
- [
- {
- "id": 1,
- "originAirport": "济南",
- "destinationAirport": "武汉",
- "departureTime": "2022-01-01T08:00:00.000Z",
- "createdAt": "2023-10-21T15:05:37.000Z",
- "updatedAt": "2023-10-23T14:44:16.000Z",
- "AirplaneId": 1,
- "BoardingTickets": {
- "createdAt": "2023-10-23T15:00:55.000Z",
- "updatedAt": "2023-10-23T15:00:55.000Z",
- "CustomerId": 1,
- "FlightScheduleId": 1
- }
- }
- ]
如果不想返回中间表,或只想返回中间表的某些属性,get方法中,可以使用joinTableAttributes来进行指定,
- const flightSchedules = await customer.getFlightSchedules({
- joinTableAttributes: []
- });
以上的查询称为 Lazy load, 先从一张表中查出数据,再从另外一张表中,查出数据,执行了两次query请求,那能不能一次性地把所有数据都查询出来,那就是Eager load,查询的时候,使用include,包含关联的表,连表查询。
- app.get('/', async (req, res) => {
- const customer = await models.Customer.findOne({
- where: { id: 1 },
- include: [{
- model: models.FlightSchedule, //连接FlightSchedules 表
- through: { attributes: [] } // 不需要中间表的数据
- }]
- });
- res.send("<pre>" + JSON.stringify(customer, undefined,
- 4) + "</pre>");
- })
一次查出了customer和它关联的 FlightSchedules。如果不使用默认外键,也可以自己定义,甚至外键不引用Id,引用其它字段,hasOne
- Actor.hasOne(Role, {
- sourceKey: 'name',
- foreignKey: 'actorName'
- });
Role model多了一个actorName属性 ,因为hasOne定义外键在Role上,它的值引用ActorModel中的name属性,hasMany也是一样
- Roles.hasMany(Costumes, {
- sourceKey: 'title',
- foreignKey: 'roleTitle'
- });
Costumes model 多了roleTitle属性, 它的值引用which will be associated with the role’s title。 belonsTo:
- Roles.belongsTo(Actors, {
- targetKey: 'name',
- foreignKey: 'actorName'
- });
belongsToMany
- Costumes.belongsToMany(Actors, {
- through: 'actor_costumes',
- sourceKey: 'name',
- targetKey: 'wardrobe'
- });
actor_costumes 中间表建立,两个字段CostumesName 和 ActorWardrobe, 分别引用Actor 和 Costume model.
事务:分为 unmanaged 事务和managed事务。 unmanaged事务,手动创建事务,提交或回滚事物
- app.get('/', async (req, res) => {
- const tx = await models.sequelize.transaction(); // 创建一个事务
- try {
- const plane = await models.Airplane.findByPk(2);
- const schedule = await models.FlightSchedule.create({
- originAirport: '上海',
- destinationAirport: '北京',
- departureTime: '2023-10-24 10:10:00',
- }, { transaction: tx });
- await schedule.setAirplane(plane, { transaction: tx });
- await tx.commit(); // 提交事务
- res.send("<pre>" + JSON.stringify(schedule, undefined,
- 4) + "</pre>");
- } catch (error) {
- await tx.rollback(); // 回滚事务
- }
- })
managed transactions 就是自动提交和回滚事务。把要做的事性作为回调函数,传递给sequelize.transaction 就是managed transactions
- app.get('/', async (req, res) => {
- try {
- const plane = await models.Airplane.findByPk(3);
- const flight = await models.sequelize.transaction(async (tx) => {
- const schedule = await models.FlightSchedule.create({
- originAirport: '上海',
- destinationAirport: '深圳',
- departureTime: '2023-10-24 10:10:00',
- }, { transaction: tx });
- await schedule.setAirplane(plane, { transaction: tx });
- return schedule
- })
- // 在这里自动提交事务
- res.send("<pre>" + JSON.stringify(flight, undefined,
- 4) + "</pre>");
- } catch (error) {
- //在这里,事务已经回滚了
- }
- })
日志: 当执行数据库操作时,默认会把执行的query在控制台打印出来,Sequelize 同时提供了几种不同的日志签名,就是函数签名,来实现自定义日志。
- function (msg) {}
- function (...msg) {}
- msg => someLogger.debug(msg)
function (...msg) {}, 不仅记录query,还会记录其它元信息
- function multiLog(...msgs) {
- msgs.forEach(function(msg) {
- console.log(msg);
- });
- }
- const sequelize = new Sequelize('sqlite::memory:', {
- logging: multiLog
- });
msg => someLogger.debug(msg) 可以让我们集成第三方日志库,比如Pino
- import pino from 'pino'
- const logger = pino();
- const sequelize = new Sequelize('sqlite::memory:', {
- logging: (msg) => logger.info(msg)
- });
再比如集成Bunyan
- import bunyan from 'bunyan'
- const logger = bunyan.createLogger({name: 'app'});
- const sequelize = new Sequelize('sqlite::memory:', {
- logging: (msg) => logger.info(msg)
- });
当然,也可以禁止输出日志,logging: false就可以了。
收集metrics和数据是使用 OpenTelemetry, 这个暂时记录一下
- npm i @opentelemetry/api @opentelemetry/sdk-trace-node @
- opentelemetry/instrumentation @opentelemetry/sdk-node @
- opentelemetry/auto-instrumentations-node opentelemetry-
- instrumentation-sequelize
在models的index.js中
- const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
- const { registerInstrumentations } = require('@opentelemetry/instrumentation');
- const { SequelizeInstrumentation } = require('opentelemetry-instrumentation-sequelize');
- const tracerProvider = new NodeTracerProvider({
- plugins: {
- sequelize: {
- // disabling the default/old plugin is required
- enabled: false,
- path: 'opentelemetry-plugin-sequelize'
- }
- }
- });
- registerInstrumentations({
- tracerProvider,
- instrumentations: [
- new SequelizeInstrumentation({
- // any custom instrument options here
- })
- ]
- });
在项目根目录下,创建trace.js
- /* tracing.js */
- // Require dependencies
- const opentelemetry = require("@opentelemetry/sdk-node");
- const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");
- const sdk = new opentelemetry.NodeSDK({
- traceExporter: new opentelemetry.tracing.ConsoleSpanExporter(),
- instrumentations: [getNodeAutoInstrumentations()]
- });
- sdk.start();
node -r "./tracing.js" index.js 启动服务器,访问服务器,就可以看到日志输出。通常会把metrics数据放到一个收集器中,比如Zipkin, 在models 下的index.js 引入
- import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
然后在const tracerProvider = new NodeTracerProvider({}) 下面写
- tracerProvider.addSpanProcessor(new BatchSpanProcessor(new
- ZipkinExporter()));
对已经存在的数据库使用Sequelize,由于数据库已经存在,我们需要创建model来适配它的Schema。比如数据库有一张表是foo_bars, 它的属性是
- id INTEGER AUTOINCREMENT
- first_name VARCHAR(200)
- last_name VARCHAR(200)
- email VARCHAR(200)
- date_created DATETIME
- date_updated DATETIME
没有createdAt 和UpdatedAt, 并且表名和字段名都使用下划线,所以在定义model的时候,都需要自定义
- {
- // options
- sequelize,
- modelName: 'FooBar',
- tableName: 'foo_bars',
- createdAt: 'date_created',
- updatedAt: 'date_updated',
- underscore: true,
- },
第二个要注意的是如果没有表中没有使用id作为主键,需要在创建的model中删除id,
- class FooBar extends Model {}
- FooBar.removeAttribute('id');
Node.js 的ORM(Sequelize) 的使用的更多相关文章
- 用node.js实现ORM的一种思路
ORM是O和R的映射.O代表面向对象,R代表关系型数据库.二者有相似之处同时也各有特色.就是因为这种即是又非的情况,才需要做映射的. 理想情况是,根据关系型数据库(含业务需求)的特点来设计数据库.同时 ...
- 在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查
Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用JavaScript开发并且能够用在Node.JS环境中,易于使用,支持多SQL方言(dialect),.它当前支持M ...
- [转]在node.js中,使用基于ORM架构的Sequelize,操作mysql数据库之增删改查
本文转自:https://www.cnblogs.com/kongxianghai/p/5582661.html Sequelize是一个基于promise的关系型数据库ORM框架,这个库完全采用Ja ...
- node+pm2+express+mysql+sequelize来搭建网站和写接口
前面的话:在这里已经提到了安装node的方法,node是自带npm的.我在技术中会用es6去编写,然后下面会分别介绍node.pm2.express.mysql.sequelize.有少部分是摘抄大佬 ...
- Node.js ORM 框架 sequelize 实践
最近在做团队的一个内部系统,这次使用的nodejs web框架是团队统一的hapi.js,而数据库依然是mysql,ORM 框架选用有着6000+ stars 的 sequelize.js,hapi- ...
- [转]Node.JS使用Sequelize操作MySQL
Sequelize官方文档 https://sequelize.readthedocs.io/en/latest/ 本文转自:https://www.jianshu.com/p/797e10fe23 ...
- node.js使用Sequelize 操作mysql
Sequelize就是Node上的ORM框架 ,相当于java端的Hibernate 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, SQLite ...
- node.js & ORM & ODM
node.js & ORM & ODM ODM & NoSQL Object Data Modeling 对象数据模型 Object Document Mapping 对象文档 ...
- Node.js中的ORM
ORM2是一款基于Node.js实现的ORM框架,名字相当的霸气,算是同类框架中非常出色的一款,具体介绍请猛击:https://github.com/dresende/node-orm2 刚接触Nod ...
- 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 ...
随机推荐
- blazor优雅的方式导入组件相关的js脚本
基本的组件导入方式为: 1 await JsRuntime.InvokeVoidAsync("import", $"XXXXX.js"); 优雅的组件导入方式: ...
- Ubuntu 20.04 安装和配置MySql5.7的详细教程
Ubuntu 20.04 安装和配置MySql5.7的详细教程 https://www.jb51.net/article/202399.htm
- MindSpore反向传播配置关键字参数
技术背景 在MindSpore深度学习框架中,我们可以向construct函数传输必备参数或者关键字参数,这跟普通的Python函数没有什么区别.但是对于MindSpore中的自定义反向传播bprop ...
- Vue3 项目
创建 Vue3 项目的步骤如下: 安装 Node.js Vue3 需要依赖 Node.js 环境,因此需要先安装 Node.js.可以从官网下载 Node.js 的安装包并安装,也可以使用包管理器安装 ...
- C 语言编程 — 编程实践
目录 文章目录 目录 前文列表 程序示例 前文列表 <程序编译流程与 GCC 编译器> <C 语言编程 - 基本语法> <C 语言编程 - 基本数据类型> < ...
- java学习之旅(day.01)
Markdown学习 标题 一级标题:#空格+标题名字 二级标题:##空格+标题名字 三级标题:###空格+标题名字 字体 粗体:两边都加两个** Hello,world 斜体:两边都加一个* Hel ...
- js RGB转HSV
function rgb2hsv (r,g,b) { var computedH = 0; var computedS = 0; var computedV = 0; //remove spaces ...
- Http 代理工具 实战 支持网页与QQ代理
前言: 有些公司不让员工上Q或封掉某些网站,这时候,干着急没办法,只能鄱墙.如果上网搜代理IP,很少能用,用HTTP-Tunnel Client代理软件,免费的也是经常性的掉线.正好手头上有N台服务器 ...
- PaddleOCR在 Linux下的webAPI部署方案
很多小伙伴在使用OCR时都希望能过采用API的方式调用,这样就可以跨端跨平台了.本文将介绍一种基于python的PaddleOCR识方案.喜欢的可以关注公众号,获取更多内容. 一. Linux环境下部 ...
- 《剑指offer3- 从末尾到头打印链表》
题目描述 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList. 本质上是逆转链表 /** * struct ListNode { * int val; * struct ListN ...