Sequelize使用教程(v5版本)

用nodejs寫服務端的小夥伴們對sequelize一定不會陌生的,它是node端著名的ORM框架,支持MySQL, MariaDB, SQLite和Microsoft SQL Server等數據庫。通過這個庫我們可以實現如下功能:

  • 生成表:使用js定義好表結構,直接將表同步生成到數據庫中,省去了手動寫sql建表的煩惱。

  • CRUD:通過sequlize提供的方法去實現數據庫的查詢等操作,清晰明瞭,不需要寫大段大段的原生sql語句。

  • 遷移:如果需要在下一版本中,增加或者刪除某個庫中的字段,亦或是增加表或刪除表,直接修改之前定義表結構的js文件,然後執行一個遷移腳本,就會自動生成一個數據庫的升級和降級的腳本,直接運行即可。(本文不講解如何實現遷移,需要的小夥伴請查看官方文檔https://sequelize.org/v5/manual/migrations.html

之前項目中用的是v4版本的sequelize(一直偷懶沒有升級),在2019.03.13的時候sequelize正式發佈的v5版本。最近正好有個新項目於是就用了v5版本的,熟悉了下新的方法,本文主要講解sequelize結合mysql的在實際項目中的用法!

seuqelize v5官方文檔地址:https://sequelize.org/v5/

一、安裝

通過npm安裝sequelize和mysql(v5版本的sequelize需要node版本爲6或者更高

npm install --save sequelize
npm install --save mysql2

二、建立mysql連接

接下來我們會用Sequelize的構造方法來創建一個mysql的連接實例。

先來看看sequelize官方文檔中構造方法的定義

public constructor(database: string, username: string, password: string, options: Object)

再來看看我們實際代碼中對構造方法的調用: 

const Sequelize = require('sequelize');

const TcDb = new Sequelize(database, username, password, {
    host: DB_CONFIG.HOST,
    port: DB_CONFIG.PORT,
    dialect: 'mysql',
    timezone: '+08:00',
    logging: false,
    pool: {
        max: 5,
        min: 0,
        idle: 10000
    },
    define: {
        freezeTableName: true,//禁止自動修改表名
        timestamps: false,//不需要添加 createdAt 和 updatedAt 兩個時間戳字段
    }
});

解釋幾個必要參數,以及日常使用頻率較高的參數

  • database:數據庫的名稱
  • username:用來認證數據庫連接的用戶名
  • password:用來認證數據庫連接的密碼
  • host:數據庫的host地址
  • port:數據庫服務的端口
  • dialect:數據庫語言,可以選mysql、postgres、sqlite等
  • timezone:時區,將數據庫時間轉換成js的date時候會用到,默認是:‘+00:00’,我們一般設置成中國的時區,避免一些時間轉換的麻煩
  • logging:執行每次數據庫操作,是否在在控制檯輸出原生sql的log
  • pool:數據庫連接池的設置,用的是默認配置
  • freezeTableName:默認爲false,是否需要固定表名,如果設置爲true,js中定義表名會和數據庫中創建的表名相同,如果爲false,Sequelize會按照他的規則生成一個表名
  • timestamps:默認爲true,默認情況下,Sequelize會爲每個表創建2個字段,createdAt和updatedAt,設置爲false則不會自動創建這2個字段

初始化完mysql的連接後,可以通過下面的代碼來驗證下是否連接成功:

TcDb.authenticate()
    .then(() => {
        console.log('Connection has been established successfully.');
    })
    .catch(err => {
        console.error('Unable to connect to the database:', err);
    });

關於更多連接配置參數的介紹請查閱官方文檔:https://sequelize.org/v5/class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor

三、編寫表的model文件

一般情況下,一個表對應一個js文件,在js文件的開頭我們先定義表結構,關於這張表的所有查詢、更新、插入等數據庫方法也會寫在這個js文件裏面。 接下來我們來看一個簡單的用戶表的model定義。

/**
 * 用戶表
 * wx_user
 */
const {TcDb} = require('../db-info');
const Sequelize = require('sequelize');
const Model = Sequelize.Model;

class WxUserModel extends Model {
}

WxUserModel.init({
    id: {type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, comment: '主鍵'},
    openId: {type: Sequelize.STRING(32), comment: '公衆號登錄授權返回的openId', unique: true},
    mobile: {type: Sequelize.STRING(50), comment: '加密後的手機號'},
    realName: {type: Sequelize.STRING(100), comment: '真實姓名'},
    belongBank: {type: Sequelize.INTEGER, comment: '所屬網點'},
    createTime: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('NOW()'),
        comment: '創建時間'
    },
    updateTime: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.literal('NOW()'),
        comment: '更新時間'
    },
}, {
    sequelize: TcDb,
    modelName: 'wx_user'
});

