Node Web開發ORM框架 Sequelize

文章選自個人免費Chat 如何使用 Sequelize 框架快速進行 Node Web 開發,分享給你。

一、什麼是 ORM?

首先看下維基百科上的定義,ORM 是「對象關係映射」的翻譯,英語全稱爲Object Relational Mapping,它是一種程序設計技術,用於實現面向對象編程語言裏不同類型系統的數據之間的轉換。從效果上說,它其實是創建了一個可在編程語言裏使用的「虛擬對象數據庫」。

隨着面向對象軟件開發方法的發展,ORM 的概念應運而生,它用來把對象模型表示的對象,映射到基於 SQL 的關係模型數據庫結構中去。這樣,我們在具體的操作實體數據庫的時候,就不需要再去和複雜的 SQL 語句打交道,只需簡單的操作實體對象的屬性和方法,就可以達到操作數據庫的效果。

ORM 技術是在對象和數據庫之間提供了一條橋樑,前臺的對象型數據和數據庫中的關係型的數據通過這個橋樑來相互轉化。

不同的編程語言,有不同的ORM框架。例如Java,它的ORM框架就有:Hibernate,Ibatis/Mybatis等等。在Node Web開發中,Sequelize 就是一款比較流行的 ORM 框架。

二、Sequelize 初步使用

在 Node Web 開發過程中,後臺數據庫我一直使用的都是 Mysql。起初在做 Node Web 開發的時候,都是提前在 Mysql 圖形界面裏創建好數據表,然後再開始實際開發,這個過程一直穿插在整個項目的開發過程中。一個人在一臺機器上,做全棧的開發,這個過程可能並不會出現什麼問題,因爲數據表結構以及整個項目代碼都在一臺電腦上,不管你怎麼修改,都是一套代碼,一個數據庫結構。

然而,當你需要在多臺電腦之間協同工作的時候,你就會發現這種方式的弊端。比如在A電腦上修改了數據表結構之後,接着去B電腦上繼續編碼,我們雖然能通過Git同步代碼,但是數據表結構卻無法同步過去,我們就需要在B電腦上,手動將數據庫結構維護成一致,否則無法接着進行。這種操作方式非常的不方便,而且很LOW。

而 Sequelize 框架就能很好的解決這個問題,通過 Sequelize 框架,我們將每個數據表直接定義爲數據模型,通過調用數據模型的一些方法,就可以直接操作數據庫,甚至是同步數據表結構。好了,廢話不多說了,直接上手實戰。

PS. 下面我會介紹,我在實際開發中的一些實用技巧,而不會逐條介紹增刪改查的使用。所以增刪改查的基本使用,建議直接查看官方文檔

1. 創建連接對象,並模塊化

新建數據庫連接模塊dbConn.js,單獨提出連接數據庫的對象sequelize,如下代碼:

 

var Sequelize = require('sequelize');
// 數據庫配置文件
var sqlConfig = {
    host: "localhost",
    user: "root",
    password: "Lupeng1",
    database: "example-sequelize"
};

var sequelize = new Sequelize(sqlConfig.database, sqlConfig.user, sqlConfig.password, {
    host: sqlConfig.host,
    dialect: 'mysql',
    pool: {
        max: 10,
        min: 0,
        idle: 10000
    }
});
module.exports = sequelize;

我們根據數據庫的一些參數,創建了sequelize數據庫連接模塊,並對外引用。

2. 定義數據表結構,將表結構寫進代碼裏

建議每個表對應一個文檔,放入項目的單獨目錄下,例如我通常放進了/db/models下,這裏拿我創建的一個todolist表來做示例,在models目錄中創建todolist.js文件,代碼如下:

 

var Sequelize = require('sequelize');
var sequelize = require('./dbConn.js');

var todolist = sequelize.define('todolist',{
    id: {
        type: Sequelize.BIGINT(11),
        primaryKey: true,
        allowNull: false,
        unique: true,
        autoIncrement: true
    },
    title: Sequelize.STRING(100),          // 標題
    content: Sequelize.STRING(500),        // 詳細內容
    priority: Sequelize.INTEGER,          // 級別
    owner: Sequelize.STRING,              // 承接人
    officer: Sequelize.STRING,             // 負責人
    startDate: Sequelize.STRING,         // 開始時間
    planFinishDate: Sequelize.STRING,     // 計劃完成時間
    realFinishDate: Sequelize.STRING,     // 實際完成時間
    bz: Sequelize.STRING(500),               // 備註
    state: Sequelize.INTEGER,            // 狀態
    createdAt: Sequelize.BIGINT,
    updatedAt: Sequelize.BIGINT,
    createUser: Sequelize.STRING,
    updateUser: Sequelize.STRING,
    version: Sequelize.BIGINT
},{
    timestamps: false               // 不要默認時間戳
});

