1. 程式人生 > 實用技巧 >專案總結,徹底掌握NodeJS中如何使用Sequelize

專案總結,徹底掌握NodeJS中如何使用Sequelize

前言

sequelize是什麼? sequelize是基於NodeJs的ORM框架,它適用於不同的資料庫,如:Postgres、MySQL、SQLite、MariaDB,我們可以通過sequelize對資料庫進行一系列的操作。通常我用它與MySQL一起使用。該文是我在使用sequelize做完專案後對sequelize的系統整理。

準備工作

一、建立資料庫和表,方便學習過程中書寫示例程式碼

建立資料庫 lesson

CREATE DATABASE IF NOT EXISTS lesson DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

建立商品表 goods(示例使用,欄位從簡)

CREATE TABLE IF NOT EXISTS goods(
	id INT(20) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '商品id',
    name VARCHAR(64) NOT NULL COMMENT '商品名稱',
    title VARCHAR(200) NOT NULL COMMENT '商品標題',
    descript TEXT COMMENT '商品描述',
    num BIGINT UNSIGNED NOT NULL COMMENT '商品庫存',
    cateid INT(10) UNSIGNED NOT NULL COMMENT '分類id',
    price DECIMAL(10,2) NOT NULL DEFAULT '0.00' COMMENT '商品價格',
    create_time DATE COMMENT '建立時間',
    update_time DATE COMMENT '修改時間'
)ENGINE=InnoDB;

建立分類表 categorys (示例使用,欄位從簡)

CREATE TABLE IF NOT EXISTS categorys(
    id INT(20) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '分類id',
    name VARCHAR(64) NOT NULL COMMENT '分類名稱',
    create_time DATE COMMENT '建立時間',
    update_time DATE COMMENT '修改時間'
)ENGINE=InnoDB;

二、使用koa框架起一個服務

要學習使用sequelize,我們要先用node搭建基礎服務,暴露介面方便測試,直觀的看到程式碼效果。

引入包

在使用sequelize之前,我們需要引入兩個包:sequelize、mysql2

npm install --save sequelize mysql2

連線資料庫

要連線資料庫,您必須new一個Sequelize的例項,給Sequelize建構函式傳遞引數完成連線。

const DB = new Sequelize(database,username,password,{
    host:'',
    dialect:''
})
  • database:要連線的資料庫name
  • username:登入資料庫時的使用者名稱
  • password:登入資料庫時的密碼
  • options:選項配置引數,為一個物件,內含許多配置屬性,如:
    • host:資料庫主機
    • port:資料庫埠號
    • dialect:指定要連線哪種類新的資料庫,如:mysql、postgres等
    • define:為模型定義預設選項
    • timezone:時區

讓我們先快速開始一個示例,從示例中學習需要注意的點 (請始終記住 我們使用的時準備階段建立的資料庫和表)

const {Sequelize,Model} = require('sequelize');

# 連線資料庫
const connect = new Sequelize('lesson','root','abc123456',{
    host:'localhost',
    port:3306,
    dialect:'mysql'
})

# 建立模型
class Category extends Model{}

Category.init({
    name:Sequelize.STRING
},{
    sequelize:connect,
    tableName:'categorys'
})

# 新增一點資料
Category.create({name:'房產'})

當我們執行程式碼新增資料時,會發現資料沒有新增成功且控制檯報錯。

為什麼會出現這個問題呢? 這是因為Sequelize為了方便開發者,它會自動幫開發者新增上建立時間和更新時間,但是它給這兩個時間規定了預設欄位為createdAtupdatedAt,如果不做任何配置,我們建立資料庫時建立時間和修改時間欄位必須與它預設規定的相同。

與這兩個欄位相關聯的有一個timestamp配置屬性,這個屬性可以啟用或不啟用Sequelize為開發者自動新增時間,預設為true。

對上面程式碼做一點點更改,如下:

# 連線資料庫
const connect = new Sequelize('lesson','root','abc123456',{
    host:'localhost',
    port:3306,
    dialect:'mysql',
    
 +  define:{
 +      timestamp:false
 +  }
})

在例項化Sequelize時,加上define引數配置,可以作用於全域性。 我們如上配置全域性timestamp為false,即表示關閉自動新增時間,再次執行程式碼 我們會發現資料庫添加了一條資料。

解決了上面新增資料失敗的問題,但是沒有建立時間,這顯然不是我們所希望的。 我們再對程式碼做一些改動,新增上create_time欄位如下:

# 建立模型
class Category extends Model{}

