Sequelize手記 - (一)

最近開始接觸數據庫,現在普遍用的都是Mysql數據庫,簡單的瞭解了一下sql語句,沒有太深入的學習,然後就開始找相關的ORM框架,然後鎖定了Sequelize,個人感覺很強大,搜索了一些文檔,但是很讓人費解,講的每一部分都是那麼的官方,不太容易理解,記錄一下學習路程。本文檔以koa+Sequelize進行編碼測試。

準備工作

在嘗試使用Sequelize之前先確認是否安裝了Mysql數據庫,安裝node,這裏使用的Mysql 5.6版本。

首先要創建一個項目執行命令如下:

mkdir 文件夾名稱
cd 文件夾名稱
npm init -y     // 直接略過所有問答,全部採用默認答案

在開始之前首先要安裝相關依賴:

//  安裝 sequelize koa mysql2
npm install --save-dev sequelize koa mysql2

注意:這裏是mysql2

建立連接

創建main.js文件,分別引入koasequelize建立與mqsql數據庫連接。在實例化Sequelize時需要配置一些參數,第一個接收的參數時數據庫的名稱,第二個是用戶名,第三個是密碼,第四項是連接mysql的相關配置。

const Koa = require("koa");
const Sequelize = require("sequelize");
const app = new Koa();
const mysqlConfig ={
    host: 'localhost',  //  接數據庫的主機
    port: '3306',       //  接數據庫的端口
    protocol: 'tcp',    //  連接數據庫使用的協議
    dialect: 'mysql',   //  使用mysql
    pool: { 
        max: 5,         //  最大連接數量
        min: 0,         //  最小連接數量
        idle: 10000     //  連接空置時間(毫秒),超時後將釋放連接
    },
    retry: {        //  設置自動查詢時的重試標誌
        max: 3          //  設置重試次數
    },
    omitNull: false,    //  null 是否通過SQL語句查詢
    timezone: '+08:00'  //  解決時差 - 默認存儲時間存在8小時誤差
};
const sequelize = new Sequelize('aarontest', 'root', '123456',mysqlConfig );
app.listen(3000);

通過上述代碼就已經與Mysql建立了連接。這個地方沒有什麼可以講的,按照上述進行配置就可以了。test數據庫不一定要存在不存在也是可以正常建立連接的。配置參數還有很多,這裏只說了一些常用的,就不多贅述了。如果覺得上述方法比較麻煩可以通過也提供了其他方法進行連接:

const sequelize = new Sequelize('mysql://root:123456@localhost:3306/aarontest', {
    ...mysqlConfig
});

這種連接方式與mongoose連接方法差不多,以至於具體使用哪種方式還是要根據個人的編碼習慣來決定。

建立model

sequelize是通過define方法建立模型的,Model相當於數據庫中的表,該對象不能通過構造函數實例化,而只能通過sequelize.define()sequelize.import()方法創建。

const AaronTest = sequelize.define('project', {
  title: Sequelize.STRING,
  description: Sequelize.TEXT
})

通過上面的方法創建好model之後,使用Navicat(數據庫可視化工具)仍然無法看到我們所創建的表,尷尬。。。sequelize提供了一個sync()方法可以同步模型到數據庫。使用該方法的時候一定要注意所連接的數據庫一定要存在否則會報錯。

define方法接收三個參數,第一個參數爲表名稱,第二個爲所需要創建的數據庫字段,第三個參數是相關表配置。

const AaronTest = sequelize.define('project', {
  title: Sequelize.STRING,
  description: Sequelize.TEXT
});
AaronTest.sync();

這樣model所創建的字段就被同步到了數據庫中,同樣相對應的表也被創建好了,需要注意通過這種方式同步的表會在表名稱後面添加一個s作爲複數。同步數據庫是會默認添加兩個字段createdAtupdatedAt有的時候可能不需要這兩個字段。這個時候需要在第三個字段中添加timestampsfalse默認爲true

const AaronTest = sequelize.define('project', {
  title: Sequelize.STRING,
  description: Sequelize.TEXT
},{
  timestamps: false
})