module.exports = todolist;

以上代碼,直接引入之前創建的sequelize對象,然後使用defind方法定義數據表結構。其他的所有數據表都可以通過這種方式來定義,保存在每一個獨立的文件中,引出數據模模型即可。

3. 同步數據表結構

這個就簡單了,在/db目錄下,新建syncTable.js,代碼如下:

 

var todolist = require('./models/todolist.js');

// 同步表結構
todolist.sync({
    force: true  // 強制同步,先刪除表,然後新建
});

當我們換臺電腦繼續項目的時候,不用手動去同步數據表結構了,只需要執行一下該文件就可以了。

 

node db/syncTable.js

4. 創建一些初始數據

我們同樣可以創建一個方法,用來初始化一些基礎數據,如下initData.js代碼:

 

var priority = require('./models/priority.js');
// 創建u_priority表的基礎數據
priority.create({
    title: '重要 緊急'
}).then(function (p) {
    console.log('created. ' + JSON.stringify(p));
}).catch(function (err) {
    console.log('failed: ' + err);
});
...

prioritys表裏創建一些初始數據,默認表名會添加s,定義表的時候可以通過tableName屬性值來定義對應的表名,如下示例將表名定義爲u_priority

 

var priority = sequelize.define('priority',{
    id: {
        type: Sequelize.BIGINT(11),
        primaryKey: true,
        allowNull: false,
        autoIncrement: true
    },
    title: Sequelize.STRING,
},{
    timestamps: false,
    tableName: 'u_priority'  // 數據表名爲u_priority
});

Sequelize 的初步使用,就介紹到這裏。接下來,通過實際項目示例,再深入瞭解一下 Sequelize 的其他功效。

三、實際項目示例

1. 協同開發規範

實際項目中,直接面臨的一個問題就是協同規範的問題,例如,數據表名命名的規則,是採用默認方案,直接在後面加s,變成prioritys,還是改成u_priority;還有,每個表是否要加時間戳,或者其他一些通用字段。

如果沒有規範,那麼同一個項目中,數據表的形式就會百花齊放了,這是個人開發習慣問題,並不是錯誤。所以,當在做協同開發的時候,我們非常有必要定義一個標準接口,大家通過統一的標準調用方法建立模型,這樣就形成一種內部規範。我們把所有需要規範的內容,全部封裝在一個對象裏,例如我們這樣改寫dbConn.js文件。

 

var Sequelize = require('sequelize');
// 數據庫配置文件
var sqlConfig = {
    host: "localhost",
    user: "root",
    password: "Lupeng1",
    database: "example-sequelize"
};

console.log('init sequelize...');
console.log('mysql: ' + JSON.stringify(sqlConfig));

var sequelize = new Sequelize(sqlConfig.database, sqlConfig.user, sqlConfig.password, {
    host: sqlConfig.host,
    dialect: 'mysql',
    pool: {
        max: 10,
        min: 0,
        idle: 10000
    },
    timezone: '+08:00' //東八時區
});

exports.sequelize = sequelize;

exports.defineModel = function (name, attributes) {
    var attrs = {};
    for (let key in attributes) {
        let value = attributes[key];
        if (typeof value === 'object' && value['type']) {
            value.allowNull = value.allowNull || false;
            attrs[key] = value;
        } else {
            attrs[key] = {
                type: value,
                // allowNull: false
            };
        }
    }
    attrs.version = {
        type: Sequelize.BIGINT,
        // allowNull: false
    };
    attrs.createUser = {
        type: Sequelize.STRING,
        allowNull: false
    };
    attrs.updateUser = {
        type: Sequelize.STRING,
        allowNull: false
    };
    return sequelize.define(name, attrs, {
        tableName: name,
        timestamps: true,
        paranoid: true, 
        charset: 'utf8mb4', 
        collate: 'utf8mb4_general_ci',
        hooks: {
            beforeBulkCreate: function(obj){
                obj.version = 0 ;
            },
            beforeValidate: function(obj){
                if(obj.isNewRecord){
                    console.log('first');
                    obj.version = 0 ; 
                }else{
                    console.log('not first');
                    obj.version = obj.version + 1 ;
                }
            }
        }
    });
};