Category.init({
    name:Sequelize.STRING,
  + update_time:{
  +     type:Sequelize.DATE
  + }
},{
    sequelize:connect,
    tableName:'categorys'
})

# 新增一點資料
+ const create_time = new Date().getTime();
Category.create({name:'房產',create_time:create_time})

這時再開啟資料庫,我們可以看到有一條具有建立時間的資料已經被新增上去了

做到這裡,我們知道了,不用Sequelize自動新增時間的方式,我們可以自己手動編碼新增,而且資料庫的時間欄位可以是任意合法的字元。

當然,我們開啟了timestamp,資料庫的時間欄位也可以是任意合法的字元。 這時候需要我們配置欄位別名,這是很簡單的,只需要新增兩行程式碼就可以了,如下:

# 連線資料庫
const connect = new Sequelize('lesson','root','abc123456',{
    host:'localhost',
    port:3306,
    dialect:'mysql',
    
 +  define:{
 +      timestamp:true,
 +   	createdAt:'create_time',
 +   	updatedAt:'update_time'
 +  }
})

上述篇幅,我們已經知道如何連線資料庫和向資料庫中新增資料,但是有些業務場景,不僅僅只是將我們在輸入框中的資料原樣插入到資料庫中,可能會對輸入的資料做一些改動,如使用者註冊時,密碼需要加密然後再插入到資料庫中。

需要實現這個需求,我們需要學習Sequelize的Setters、Getters和Virtuals

Setters: setter是為模型中欄位的set()函式,它接收欄位的值。它作用在資料的新增階段,在set()函式中,我們可以獲取欄位的值並對原始值做一系列的變化,然後再設定最終值。

Getters: getter是為模型中欄位的get()函式,它作用在資料的獲取階段,在get()函式中,我們可以先對返回值做一系列的操作,再返回最終值。

Virtuals: virtuals是虛擬欄位,它不存在與資料庫中。開發者可以使用它將兩個欄位拼接返回。

接下來我們來看看實際使用的栗子,程式碼如下:

Category.init({
    name:{
        type:Sequelize.STRING,
        set(val){
            const newVal = val + 'sb';	// 所有新增的資料 name後都加上sb字元
            this.setDataValue('name',newVal);
        },
        get() {
            const rawValue = this.getDataValue('name');	//獲取欄位值
            return rawValue ? rawValue.toUpperCase() : null;	//將欄位值的字母轉換成大寫輸出
        }
    },
    vitName:{	// 這是一個虛擬欄位
        type:Sequelize.VIRTUAL,
        get(){
            return `${this.name}`
        }
    }
},{
    sequelize:connect,
    tableName:'categorys'
})

到現在,我們已經學習了Sequelize很多的知識點了,接下來我們繼續學習Sequelize的查詢、修改、刪除等方法。 在學習之前 我們還是先將資料庫的資料維護一份正式一點的資料。

分別是分類表和商品表 如下圖:

Sequelize為開發者提供了查詢方法,預設情況下,所有查詢器方法的結果都是模型類的例項,而不是簡單的javascript物件。這意味著在資料庫返回結果後,Sequelize會自動將所有內容打包到適當的例項物件中。在某些情況下,當結果太多時,這種包裝可能效率低下,可以通過{raw:true}選項配置禁用。

查詢選擇器:

  • findAll:它生成一條標準的SELECT查詢語句,在不受到條件語句限制的情況下從表中檢索所有資料。
  • findByPk:根據提供的主鍵,從表中檢索處一條資料。
  • findOne:如果不提供查詢選項的話,它會檢索到表中第一條資料。
  • count:檢索資料庫中所有資料,得到資料總數。
  • findAndCountAll:可以獲得資料總量和資料列表。這在做分頁資料時時非常方便的。

現在我們來查詢表資料,實現一些需求。

所有商品:
router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findAll();
    ctx.body = {
        msg:result
    }
})

得到的結果為:

