在後端服務應用中,咱們經常會用到關係型數據庫,可是在面向對象的語言代碼邏輯裏,數據都是用對象的形式表示的,因此當操做數據庫的時候,須要將內存的對象序列化後存儲到數據庫中,或者在讀取數據庫數據的時候,須要將讀取的數據反序列化爲對象,而這種就面向對象與關係數據庫互不匹配的現象使得開發和維護變得更加困難,而 對象關係映射(Object Relational Mapping,簡稱ORM)就是解決這種問題的技術,ORM 經過一箇中間層將程序中的對象自動持久化到關係數據庫中,本文咱們就來學習一下基於 Promise 的對象映射框架 Sequelize,本文主要包含如下幾部份內容:javascript
引用官網介紹html
Sequelize 給 Node.js v4 版本之上 提供的一個基於 Promise 的對象關係映射技術的庫,同時,也支持 PostgreSQL,MySQL,SQLite,MSSQL 等數據庫,支持事務,表關聯,讀複製等功能。java
可見在 Node.js 環境中使用 Sequelize 是再天然不過的事了,接下來就經過一些簡單的例子來學習一個 Sequelize 的使用。node
注意: 這裏省略了 MySql 的安裝和配置mysql
第一步,對設置數據庫的配置git
/* * config/db.js */
module.exports = {
name: 'test', // 數據庫名稱,
username: 'root', // 用戶名,
password: '123456', // 密碼
option: {
host: 'localhost',
dialect: 'mysql', // 數據庫類型
pool: { // 鏈接池配置
max: 5, // 數據庫鏈接池最大數
min: 0, // 數據庫鏈接池最小數
idle: 10000, // 在釋放鏈接以前容許空閒的最大毫秒數
acquire: 30000 // 在拋出錯誤以前容許獲取鏈接的最大時長(毫秒)
}
}
}
複製代碼
第二步,引入相關庫 (Koa, Sequelize), 實例化 sequelize 並傳入配置github
/* * db/index.js */
const Sequelize = require('sequelize');
const dbConf = require('../config/db')
module.exports = new Sequelize(dbConf.name, dbConf.username, dbConf.password, dbConf.option);
複製代碼
定義數據模型,就是建立一個 MySql 的數據表,例以下面定義了一個 student 的表:sql
/* * model/student.js */
module.exports = (sequelize, DataTypes) => {
/** * 第一個參數 String: 表的名稱 * 第二個參數 Object: 表中每一行的數據項,每一個屬性表明一列數據 * 第三個參數 Object: 額外的配置 */
return sequelize.define('student', {
name: {
type: DataTypes.STRING(50), // 定義類型(長度)
allowNull: false // 是否容許爲 NULL
},
number: {
type: DataTypes.INTEGER(5), // 定義類型(長度)
allowNull: false, // 是否容許爲 NULL
unique: true // 是不是惟一的
},
age: {
type: DataTypes.INTEGER(3), // 定義類型(長度)
allowNull: false, // 是否容許爲 NULL
default: 1 // 默認值
},
class: {
type: DataTypes.INTEGER(3), // 定義類型(長度)
allowNull: false, // 是否容許爲 NULL
default: 1 // 默認值
}
})
}
複製代碼
Sequelize 提供了不少種數據類型的選擇,可是有些數據類型是特定數據庫才能使用的,好比 Array 數組類型就只有 Postgres 纔可使用,Sequelize 詳細的數據類型描述可戳這裏數據庫
上文只是定義了數據表的模型,但並未在數據庫中建立表,接下來建立數據表:後端
/* * model/index.js */
const sequelize = require('../db/index')
// 導入模型
const student = seqeulize.import('./student')
// 同步模型到數據庫,便是建立一個 表
sequelize.sync()
// 導出模型
exports.student = student
複製代碼
在執行上述的操做後,test 數據庫中就會生成一個 student 數據表,若是下圖所示:
上圖中 createAt 和 updateAt 是 Sequlize 默認添加的字段,咱們能夠在配置 model 的時候選擇去掉。
有了數據表後,咱們能夠用 model 對數據表進行增刪改查的操做了
/* * router/student.js */
const models = require('../model/index')
const student = models.student
// 增
router.post('/student/add', async(ctx, next) => {
const { name, number, age, classNumber } = ctx.request.body
let ret
let error
try {
ret = await student.create({
name,
number,
age,
classNumber
})
}catch(e) {
error = e.error
}
if (ret) {
ctx.body = {
code: 0,
data: ret
}
} else {
ctx.body = {
code: -1,
data: error
}
}
})
// 改
router.post('/student/update/:id', async(ctx, next) => {
const keys = ['name', 'number', 'age', 'classNumber']
const id = ctx.params.id
let obj = {}
keys.map((key) => {
if (ctx.request.body[key] !== undefined) {
obj[key] = ctx.request.body[key]
}
})
let ret
let error
try {
ret = await student.update(obj, {
where: {
id
}
})
}catch(e) {
error = e.error
}
if (ret) {
ctx.body = {
code: 0,
data: ret
}
} else {
ctx.body = {
code: -1,
data: error
}
}
})
// 省略刪查
複製代碼
接下來咱們用 curl 測試一下咱們的接口:
curl -d 'name=lili&number=1&age=16&classNumber=1' -X POST http://localhost:9003/student/add
複製代碼
其返回以下:
查詢數據表 student 結果以下:
其餘刪改查相似。
上述只講述了單表的操做,可是在工做中,咱們經常須要多表聯合操做,下面咱們來看看,Sequelize 如何進行多表聯合操做。
假設每一個學生須要記錄本身所學的課程信息,學生和課程之間的關係爲 1:N, 一個學生可能選了不少課,也可能一門課也沒有,因此最好的辦法是將課程做爲一個單獨的表,而後和學生關聯起來。
首先讓咱們來理解一個概念:
假設有個學生表 (students tabel) 以下:
id | 姓名 | 班級 | 學號 |
---|---|---|---|
1 | lili | 2 | 202001 |
2 | xixi | 3 | 202002 |
而每一個學生有本身的選課狀況,選課表 (subject) 以下:
id | 選課學生 id | 課程名稱 |
---|---|---|
1 | 1 | math |
2 | 1 | english |
3 | 1 | chinese |
4 | 2 | chinese |
5 | 2 | physics |
對於第二個表 subject 來講,選課學生 id 就是外鍵,其是和學生表中的 id 相關聯的值,能夠經過這個鍵在學生表中查詢這個選課記錄對應的學生。咱們稱 student 表爲主表,subject 表爲從表,通常而言,外鍵一般關聯的是主表的主鍵或者設置了unique 的字段的項。
接下來咱們經過一個例子來學習數據表的關聯操做。
/* * model/subject.js */
module.exports = (sequelize, DataTypes) => {
return sequelize.define('subject', {
name: {
type: DataTypes.STRING(50), // 定義類型(長度)
allowNull: false // 是否容許爲 NULL
}
})
}
複製代碼
/* * model/index.js */
const sequelize = require('../db/index')
// 導入模型
const student = sequelize.import('./student')
const subject = sequelize.import('./subject')
// 關聯 student subject 表
student.hasMany(subject) // 會自動的將 studentId 添加到 subject 表中
// 同步模型到數據庫,便是建立表
sequelize.sync()
// 導出模型
exports.student = student
複製代碼
咱們修改上述單表操做的代碼,增長關聯表操做的邏輯
/* * router/student.js */
router.post('/student/add', async(ctx, next) => {
const { name, number, age, classNumber, subjects } = ctx.request.body
let ret
let error
try {
ret = await student.create({
name,
number,
age,
classNumber,
subjects // 注意這裏屬性是複數形式
}, {
include: [ models.subject ]
})
}catch(e) {
error = e
}
if (ret) {
ctx.body = {
code: 0,
data: ret
}
} else {
ctx.body = {
code: -1,
data: error
}
}
})
複製代碼
咱們用 curl 測試一下接口:
curl -X POST \
http://localhost:9003/student/add \
-d '{ "name": "hhh1", "age": 12, "number": 7, "classNumber": 3, "subjects": [{"name": "math"}, {"name": "chinese"}] }'
複製代碼
查看 subject 數據庫以下:
注意上述的代碼,插入數據的時候,subject 數據的屬性鍵名必須是複數,值必須是數組,由於關聯的時候是使用 student.hasMany(subject) 關聯的,Sequelize 內部使用 inflection-js 將 'subject' 轉化爲複數,做爲關聯表數據操做時的屬性名稱。
例如上述建立部分代碼若是改爲:
ret = await student.create({
name,
number,
age,
classNumber,
subjectes: subjects // 注意這裏屬性是複數形式
}, {
include: [ models.subject ]
})
複製代碼
則沒法將關聯 subjects 數據插入到 subject 表中,對於這種易錯的使用方式,Sequelize 提供了手動命名的方式,這裏暫且不講。
下面咱們再來看看關聯表的刪除
首先爲了方便測試,咱們在 subject 表中多插入幾條數據:
接着,咱們在主表中刪除 studentId 爲 12 的數據
curl -X DELETE http://localhost:9003/student/delete?id=12
複製代碼
而後你會發現,subject 表中 studentId 對應爲 12 個數據項都被設置爲了 NULL, 其實這個是 MySql 關聯表刪除主表時,Sequelize 設置的從表的默認表現。
在 MySql 中,在父表上進行 update/delete 以更新或刪除在子表中有一條或多條對應匹配行的候選鍵時,父表的行爲取決於:在定義子表的外鍵時指定的on update/on delete 子句,其有四種表現形式:
關鍵字 | 含義 |
---|---|
CASCADE | 刪除包含與已刪除鍵值有參照關係的全部記錄 |
SET NULL | 修改包含與已刪除鍵值有參照關係的全部記錄,使用NULL值替換(只能用於已標記爲NOT NULL的字段) |
RESTRICT | 拒絕刪除要求,直到使用刪除鍵值的輔助表被手工刪除,而且沒有參照時(這是默認設置,也是最安全的設置) |
NO ACTION | 啥也不作 |
因此,若是咱們想在刪除主表的時候,對應從表中相關的數據也刪除的話,能夠成以下刪除方式:
/* * router/student.js */
router.delete('/student/delete', async(ctx, next) => {
const id = ctx.query.id
let ret
let error
try {
ret = await student.destroy({
cascade: true, // 同時刪除從表
where: {
id
}
})
}catch(e) {
error = e
}
if (ret) {
ctx.body = {
code: 0,
data: ret
}
} else {
ctx.body = {
code: -1,
data: error
}
}
})
複製代碼
另外還須要更改關聯表時的配置:
/* * model/index.js */
// 關聯 student subject 表時,要設置 外鍵爲 allowNull 爲 false
student.hasMany(subject, {foreignKey : {name: 'studentId', allowNull: false}})
複製代碼
刪除後須要從新建立 subjects 數據表才能生效:
drop table subjects
複製代碼
下面是從表刪除前的數據圖:
執行 curl 刪除主表 id 爲 37 的數據:
curl -X DELETE http://localhost:9003/student/delete?id=37
複製代碼
刪除後 subjects 從表數據結果爲:
可見從表裏面的數據也被刪除了。
須要注意的時,只有從表的徹底只依附於主表的時候,才能執行這樣的關聯刪除操做。
而對於更新的操做,若是須要更新從表的數據某指定數據的話,能夠直接對從表進行操做:
ret = await subject.update(subjectObj, {
where: {
id
}
})
複製代碼
本文對於 Sequelize 作了簡單的介紹,以及結合例子還實現簡單的 CRUD 操做,固然,在實際的工做中,其場景遠遠比這個例子複雜。 Sequelize 就是一箇中間層,將咱們經過面向對象的代碼轉化爲一條條的 Sql 語句。經過 Squelize, 咱們能更好更直觀的對數據庫進行操做,可是也會帶了一部分的學習成本。