我們在dbConn.js中,定義了defineModel方法,這個方法中,我們規範了,每個模型都要添加versioncreateUser以及updateUser三個字段、表名即爲模型名,以及數據的字符集爲utf8mb4等等。那麼,當我們要創建模型的時候,通過調用defineModel方法就可以達到統一規範的效果。如下模型代碼:

 

var Sequelize = require('sequelize');
var db = require('../dbConn.js');

module.exports = db.defineModel('project_master', {
    p_id: {
        type: Sequelize.BIGINT(11),
        primaryKey: true,
        allowNull: false,
        autoIncrement: true
    },
    p_name: Sequelize.STRING(100),
    p_academy: Sequelize.STRING(100),
    p_start_date: Sequelize.STRING(10),
    p_end_date: Sequelize.STRING(10),
    p_days: Sequelize.DECIMAL(10, 1),
    p_place: Sequelize.STRING(20),
    p_owner: Sequelize.STRING(10),
    p_operator: Sequelize.STRING(10),
    p_is_fee: Sequelize.BIGINT(1),
    p_state: Sequelize.BIGINT(2),  // 開啓,關閉
    p_bz: Sequelize.STRING(255),
});

這樣還有一個好處,那就是在模型定義裏,只需要寫業務相關的字段,與業務無關的一些通用字段(例如,時間戳等等)就全部放到規範裏。業務字段一目瞭然,顯得更加清晰。

2. 批量同步數據結構

上面介紹過,在同步數據結構的時候,我們可以通過sync方法進行同步,這裏介紹一種批量同步的方法。正常情況下,我們都會把數據模型放在一個目錄下models,於是我們在models目錄外,新建一個sync.js文件。

 

var sequelize = require('./dbConn.js').sequelize;
var fs = require('fs');
var files = fs.readdirSync(__dirname + '/models');
var js_files = files.filter((f)=>{
    return f.endsWith('.js');
}, files);
console.log(js_files);
module.exports = {};
for (var f of js_files) {
    console.log(`import model from file ${f}...`);
    var name = f.substring(0, f.length - 3);
    module.exports[name] = require(__dirname + '/models/' + f);
}
sequelize.sync();

該方法的本質其實就是自動找出models目錄下,所有以js結尾的文件,引入並執行sync()方法。命令行執行node db/sync.js後,效果如下圖:

sync

再查看數據庫情況,可以發現models目錄下的所有模型全部創建成功。

image.png

3. 數據表關係結構

我們知道數據表的關係有一對一一對多以及多對多的關係結構。一般 ORM 框架都會提供與之對應的對象方法,當然 Sequelize 也不例外。實戰項目,直接上代碼說話。新建一個relation.js 文件,用來定義模型之間的關係,代碼如下:

 

// 項目表
var ProjectMaster = require('./models/Project-master');
var ProjectCost = require('./models/Project-cost');
var ProjectState = require('./models/Project-state');
// 費用明細表
var TeachFee = require('./models/Detail-teach-fee.js');

ProjectMaster.hasOne(ProjectState, {foreignKey: 'p_id', as: 'State'});
ProjectMaster.hasOne(ProjectCost, {foreignKey: 'p_id', as: 'Cost'});
ProjectMaster.hasMany(TeachFee, {foreignKey: 'p_id', as: 'TeachFee'});

module.exports = {
    ProjectMaster: ProjectMaster,
    ProjectCost: ProjectCost,
    ProjectState: ProjectState,
    TeachFee: TeachFee
};

這裏介紹一對一,一對多的關係,示例中,ProjectMasterProjectCost, ProjectState是一對一的關係,於是我們使用hasOne的方法,並且指定字段p_id爲連接外鍵。而TeachFee是費用明細表,與項目表是一對多的關係,於是使用hasMany的方法,同樣指定外鍵。

當定義完數據表關係後,我們重新編輯同步數據表方法,因爲我們需要使用新定義的關係模型,新建sync2.js文件,代碼如下:

 

var sequelize = require('./dbConn.js').sequelize;
var relation = require('./relation.js');

module.exports = {
  ProjectMaster: relation.ProjectMaster,
  ProjectCost: relation.ProjectCost,
  ProjectState: relation.ProjectState,
  TeachFee: relation.TeachFee
};