{
    "msg": [
        {
            "id": 2,
            "name": "工裝七分褲",
            "title": "優衣庫 男裝 工裝七分褲(卷邊) 425146 UNIQLO ",
            "descript": "優衣庫 男裝 工裝七分褲(卷邊) 425146 UNIQLO 優衣庫 男裝 工裝七分褲(卷邊) 425146 UNIQLO ",
            "num": 158,
            "cateid": 2,
            "price": "149.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        },
        {
            "id": 3,
            "name": "花花公子旗艦短袖t恤",
            "title": "花花公子旗艦短袖t恤男2020新款夏季潮牌寬鬆男士半袖潮流打底衫T ",
            "descript": "花花公子旗艦短袖t恤男2020新款夏季潮牌寬鬆男士半袖潮流打底衫T 花花公子旗艦短袖t恤男2020新款夏季潮牌寬鬆男士半袖潮流打底衫T ",
            "num": 3000,
            "cateid": 2,
            "price": "269.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        },
        {
            "id": 4,
            "name": "華為nova",
            "title": "Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦店 ",
            "descript": "Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦店",
            "num": 2500,
            "cateid": 3,
            "price": "1999.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        },
        {
            "id": 5,
            "name": "榮耀30",
            "title": "華為旗下榮耀30新品5G手機50倍超穩遠攝麒麟985晶片全新智慧手機正品官方旗艦店",
            "descript": "華為旗下榮耀30新品5G手機50倍超穩遠攝麒麟985晶片全新智慧手機正品官方旗艦店華為旗下榮耀30新品5G手機50倍超穩遠攝麒麟985晶片全新智慧手機正品官方旗艦店",
            "num": 3512,
            "cateid": 3,
            "price": "3299.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        },
        {
            "id": 6,
            "name": "美的變頻空調",
            "title": "美的i青春大1.5匹空調智慧掛機冷暖壁掛式官方旗艦店",
            "descript": "美的i青春大1.5匹空調智慧掛機冷暖壁掛式官方旗艦店美的i青春大1.5匹空調智慧掛機冷暖壁掛式官方旗艦店",
            "num": 4215,
            "cateid": 4,
            "price": "2999.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        },
        {
            "id": 7,
            "name": "格力Gree",
            "title": "Gree/格力 KFR-35GW 大1.5匹空調掛機智慧變頻冷暖一級壁掛式品悅",
            "descript": "Gree/格力 KFR-35GW 大1.5匹空調掛機智慧變頻冷暖一級壁掛式品悅Gree/格力 KFR-35GW 大1.5匹空調掛機智慧變頻冷暖一級壁掛式品悅",
            "num": 4215,
            "cateid": 4,
            "price": "4199.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        },
        {
            "id": 8,
            "name": "鬱香菲2020夏季新品",
            "title": "鬱香菲2020夏季新品 荷葉邊兩穿收腰長款連衣裙純色垂感飄逸長裙 ",
            "descript": "鬱香菲2020夏季新品 荷葉邊兩穿收腰長款連衣裙純色垂感飄逸長裙 鬱香菲2020夏季新品 荷葉邊兩穿收腰長款連衣裙純色垂感飄逸長裙 ",
            "num": 4215,
            "cateid": 5,
            "price": "699.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        },
        {
            "id": 9,
            "name": "美背文胸",
            "title": "運動內衣女無鋼圈聚攏冰絲無痕背心式胸罩夏季薄款調整型美背文胸 ",
            "descript": "運動內衣女無鋼圈聚攏冰絲無痕背心式胸罩夏季薄款調整型美背文胸 運動內衣女無鋼圈聚攏冰絲無痕背心式胸罩夏季薄款調整型美背文胸 ",
            "num": 10000,
            "cateid": 5,
            "price": "99.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        }
    ]
}
條件查詢

查詢

router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findAll({
        where:{
            id:4
        }
    });
    ctx.body = {
        msg:result
    }
})

結果:

{
    "msg": [
        {
            "id": 4,
            "name": "華為nova",
            "title": "Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦店 ",
            "descript": "Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦店",
            "num": 2500,
            "cateid": 3,
            "price": "1999.00",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        }
    ]
}

查詢

router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findOne({
        where:{
            id:4
        }
    });
    ctx.body = {
        msg:result
    }
})
# 等同於
router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findByPk(4);
    ctx.body = {
        msg:result
    }
})

結果一樣,都是

{
    "msg": {
        "id": 4,
        "name": "華為nova",
        "title": "Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦店 ",
        "descript": "Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦Huawei/華為 nova6 SE超級快充4800萬AI四攝大運存nova6se 華為手機華為官方旗艦店",
        "num": 2500,
        "cateid": 3,
        "price": "1999.00",
        "create_time": "2020-07-25",
        "update_time": "2020-07-25"
    }
}

注意:findAll和findOne、findByPk方法得到的結果是不同的,findAll得到的結果是陣列包含著資料項,而findOne、findByPk得到的結果就是一條資料

分頁查詢

我們在專案中經常會需要分頁查詢,一次性查詢所有的資料,無論是伺服器還是前端頁面顯示都是不友好的。Sequelize為開發者提供了分頁機制,我們只需配置offsetlimit兩個屬性就可以實現分頁。

  • offset:偏移量,以0開始做偏移
  • limit:限制條數

值得我們注意的是offset是以0開始做偏移的,通常前端分頁會傳遞兩個引數:pageCurrentpageSize。 pageCurrent通常是以1開始的。所以後端接收到pageCurrent後需要減1傳遞給Sequelize做偏移。

router.get('/pro/list',async (ctx,next) => {
    const { pageCurrent,pageSize } = ctx.request.body;
    let limit = pageSize ? pageSize : limit;
    let offset = pageCurrent ? (pageCurrent - 1)*limit : offset;
    const result = await Product.findAll({
        offset,
        limit
    });
    ctx.body = {
        msg:result
    }
})
指定欄位
router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findOne({
        attributes:['name','price']
    });
    ctx.body = {
        msg:result
    }
})