上面可以看出通過Sequelize.STRING設置當前字段的類型,Sequelize提供了很多數據類型供我們進行使用:

類型 說明
STRING 將字段指定爲變長字符串類型,默認長度爲 255。Sequelize.STRING(64)
CHAR 將字段指定爲定長字符串類型,默認長度爲 255。Sequelize.CHAR(64)
TEXT 將字段指定爲(無)有限長度的文本列。可用長度:tiny, medium, long,Sequelize.TEXT('tiny')
INTEGER 32位整型,可用屬性:UNSIGNED,ZEROFILL,Sequelize.INTEGER('UNSIGNED')
BOOLEAN 小數,接受一個或兩個參數表示精度,Sequelize.BOOLEAN()
TIME 指定爲時間類型列,Sequelize.TIME()
DATE 指定爲日期時間類型列,Sequelize.DATE()
DATEONLY 指定爲日期類型列,Sequelize.DATEONLY()
HSTORE 指定爲鍵/值類型列,僅Postgres適用,Sequelize.HSTORE()
JSON 指定爲JSON字符串類型列,僅Postgres適用,Sequelize.JSON()
JSONB 指定爲預處理的JSON數據列,僅Postgres適用,Sequelize.JSONB()
NOW 一個表示當前時間戳的默認值,Sequelize.NOW()
UUID UUID類型列,其默認值可以爲UUIDV1或UUIDV4,Sequelize.UUID()
ENUM 枚舉類型,Sequelize.ENUM()
ARRAY 數組類型,僅Postgres適用,Sequelize.ARRAY()

設定model的時候在添加第三個參數可以對當前model進行二次設置,以使數據庫更加強壯,常用字段如下

  • timestamps:不要添加時間戳屬性 (updatedAt, createdAt)
  • paranoid:paranoid 屬性只在啓用 timestamps 時適用,不從數據庫中刪除數據,而只是增加一個 deletedAt 標識當前時間,我們常說的邏輯刪除
  • underscored: 不使用駝峯式命令規則,這樣會在使用下劃線分隔,updatedAt的字段名會是 updated_at
  • freezeTableName:禁止修改表名. 默認情況下sequelize會自動使用傳入的模型名(define的第一個參數)做爲表名,如果你不想使用這種方式你需要進行以下設置
  • tableName:定義表名
  • createdAt:不想使用 createdAt
  • updatedAt:想 updatedAt 的實際名爲'*'
  • deletedAt: 要將 deletedAt 設置爲 destroyTime (注意要啓用paranoid)

當創建model的時候可能需要有些附加屬性,比如主鍵,自增,不能爲null,默認值等等,可以在創建model的時候進行手動設置。

  • autoIncrement:是否自增
  • references:通過references選項可以創建外鍵
  • allowNull:設置 allowNull 選項爲 false 後,會爲列添加 NOT NULL 非空限制
  • defaultValue:設置默認值
  • type:字段類型
  • unique:添加唯一(unique)約束後插入重複值會報錯,unique屬性可以是boolean 或 string類型
  • primaryKey:設置爲主鍵
  • comment:字段描述
  • field:指定數據庫中的字段名

除了這些以外Sequelize在定義model的時候,還提供了一個validate屬性,可以爲添加的字段進行校驗處理:

字段 說明 值類型
is 存儲值必須滿足正則 正則
not 除正則之外的值 布爾
isEmail 是否爲郵箱 布爾
isUrl 檢查Url格式 布爾
isIP 檢查 IPv4 或 IPv6 格式 布爾
isIPv4 檢查 IPv4 布爾
isIPv6 檢查 IPv6 布爾
isAlpha 不能使用字母 布爾
isAlphanumeric 只允許字母數字字符 布爾
isNumeric 只能使用數字 布爾
isInt 只能是整數 布爾
isFloat 只能是浮點數 布爾
isDecimal 檢查數字 布爾
isLowercase 檢查小寫字母 布爾
isUppercase 檢查大寫字母 布爾
notNull 不允許null 布爾
isNull 只能爲null 布爾
notEmpty 不能空字符串 布爾
equals 只能使用指定值 字符串
contains 必須包含子字符串 字符串
notIn 不能是數組中的任意一個值 數組
isIn 只能是數組中的任意一個值 數組
notContains 不能包含子字符串 字符串
len 值的長度必在 2 和 10 之間 數組
isUUID 只能是UUID 數字
isDate 只能是日期字符串 布爾
isAfter 只能使用指定日期之後的時間 字符串
isBefore: 只能使用指定日期之前的時間 字符串
max 允許的最大值 數字
min 允許的最小值 數字
isArray 不能使用數組 布爾
isCreditCard 檢查是有效的信用卡 布爾

