什么是Sequelize

Sequelize 是一个基于promise的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。

简单说就是nodejs的ORM库,满足大部分SQL数据库。

安装

npm i sequelize

# 安装数据库驱动程序

npm i pg pg-hstore # PostgreSQL
npm i mysql2 # MySQL
npm i sqlite3 # SQLite
npm i tedious # Microsoft SQL Server
npm i ibm_db # DB2

连接数据库


const { Sequelize } = require("sequelize")

// Option 1: Passing a connection URI
const sequelize = new Sequelize('mysql://root:123456@localhost:3306/test')


//  Option 2: Passing parameters separately
const sequelize = new Sequelize({
  dialect: 'mysql', //指定连接的数据库类型
  host: 'localhost',
  username: 'root',
  password: '123456',
})

参数请参照API

测试连接


try {
  await sequelize.authenticate();
  console.log("连接成功")
} catch(error) {
  console.error('连接失败:', error);
}

关闭连接

通常sequelize一旦启用连接后就会一直保持连接,但很容易导致SQL负载,所以sequelize也提供了一个方式关闭连接,sequelize.close()

数据库编程大致过程:

  1. 建库和建表
  2. 配置数据库连接信息
  3. 建立数据库连接或数据库连接池
  4. 拿到数据库连接然后开启事务
  5. 执行数据库操作相关代码(如: select xxx)
  6. 提交事务
  7. 关闭数据库连接或将数据库连接归还到数据库连接池

支持引擎

EngineMinimum supported version
Postgre9.5
MySQL5.7
MariaDB10.1
Microsoft SQL?
SQLite3.0

使用V6需要注意数据库版本,否则你可能会得到报错:

(node:119744) [SEQUELIZE0006] DeprecationWarning: This database engine version is not supported, please update your database server. More information https://github.com/sequelize/...

日志

默认情况下,Sequelize会打印它执行的每一个SQL查询,可以通过传入自定义函数给option.log这个属性来来改变打印日志的行为。它的默认值是console.log,默认只显示日志函数的第一个参数。

const sequelize = new Sequelize('sqlite::memory:', {
  // Choose one of the logging options
  logging: console.log,                  // Default, displays the first parameter of the log function call
  logging: (...msg) => console.log(msg), // Displays all log function call parameters
  logging: false,                        // Disables logging
  logging: msg => logger.debug(msg),     // Use custom logger (e.g. Winston or Bunyan), displays the first parameter
  logging: logger.debug.bind(logger)     // Alternative way to use custom logger, displays all messages
});

获取器, 设置器 & 虚拟字段

获取器

获取器是为模型定义中的一列定义的 get() 函数:

const User = sequelize.define('user', {
  // 假设我们想要以大写形式查看每个用户名,
  // 即使它们在数据库本身中不一定是大写的
  username: {
    type: DataTypes.STRING,
    get() {
      const rawValue = this.getDataValue('username');
      return rawValue ? rawValue.toUpperCase() : null;
    }
  }
});

设置器

设置器是为模型定义中的一列定义的 set() 函数. 它接收要设置的值:

const User = sequelize.define('user', {
  username: DataTypes.STRING,
  password: {
    type: DataTypes.STRING,
    set(value) {
      // 在数据库中以明文形式存储密码是很糟糕的.
      // 使用适当的哈希函数来加密哈希值更好.
      this.setDataValue('password', hash(value));
    }
  }
});

虚拟字段

虚拟字段是 Sequelize 在后台填充的字段,但实际上它们不存在于数据库中.

例如,假设我们有一个 UserfirstName` 和 lastName` 属性.

如果有一种简单的方法能直接获取 全名 那会非常好! 我们可以将 getters 的概念与 Sequelize 针对这种情况提供的特殊数据类型结合使用:DataTypes.VIRTUAL:

const { DataTypes } = require("sequelize");

const User = sequelize.define('user', {
  firstName: DataTypes.TEXT,
  lastName: DataTypes.TEXT,
  fullName: {
    type: DataTypes.VIRTUAL,
    get() {
      return `${this.firstName} ${this.lastName}`;
    },
    set(value) {
      throw new Error('不要尝试设置 `fullName` 的值!');
    }
  }
});

关联