結果

{
    "msg": {
        "name": "工裝七分褲",
        "price": "149.00"
    }
}
排除欄位
router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findOne({
        attributes:{
            exclude:['descript']
        }
    });
    ctx.body = {
        msg:result
    }
})

結果

{
    "msg": {
        "id": 2,
        "name": "工裝七分褲",
        "title": "優衣庫 男裝 工裝七分褲(卷邊) 425146 UNIQLO ",
        "num": 158,
        "cateid": 2,
        "price": "149.00",
        "create_time": "2020-07-25",
        "update_time": "2020-07-25"
    }
}
排序查詢

配置order選項,order是一個數組,欄位名和排序方式。 desc(降序),asc(升序)

router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findAll({
        order:[
            ['id','desc']
        ]
    });
    ctx.body = {
        msg:result
    }
})
關聯查詢

sequelize提供了四種類型的關聯關係

  • hasOne關聯:與目標建立一對一關聯關係,外來鍵存在於目標模型中
  • belongsTo關聯:與目標建立一對一關聯關係,外來鍵存在於源模型中
  • hasMany:與目標建立一對多關聯關係,外來鍵存在於目標模型中
  • belongsToMany:與目標建立多對多關聯關係,外來鍵存在於源模型中
# 外來鍵存在於源模型中
Product.belongsTo(Category,{
    foreignKey:'cateid',
    targetKey:'id'
})

# 外來鍵存在於目標模型中
Product.hasOne(Category,{
    as:'cate',
    foreignKey:'id',
    targetKey:'cateid'
})

上面兩個程式碼示例, Product模型是源模型,Category模型是目標模型。cateid是Product中定義的欄位列,可以看到使用belongsTo時外來鍵foreignKey的值是cateid 使用hasOne時外來鍵foreignKey的值是id ,這個id是Category中的主鍵id。

示例程式碼:

Product.hasOne(Category,{
    foreignKey:'id',
    targetKey:'cateid'
})

router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findOne({
        include:{
            model:Category
        }
    });
    ctx.body = {
        msg:result
    }
})

得到結果:

{
    "msg": {
        "id": 2,
        "name": "工裝七分褲",
        "title": "優衣庫 男裝 工裝七分褲(卷邊) 425146 UNIQLO ",
        "descript": "優衣庫 男裝 工裝七分褲(卷邊) 425146 UNIQLO 優衣庫 男裝 工裝七分褲(卷邊) 425146 UNIQLO ",
        "num": 158,
        "cateid": 2,
        "price": "149.00",
        "create_time": "2020-07-25",
        "update_time": "2020-07-25",
        "Category": {
            "id": 2,
            "name": "男裝",
            "create_time": "2020-07-25",
            "update_time": "2020-07-25"
        }
    }
}

商品資訊中就有了該商品所屬分類的資訊,可以看到返回的商品資訊欄位是Category即是以定義的類名Category為欄位,我們可以更改它。

Product.hasOne(Category,{
  + as:'cate',
    foreignKey:'id',
    targetKey:'cateid'
})

router.get('/pro/list',async (ctx,next) => {
    const result = await Product.findOne({
        include:{
            model:Category,
   +        as:'cate'
        }
    });
    ctx.body = {
        msg:result
    }
})

這樣分類資訊的欄位就跟更改成了cate

Category.update(param,option)

  • param:修改的引數集合,Object
  • option:配置選項
router.post('/edit',async (ctx,next) => {
    await Category.update({
        name:'男裝內褲'
    },{
        where:{
            id:2
        }
    })
})

上面程式碼 修改id等於2的那條資料,將name欄位的值更改為'男裝內褲'。

Category.destroy(option)

Category.destroy({
    where:{
        id:2
    }
})