module.exports = WxUserModel;

第一步:創建一個用戶表對應的class類,繼承於Model。這個Model類就是ORM框架的紐帶,用來將我們js中定義的表結構映射到Mysql中。

第二步:調用我們用戶表Model的init()方法來定義我們的表結構。

public static init(attributes: Object, options: Object): Model

init()方法需要傳入2個對象就能完成一個表的定義的,我們來分別看看兩個參數的主要屬性分別有什麼:

【attributes】第一個參數對象裏面存放的是表中每一列的定義,key是表中的列名,vaule也是一個對象,包含了該列的屬性,常用的屬性如下:

Sequelize.STRING                      // VARCHAR(255)
Sequelize.STRING(1234)                // VARCHAR(1234)
Sequelize.STRING.BINARY               // VARCHAR BINARY
Sequelize.TEXT                        // TEXT
Sequelize.TEXT('tiny')                // TINYTEXT

Sequelize.INTEGER                     // INTEGER
Sequelize.BIGINT                      // BIGINT
Sequelize.BIGINT(11)                  // BIGINT(11)

Sequelize.FLOAT                       // FLOAT
Sequelize.FLOAT(11)                   // FLOAT(11)
Sequelize.FLOAT(11, 10)               // FLOAT(11,10)

Sequelize.DOUBLE                      // DOUBLE
Sequelize.DOUBLE(11)                  // DOUBLE(11)
Sequelize.DOUBLE(11, 10)              // DOUBLE(11,10)

Sequelize.DECIMAL                     // DECIMAL
Sequelize.DECIMAL(10, 2)              // DECIMAL(10,2)

Sequelize.DATE                        // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
Sequelize.DATEONLY                    // DATE without time.
Sequelize.BOOLEAN                     // TINYINT(1)
  • allowNull:可選參數,默認爲true,對應mysql中是否允許該列爲空
  • defaultValue:可選參數,默認爲null,對應mysql中該列的默認值
  • unique:可選參數,默認爲false,標記該列是否唯一,如果設置爲ture,會爲該列創建一個唯一索引
  • primaryKey:可選參數,默認爲false,標記該列是否爲主鍵
  • autoIncrement:可選參數,默認爲false,該列是否自增,一般和主鍵聯用
  • comment:可選參數,默認爲null,列的註釋,v5版本中會將comment字段同步的mysql中的註釋,見下圖

【options】第二個參數對象包含的主要屬性再來看看:

  • sequelize:必選參數,對應是我們前面第二步中創建mysql庫連接實例時候返回的對象
  • modelName:可選參數,數據庫的表名,這個字段一般會自定義的,默認爲class的名稱
  • indexes:可選參數,用來定義表的索引,可以在此處設置表中字段的唯一索引、全文索引等
  • .............

關於上面init()方法的2個參數對象的完整文檔,請參考https://sequelize.org/v5/class/lib/model.js~Model.html#static-method-init

四、生成mysql中的表

剛剛我們完成了js中表model的定義,這時候我們需要在數據庫中創建這個表了。Model提供了一個sync()方法用來創建表。

const WxUserModel = require('./model/wx-user');

WxUserModel.sync({force: true})
        .then((result) => {
            console.log(result);
        })
        .catch((err) => {
            console.error(err);
        });