Sequelize 支持标准关联关系: 一对一, 一对多 和 多对多.

为此,Sequelize 提供了 四种 关联类型,并将它们组合起来以创建关联:

  • HasOne 关联类型
  • BelongsTo 关联类型
  • HasMany 关联类型
  • BelongsToMany 关联类型

一对一关系

// Sequelize 知道必须将 fooId 列添加到 Bar 中.
Foo.hasOne(Bar);
Bar.belongsTo(Foo);

可以按照生成sql去理解:
//  B  belongsTo A
// where B.foreignKey = A.primaryKey

// A hasOne B
// where A.primaryKey = B.foreignKey

二者必须同时配置,才可以互相调用查询。

  • 自定义外键
// 方法 1
Foo.hasOne(Bar, {
  foreignKey: 'myFooId',
  type: DataTypes.UUID
});
Bar.belongsTo(Foo, {
  foreignKey: 'myFooId',
  type: DataTypes.UUID // 可选
});

强制性与可选性关联

用的比较少,了解即可

Foo.hasOne(Bar, {
  foreignKey: {
    allowNull: false
  }
});

一对多关系

一个 Team 有 Player ,而每个 Player 都属于一个 Team.

Team.hasMany(Player);
Player.belongsTo(Team);

其余的参考一对一关系

多对多关系

多对多关系通常通过联结表来实现。

const Movie = sequelize.define('Movie', { name: DataTypes.STRING });
const Actor = sequelize.define('Actor', { name: DataTypes.STRING });
Movie.belongsToMany(Actor, { through: 'ActorMovies' });
Actor.belongsToMany(Movie, { through: 'ActorMovies' });

除了字符串以外,还支持直接传递模型,在这种情况下,给定的模型将用作联结模型(并且不会自动创建任何模型). 例如:

const Movie = sequelize.define('Movie', { name: DataTypes.STRING });
const Actor = sequelize.define('Actor', { name: DataTypes.STRING });
const ActorMovies = sequelize.define('ActorMovies', {
  MovieId: {
    type: DataTypes.INTEGER,
    references: {
      model: Movie, // 'Movies' 也可以使用
      key: 'id'
    }
  },
  ActorId: {
    type: DataTypes.INTEGER,
    references: {
      model: Actor, // 'Actors' 也可以使用
      key: 'id'
    }
  }
});
Movie.belongsToMany(Actor, { through: ActorMovies });
Actor.belongsToMany(Movie, { through: ActorMovies });

连接池

业务服务器与数据库提前建立好连接,并将这些连接一直保持,当有业务到来时,直接使用这些连接来处理业务,这样的技术叫连接池技术。使用连接池能够减少资源对象的创建次数,提⾼程序的响应性能,特别是在⾼并发下这种提⾼更加明显。比如在mysql连接池中,可以尽量避免连接三次握手四次挥手等非业务流程带来的损耗。池化技术,本质上说就是资源复用。

如果要从单个进程连接到数据库,则应仅创建一个 Sequelize 实例. Sequelize 将在初始化时建立连接池. 可以通过构造函数的 options 参数(使用 options.pool)来配置此连接池,如以下示例所示:


const sequelize = new Sequelize(/* ... */, {
  // ...
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
});

如果要从多个进程连接到数据库,则必须为每个进程创建一个实例,但是每个实例的最大连接池大小应达到最大总大小. 例如,如果你希望最大连接池大小为90,并且有三个进程,则每个进程的 Sequelize 实例的最大连接池大小应为30.

原始查询

由于常常使用简单的方式来执行原始/已经准备好的SQL查询,因此可以使用sequelize.query 方法.

默认情况下,函数将返回两个参数 - 一个结果数组,以及一个包含元数据(例如受影响的行数等)的对象. 请注意,由于这是一个原始查询,所以元数据都是具体的方言. 某些方言返回元数据 "within" 结果对象(作为数组上的属性). 但是,将永远返回两个参数,但对于MSSQL和MySQL,它将是对同一对象的两个引用.

const [results, metadata] = await sequelize.query("UPDATE users SET y = 42 WHERE x = 12");
// 结果将是一个空数组,元数据将包含受影响的行数.

在不需要访问元数据的情况下,你可以传递一个查询类型来告诉后续如何格式化结果. 例如,对于一个简单的选择查询你可以做:

const { QueryTypes } = require('sequelize');
const users = await sequelize.query("SELECT * FROM `users`", { type: QueryTypes.SELECT });
// 我们不需要在这里分解结果 - 结果会直接返回

替换(sql防注入)

查询中的替换可以通过两种不同的方式完成:使用命名参数(以:开头),或者由?表示的未命名参数. 替换在options对象中传递.

  • 如果传递一个数组, ? 将按照它们在数组中出现的顺序被替换
  • 如果传递一个对象, :key 将替换为该对象的键. 如果对象包含在查询中找不到的键,则会抛出异常,反之亦然.
const { QueryTypes } = require('sequelize');

await sequelize.query(
  'SELECT * FROM projects WHERE status = ?',
  {
    replacements: ['active'],
    type: QueryTypes.SELECT
  }
);

await sequelize.query(
  'SELECT * FROM projects WHERE status = :status',
  {
    replacements: { status: 'active' },
    type: QueryTypes.SELECT
  }
);

防止sql注入

如何格式化日期类型的值

Sequelize有2种与日期相关的类型:

  • DATEONLY -- 相当于SQL的DATE
  • DATE -- 相当于SQL的DATETIME

这2种类型使用ISO标准来格式化它们从SQL数据库发送和接收的值。

下面通过例子来查看这2个类型,假设你的数据库中有一个invoice表,列定义如下:

+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| id          | int      | NO   | PRI | NULL    | auto_increment |
| invoiceDate | date     | YES  |     | NULL    |                |
| paymentDate | datetime | YES  |     | NULL    |                |
| amount      | int      | YES  |     | NULL    |                |
+-------------+----------+------+-----+---------+----------------+

该表只有一行数据,如下图所示:

+----+-------------+---------------------+--------+
| id | invoiceDate | paymentDate         | amount |
+----+-------------+---------------------+--------+
|  1 | 2022-01-17  | 2022-01-17 04:33:12 |    300 |
+----+-------------+---------------------+--------+

我们使用Sequelize来定义这张表:

const Invoice = sequelize.define("Invoice", {
  invoiceDate: {
    type: Sequelize.DATEONLY,
  },
  paymentDate: {
    type: Sequelize.DATE,
  },
  amount: {
    type: Sequelize.INTEGER,
  },
},
{
  timestamps: false,
});

接下来就能简单查询到这条数据

const invoice = await Invoice.findByPk(1);

console.log(invoice.toJSON());

查询结果如下:

{
  id: 1,
  invoiceDate: '2022-01-17',
  paymentDate: 2022-01-17T04:33:12.000Z,
  amount: 300
}

注意:paymentDate值作为JavaScript Date对象返回,使用ISO格式YYYY-MM-DDTHH:MM:SSZ

当你需要改变返回值的格式时,你可以使用以下2种方法:

  • 使用sequelize.fn()方法调用数据库原生日期格式函数
  • 使用attributes.column.get()方法在JavaScript中格式化日期

sequelize.fn()方法用于调用本地数据库函数来修改查询的工作方式。

const invoices = await Invoice.findAll({
  attributes: {
    include: [
      "id",
      "invoiceDate",
      [
        sequelize.fn
        (
          "DATE_FORMAT", 
          sequelize.col("paymentDate"), 
          "%d-%m-%Y %H:%i:%s"
        ),
        "paymentDate",
      ],
      "amount",
    ],
  },
});

console.log(invoices[0].toJSON());

每个Sequelize模型属性都带有get()和set()方法,允许您为列提供自定义getter和setter。一般推荐这种做法,因为无需考虑数据库迁移的问题。

const Invoice = sequelize.define("Invoice", {
  invoiceDate: {
    type: Sequelize.DATEONLY,
  },
  paymentDate: {
    type: Sequelize.DATE,
    get: function() { // or use get(){ }
      return this.getDataValue('paymentDate')
        .toLocaleString('en-GB', { timeZone: 'UTC' });
    }
  },
  amount: {
    type: Sequelize.INTEGER,
  },
},
{
  timestamps: false,
});

此外,你可以使用任何JavaScript日期库来格式化返回的时间。

参考文章


看见了
840 声望13 粉丝

前端开发,略懂后台;