除了上面的校驗方法以外還提供了自定義校驗方法,使用isEven去定義一個函數,其函數的第一個參數就是所存如的值:

const AaronTest = sequelize.define('project', {
    title: Sequelize.STRING,
    description: {
    type:Sequelize.TEXT,
    validate:{
        isEven: function(value) {
            if(parseInt(value) % 2 != 0) {
                throw new Error('Only even values are allowed!')
            }
        }
    }
    }
},{
    timestamps: false
})

上面說過使用sequelize.import()也可以創建model這個方法其實是模型導入,通過文件導入模型定義。檢查模型是否已經定義。被導入的模型會被緩存,所以多次導入並不會重複加載,path表示要導入文件的路徑,如果使用相對路徑會自動轉換爲絕對路徑。

const AaronTest = sequelize.import('../model/user.js');

user.js

module.exports = function(sequelize, DataTypes) {
  return sequelize.define("project", {
    name: DataTypes.STRING,
    description: DataTypes.TEXT
  })
}

CRUD

CRUD:是指在做計算處理時的增加(Create)、讀取(Read)、更新(Update)和刪除(Delete)幾個單詞的首字母簡寫。crud主要被用在描述軟件系統中數據庫或者持久層的基本操作功能。

創建

創建數據的方法有很多種,這裏簡單的介紹一些常用的:

第一種:

先創建數據實例,然後調用實例的save方法,完成數據存儲。

const Aaron = AaronTest.build({
  'title': `後端 | ${Math.random()}`,
  'description': '技術部'
});
Aaron.save().then((result) => {
    //  成功
    console.log(result)
}).catch((error) => {
    //  失敗
    console.log(error)
})

第二種:

通過靜態create方法

const user = AaronTest.create({
  'title': `前端 | ${Math.random()}`,
  'description': '網絡部'
}).then(function(result) {
    //  成功
    console.log(result)
}).catch(function(error) {
    //  失敗
    console.log(error)
});
讀取

通過findAll方法讀取數據。

AaronTest.findAll({
  where: {
    description: '網絡部'
  },
  limit: 10,
  offset: 0
}).then(function(result) {
  // success
  console.log(result)
}).catch(function(error) {
  // error
  console.log(error)
});

通過上述方法創建的數據可以直接作爲返回結果返回給前臺,但是如果對查詢到的結果進行數據操作時不行的,因爲查詢到的結果是sequelize處理過的模型,如果想對其操作需要在參數中添加row:true屬性。

AaronTest.findAll({
  where: {
    description: '網絡部'
  },
  limit: 10,    //  查詢多少條
  offset: 0,    //  查詢開始位置
  raw:true
}).then(function(result) {
  // success
  console.log(result)
}).catch(function(error) {
  // error
  console.log(error)
});

添加row:true返回的則是一個沒有被包裝過的數組了。在項目過程中需要查詢一下當前所查詢的數據共有多少條返回給前端。

AaronTest.count({
  where:{
    description: '網絡部'
  }
}).then().then(function(result) {
  // success
  console.log(result)
}).catch(function(error) {
  // error
  console.log(error)
});

使用這種方法是確實可以查詢到想要的結果,但是無論是先查詢列表還是先查詢總數,都需要對數據庫進行兩次查詢很繁瑣。sequelize提供了一個很方便的方法。

AaronTest.findAndCountAll({
  where: {
    description: '網絡部'
  },
  limit: 10,
  offset: 0,
  raw:true,
  attributes:["id", "title"]    //  需要查詢出的字段
}).then(function(result) {
  // success
  console.log(result)
}).catch(function(error) {
  // error
  console.log(error)
});