sycn()方法中傳了一個參數force(是否強制創建表),false表示如果數據庫已存在這個表則不執行任何操作;true表示如果數據庫中存在這個表,會先將原來的表以及裏面的數據刪除再創建。

五、增刪改查

Sequelize的Model類爲我們提供了對數據庫CRUD操作的方法。在第三步的時候我們創建了一個WxUserModel繼承於Model,Model類有很多自帶的方法比如create()、findOne()、update()、destory()........

但是我們的實際項目中對數據庫的操作不會是簡單的增刪改查,我們會對Model類提供的方法上添加很多自己的邏輯。比如查詢用戶列表的時候,可能涉及到很多過濾條件(年齡、名字以及分頁信息等),所以我們一般會封裝自己的方法。爲了避免自定義的方法名和Model類的方法名重複,我的習慣是在自定義的方法前面加一個小寫的c,表示是class方法,比如

WxUserModel.cCreateOne(param1, param2, param2)
WxUserModel.cFindAllByFilter(param1, param2, param2)
WxUserModel.cUpdateOne(param1, param2, param2)
WxUserModel.cDeleteOne(param1, param2, param2)

插入

//Sequelize Model定義的插入方法
public static create(values: Object, options: Object): Promise<Model>

//自定義方法
WxUserModel.cCreateOne = function (openId, mobile, realName) {
    return WxUserModel.create({openId: openId, mobile: mobile, realName: realName});
};

//方法調用
let result = await WxUserModel.cCreateOne('abcdefghij', '18888888888', 'wyk');

查找

//Sequelize Model定義的插入方法
public static findOne(options: Object): Promise<Model>

//自定義方法
WxUserModel.cFindOneByName = function (realName) {
    return WxUserModel.findOne({
        where: {realName: realName}
    });
};

//方法調用
let result = await WxUserModel.cFindOneByName('wyk');

更新

//Sequelize Model定義的插入方法
public update(values: Object, options: Object): Promise<Model>

//自定義方法,updateDict是我們需要更新成的新屬性的對象
WxUserModel.cUpdateOneByOpenId = function (openId, updateDict) {
    updateDict.updateTime = new Date();

    return WxUserModel.update(updateDict, {
        where: {openId: openId}
    })
};

//方法調用
let result = await WxUserModel.cUpdateOneByOpenId('abcdefghij', {name:'newName', mobile: '19999999999'});

刪除

//Sequelize Model定義的插入方法
public destroy(options: Object): Promise

//自定義方法
WxUserModel.cDeleteOneByOpenId = function (openId) {
    return WxUserModel.destroy({
        where: {openId: openId}
    })
};

//方法調用
let result = await WxUserModel.cDeleteOneByOpenId('abcdefghij');

上面舉得例子都是比較基礎的用法,比如我們插入時候用的create()方法,它實際上有2個參數,第一個參數是我們插入的數據對象,第二個可選參數對象包含了如下屬性:

options Object
  • optional

Build and query options

options.raw boolean
  • optional
  • default: false

If set to true, values will ignore field and virtual setters.

options.isNewRecord boolean
  • optional
  • default: true

Is this new record

options.include Array
  • optional

An array of include options - Used to build prefetched/included model instances. See set

options.fields string[]
  • optional

An optional array of strings, representing database columns. If fields is provided, only those columns will be validated and saved.

options.silent boolean
  • optional
  • default: false

If true, the updatedAt timestamp will not be updated.

options.validate boolean
  • optional
  • default: true

If false, validations won't be run.

options.hooks boolean
  • optional
  • default: true

Run before and after create / update + validate hooks

options.logging Function
  • optional
  • default: false

A function that gets executed while running the query to log the sql.

options.benchmark boolean
  • optional
  • default: false

Pass query execution time in milliseconds as second argument to logging function (options.logging).

options.transaction Transaction
  • optional

Transaction to run query under

options.searchPath string
  • optional
  • default: DEFAULT

An optional parameter to specify the schema search_path (Postgres only)

options.returning boolean | Array
  • optional
  • default: true

