博客 / 詳情

返回

sequelize總結

什麼是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. 關閉數據庫連接或將數據庫連接歸還到數據庫連接池

支持引擎

Engine Minimum supported version
Postgre 9.5
MySQL 5.7
MariaDB 10.1
Microsoft SQL ?
SQLite 3.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日期庫來格式化返回的時間。

參考文章

  • 解決sequelize嵌套過深問題
  • 關於使用Sequelize進行數據庫連接/釋放方面的解惑
  • Sequelize的讀寫分離
  • sql防注入--sequelize
  • Sequelize - how to format the date for date types values
user avatar messchow 頭像 skipshow 頭像
2 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.