通過上面的方法則可以查詢到總數以及條件範圍內的數據,一舉兩得。查詢結果返回的是一個json對象,其包括controws兩個屬性,分別是總數和數據。很舒服有沒有。

以上方式是查詢列表,查詢單條數據使用其他方法:

AaronTest.findOne({
  where:{
    id:6
  },
  raw:true,
  attributes:["id", "title"] 
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})
更新

修改數據可以直接調用靜態的update方法,通過where條件查詢,對其搜索到的數據進行查詢,並對查詢到的數據進行更改。

AaronTest.update({
  description: '前端部',
  title:`前端 | ${Math.random()}`
},{
  where:{
    description: "網絡部"
  }
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

該方法修改所有查詢的到的數據,返回結果爲數組形式,數據只有一個值,也就是數組的第0項,則是N條數據修改成功。

刪除

刪除操作通過destroy方法,同樣也是通過where條件查詢,對所查詢數據進行刪除。

AaronTest.destroy({
  where: {
    description: "UI部",
  }
}).then(function(result) {
  console.log(result)
}).catch(function(error) {
  console.log(error)
});

當刪除成功後,返回結果爲Number,刪除多少條數據,如果沒有刪除則會返回0。此方法屬於物理刪除,刪除後無法進行恢復。

查詢參數

CRUD操作過程中,都少不了的就是查詢,細心的應該可以看的出,上面的例子中查詢的時候多多少少的對其進行了一些小的改動。Sequelize中有兩種查詢:使用Model(模型)中的方法查詢和使用sequelize.query()進行基於SQL語句的原始查詢。上面用到的是Model查詢方式,接下來就詳細的介紹一些常用的參數以及其代表的意義。

attributes - 屬性與查詢字段

查詢時,如果只需要查詢模型的部分屬性,可以在通過在查詢選項中指定attributes實現。該選項是一個數組參數,在數組中指定要查詢的屬性即可,這個字段在上面進行查詢的時候已經使用過了。

AaronTest.findOne({
  where:{
    id:6
  },
  raw:true,
  attributes:["id", "title", "description"]
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

查詢屬性(字段)可以通過傳入一個嵌套數據進行重命名,這裏需要強調一下重命名所指的是對查詢出的數據鍵值進行重命名處理,而不是更改數據表中的字段名稱。

AaronTest.findOne({
  where:{
    id:2
  },
  attributes:["id", ["title","t"]],
  raw:true
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})
//  注意這裏t ↓
//  { id: 2, t: '前端 | 0.8765218593370694' }

通過sequelize.fn方法可以進行聚合查詢,個人覺得這個方法不太常用,但是還是簡單的介紹一下,這種查詢方式是向當前查詢內容中添加一個新的屬性,並且查詢的是列表還是查詢單條數據。

應用場景:

比如有很多商品,每個商品都有自己的分類,根據id進行查詢了一個商品,但是與其同類的商品有多少?就可以使用這個方法添加進去。下面例子中的count則是添加進去屬性的鍵。

AaronTest.findAll({
    where:{
        id:2
    },
    attributes: [
        "id", 
        ["title","t"],
        [sequelize.fn('COUNT', sequelize.col('id')), 'count']
    ],
    raw:true
}).then((result) => {
    console.log(result)
}).catch((error) => {
    console.log(error)
})

可能有一種情況,當前所需要查詢的表字段太多,但是隻有一兩個數據不想要,在attributes數組中添加很長的字段名稱,這樣會顯得代碼很臃腫。attributes不光可以爲數組,還可以爲對象在對象存在exclude這個屬性,這個屬性就是剔除掉那些不想要的屬性。

AaronTest.findOne({
  where:{
    id:2
  },
  attributes:{
    exclude: ['id'] 
  },
  raw:true
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})
where - 指定篩選條件

上面的那麼多例子中where出現的次數最多了,除了增加數據不需要,其他的都需要用到where條件,可以指定一個where選項以指定篩選條件,where是一個包含屬性/值對對象,sequelize會根據此對象生產查詢語句的篩選條件。

where的基礎用法也就向上面那樣,針對某些特定的條件進行查詢處理。

AaronTest.findOne({
  where:{
    id:2
  },
  attributes:{
    exclude: ['id'] 
  },
  raw:true
}).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

就像上面那樣簡單的查詢無法滿足所有的業務需求,Sequelize還提供了操作符以滿足更多的查詢條件,常用的操作符如下:

$and: {a: 5}                    // AND (a = 5)
$or: [{a: 5}, {a: 6}]           // (a = 5 OR a = 6)
$gt: 6,                         // > 6
$gte: 6,                        // >= 6
$lt: 10,                        // < 10
$lte: 10,                       // <= 10
$ne: 20,                        // != 20
$not: true,                     // IS NOT TRUE
$between: [6, 10],              // BETWEEN 6 AND 10
$notBetween: [11, 15],          // NOT BETWEEN 11 AND 15
$in: [1, 2],                    // IN [1, 2]
$notIn: [1, 2],                 // NOT IN [1, 2]
$like: '%hat',                  // LIKE '%hat'
$notLike: '%hat'                // NOT LIKE '%hat'
$iLike: '%hat'                  // 包含'%hat' (case insensitive) (PG only)
$notILike: '%hat'               // 不包含'%hat'  (PG only)
$like: { $any: ['cat', 'hat']}  // 像任何數組['cat', 'hat'] -也適用於iLike和notLike
limit/offset - 分頁與限制返回結果數

在進行列表查詢時,不能把查詢道德所有數據全部返回出去,需要對數據進行分頁處理。

// 獲取 10 條數據(實例)
AaronTest.findAll({ limit: 10 })
// 跳過 8 條數據(實例)
AaronTest.findAll({ offset: 8 })
// 跳過 5 條數據並獲取其後的 5 條數據(實例)
AaronTest.findAll({ offset: 5, limit: 5 })
查詢排序

order選項用於查詢結果的排序數據。排序時應該傳入一個包含屬性-排序方向的元組/數組,以保證正確的轉義:

AaronTest.findAll({
  order: [
    // 轉義 username 並對查詢結果按 DESC 方向排序
    ['username', 'DESC'],
    // 按 max(age) 排序
    sequelize.fn('max', sequelize.col('age')),
    // 按 max(age) DESC 排序
    [sequelize.fn('max', sequelize.col('age')), 'DESC'],
    // 按 otherfunction(`col1`, 12, 'lalala') DESC 排序
    [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
    // 按相關聯的User 模型的 name 屬性排序
    [User, 'name', 'DESC'],
    // 按相關聯的User 模型的 name 屬性排序並將模型起別名爲 Friend
    [{model: User, as: 'Friend'}, 'name', 'DESC'],
    // 按相關聯的User 模型的嵌套關聯的 Company 模型的 name 屬性排序
    [User, Company, 'name', 'DESC'],
  ]
  // 以下所有聲明方式都會視爲字面量,應該小心使用
  order: 'convert(user_name using gbk)'
  order: 'username DESC'
  order: sequelize.literal('convert(user_name using gbk)')
})

上面說的這些對於SQL語句瞭解一些,都是很容理解,有些API不常用也就沒些,詳細可以查看中文文檔。

SQL語句查詢

原始查詢中有兩種替換查詢參數的方法,以:開頭的參數的形式替換或以不命名以?替換。在選項對象中傳遞參數:

  • 如果傳遞一個數組,? 會按數組的順序被依次替換
  • 巢傳遞一個對象,:key將會用對象的鍵替換。如果對象中未找到指定鍵,則會引發異常(反之亦然)
//  這裏是sequelize,並不是model
sequelize.query('SELECT * FROM projects WHERE id = ?',
  { replacements: ['active'], type: sequelize.QueryTypes.SELECT }
).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)
})

總結

以上是對sequelizeapi進行了整理,雖然不太全面,熟練掌握上述API可以做一個項目了,有關sequelize的更多的用法我也在繼續爬坑中,可能文章中有些許錯誤,大家可以在下方留言,我會盡快做出改正。感謝大家的閱讀。

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