Appends RETURNING <model columns> to get back all defined values; if an array of column names, append RETURNING <columns> to get back specific columns (Postgres only)

太多了吧!!具體就不展開了,詳情請參考create()的文檔 https://sequelize.org/master/class/lib/model.js~Model.html#static-method-create,find()、update()、destroy()的文檔裏面都有。

六、事務

事務在mysql中是必不可少的。舉個經典的例子,當用戶購買某個產品服務時支付成功了,我們需要對數據庫進行的操作有:置訂單的狀態、服務表插入記錄、修改用戶屬性等等等。這些操作要麼全部成功,要麼全部失敗,不能說其中2個成功,其他操作失敗了。所以我們就需要把每一個邏輯單元中的所有操作放在一個事務裏,如果所有數據庫操作執行完成後沒有異常,那就提交這次事務;如果出現異常,事務則執行回滾。

Sequelize中事務的文檔請參考:https://sequelize.org/v5/manual/transactions.html

Sequelize爲我們提供了2種使用事務方法

  • 託管式:在這種模式下,順利執行完成會自動提交事務,出現異常會自動執行回滾。它基於是promise的鏈式調用,在sequelize.transaction的回調中,必須返回一個promise對象。如果promise最終的狀態爲fulfilled,則自動提交事務;如果promise最終的狀態爲rejected,則自動回滾。看一個官方的例子:
//TcDb爲第二步中創建的數據庫連接對象
return TcDb.transaction(t => {
  //回調中務必返回一個promise對象
  return WxUserModel.create({
    openId: 'openId1'
  }, {transaction: t}).then(user => {
    return WxUserModel.create({
      openId: 'openId2'
    }, {transaction: t});
  });
}).then(result => {
  //執行完成,事務已自動提交callback
}).catch(err => {
  //出現異常,事務已回滾
});

 

  • 非託管式:提交事務和回滾的操作需要用戶手動調用。這個是我在項目中使用的方法,只要稍作封裝,使用起來會很方便。詳細介紹下用法:

比如有一個邏輯操作,需要創建一個用戶A並且更新用戶B的手機號,這2個操作需要放在事務中來保證一致性。我們要先對第五步中的創建方法和更新方法進行一點小改造:傳入的參數增加一個transaction對象。

//創建
WxUserModel.cCreateOne = function (t, openId, mobile, realName) {
    return WxUserModel.create(
        {openId: openId, mobile: mobile, realName: realName},
        {transaction: t}
    );
};

//更新
WxUserModel.cUpdateOneByOpenId = function (t, openId, updateDict) {
    updateDict.updateTime = new Date();

    return WxUserModel.update(updateDict, {
        where: {openId: openId},
        transaction: t
    })
};

上面2個方法都增加了一個參數t(實際項目中每一個CRUD方法都需要傳入t參數)。參數t是什麼呢?他是一個數據庫事務對象,通過TcDb.transaction()返回的promise中得到的。一般會在數據庫連接對象(TcDb)上再封裝一個getTransaction()方法,用來獲取事務對象t,以及管理事務流程(commit和callback操作)。

/**
 * 封裝開啓數據庫事務的方法
 *
 * @param callback
 * @returns {Promise<void>}
 */
TcDb.getTransaction = async (callback) => {
    let t = null;
    try {
        t = await TcDb.transaction();
        await callback(t);
        t.commit();
    } catch (err) {
        if (t)
            t.rollback();
        console.error(err);
        //try-catch只是爲了能夠在異常的時候rollback,如果有異常,可拋出
        //throw new Error(err);
    }
};

最後將獲取事務方法和帶事務參數插入和更新方法連起來使用。

async function dbTest() {
    await TcDb.getTransaction(async (t) => {
        let result = await WxUserModel.cCreateOne(t, 'openId1', '18888888888', 'wyk');
        console.log(result);

        let result2 = await WxUserModel.cUpdateOneByOpenId(t, 'abcdefghij', {name: 'newName', mobile: '19999999999'});
        console.log(result2);
    });
}

dbTest();

七、實際應用

未完待續.....

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章