什麼是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()
數據庫編程大致過程:
- 建庫和建表
- 配置數據庫連接信息
- 建立數據庫連接或數據庫連接池
- 拿到數據庫連接然後開啓事務
- 執行數據庫操作相關代碼(如: select xxx)
- 提交事務
- 關閉數據庫連接或將數據庫連接歸還到數據庫連接池
支持引擎
| 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 在後台填充的字段,但實際上它們不存在於數據庫中.
例如,假設我們有一個 User 的 firstName` 和 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的DATEDATE-- 相當於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