sequelize.sync({
    force: true      // 強制同步
});

執行node db/sync2.js後,在表結構中,你會發現出現的外鍵關聯關係。上述代碼會在ProjectMasterProjectCost以及TeachFee模型對應的數據表中,新增了外鍵關聯。如下狀態表的情況。

 

CREATE TABLE `project_state` (
  `p_state_id` bigint(11) NOT NULL AUTO_INCREMENT,
  `p_id` bigint(11) DEFAULT NULL,
  `p_state_teach` bigint(2) DEFAULT NULL,
  `p_state_place` bigint(2) DEFAULT NULL,
  `p_state_stay` bigint(2) DEFAULT NULL,
  `p_state_catering` bigint(2) DEFAULT NULL,
  `p_state_goods` bigint(2) DEFAULT NULL,
  `p_state_clean` bigint(2) DEFAULT NULL,
  `p_state_report` bigint(2) DEFAULT NULL,
  `version` bigint(20) DEFAULT NULL,
  `createUser` varchar(255) NOT NULL,
  `updateUser` varchar(255) NOT NULL,
  `createdAt` datetime NOT NULL,
  `updatedAt` datetime NOT NULL,
  `deletedAt` datetime DEFAULT NULL,
  PRIMARY KEY (`p_state_id`),
  KEY `p_id` (`p_id`),
  CONSTRAINT `project_state_ibfk_1` FOREIGN KEY (`p_id`) REFERENCES `project_master` (`p_id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

創建完表之後,接下來的問題就是我們如何使用?這裏給個建議方案,我們在db目錄下,新建一個api的目錄,用來存放數據庫調用接口。我們把所有對數據庫的操作,全部寫成方法,供後臺路由調用。例如,對項目的查詢,刪除,修改等等操作,都定義成一個一個的方法。在api目錄下,新建projectModel.js文件,用來定義對項目的一些數據庫操作。代碼如下:

 

var sequelize = require('../dbConn.js').sequelize;

var ProjectMaster = require('../relation.js').ProjectMaster;
var ProjectCost = require('../relation.js').ProjectCost;
var ProjectState = require('../relation.js').ProjectState;

module.exports = {
    // 單表:僅更新項目表
    updateProject: function(data, id, callback){
        ProjectMaster.update(data ,{where: {p_id: id}}).then(function(p){
            callback();
        });
    }
    // 雙表:查找成本表
    getCostList: function(start, end, callback){
        ProjectMaster.findAll({
            include: [{
                model: ProjectCost,
                as: 'Cost',
            }],
            where: {
                p_start_date: {
                    $lte: end,
                    $gte: start
                }
            },
            order: [sequelize.literal('p_start_date')]
        }).then(function(p){
            callback(p);
        });
    },
    // 雙表:添加項目,同時在狀態表添加項目狀態
    addProject: function(data, callback){
        ProjectMaster.create(data).then(function(p){
            var state = ProjectState.build({
                p_state_teach: 1,
                p_state_stay: 1,
                p_state_catering: 1,
                p_state_place: 1,
                p_state_goods: 1,
                p_state_clean: 1,
                p_state_report: 1,
                createUser: data.createUser,
                updateUser: data.updateUser,
            });
            p.setState(state);
            callback(p);
        });
    },
};

由於對數據庫的操作方法較多,這裏用3個示例方法來介紹。

  1. 第一個爲單表操作,更新項目表。增刪改查的一些基本方法,建議查看官方文檔;
  2. 第二個爲雙表聯合查詢,通過include參數來關聯模型,得到的結果中會包含一個Cost對象,包含ProjectCost的模型數據;
  3. 第三個爲添加項目的時候,同時添加狀態表,這裏用到關係模型中的set方法,創建項目主數據表後,通過關聯關係,使用set方法自動在State表中insert數據。

總結

本次Chat主要分享了 ORM 模型的簡單介紹,以及Node Web 開發中,Sequelize 框架的初步使用,最後介紹了,在實際項目中,Sequelize 的一些操作方法及技巧。

由於時間關係,就介紹到這裏,關於 Sequelize 框架,沒有講到的內容還是挺多的,還是建議多翻官方文檔,會有更大的收穫。這裏權當拋磚引玉了,最後謝謝觀看。

鏈接:https://www.jianshu.com/p/c148a3e9e39b

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