1. 程式人生 > 實用技巧 >玩轉Node.js-Sequelize基礎

玩轉Node.js-Sequelize基礎

Sequelize是什麼?

Sequelize 是一個基於 promise 的 Node.js ORM, 目前支援 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有強大的事務支援, 關聯關係, 預讀和延遲載入,讀取複製等功能。

Sequelize 遵從 語義版本控制。 支援 Node v10 及更高版本以便使用 ES6 功能。

說簡單點sequelize就是幫助我們操作資料庫的一個ORM(Object–relational mapping,物件關係對映)框架,我們使用mysql2操作資料庫,要使用各種原生的sql語句去操作,功能簡單還好說,功能複雜龐大就會顯得很麻煩,繁瑣,畢竟要寫大量的原生sql語句,sequelize的出現幫助我們只需要通過物件的方式就可以完成資料庫表建立、設計、資料的CRUD操作,簡化開發、提高效率。

中文文件:https://www.sequelize.com.cn/

安裝

Sequelize 的使用可以通過 npm (或 yarn).

npm install --save sequelize

你還必須手動為所選資料庫安裝驅動程式:

# 選擇以下之一:
$ npm install --save pg pg-hstore # Postgres
$ npm install --save mysql2
$ npm install --save mariadb
$ npm install --save sqlite3
$ npm install --save tedious # Microsoft SQL Server

因為我們要操作的是mysql,所以這個地方我們選擇mysql2。

連線到資料庫

const Sequelize = require('sequelize');
// 建立Sequelize的例項物件並配置連線資訊
const sequelize = new Sequelize({
  host: '127.0.0.1',
  username: 'root',
  password: 'root',
  database: 'todos',
  dialect: 'mysql',
  timezone: 'Asia/Shanghai' //當我們向資料庫中寫入時間的時候,預設會根據系統當前所在時區進行設定
})
// 測試連線
sequelize.authenticate().then(_=>{
  console.log("資料庫連線成功!");
}).catch(err=>{
  console.log("資料庫連線失敗! ", err);
})

new Sequelize options:
host:主機,預設localhost
port:埠,預設3306
dialect:資料庫型別,預設mysql,必填
timezone:時區,影響資料庫日期時間格式的值,格式:+08:00 或 字串格式

什麼是模型?

用來表述(描述)資料庫表字段資訊的物件,每一個模型物件表示資料庫中的一個表,後續對資料庫的操作都是通過對應的模型物件來完成的。

模型物件

建立模型物件

public define(modelName: String, attributes: Object, options: Object): Model

  • modelName:模型名稱,自定義
  • attributes:模型中包含的資料,每一個數據對映對應表中的每一個欄位
  • options:模型(表)的設定
  /**
   * 資料庫連線完成以後,需要確定操作的表
   * 使用ORM,不需要通過sql來操作表,而是通過物件來操作
   * 給每一個要操作的表定義一個物件 - 模型 Model
   */
  const UserModel = sequelize.define('User', {
    // 描述表中對應的欄位資訊
    // 物件的key預設對應這表的column欄位
    id: {
      // 每一個欄位的資訊
      type: Sequelize.INTEGER(10), // 整數型別
      allowNull: false, // 不允許為空
      primaryKey: true, // 設定為主鍵
      autoIncrement: true // 允許自增
    },
    username: {
      type: Sequelize.STRING(255),
      allowNull: false,
      defaultValue: 0
    },
    age: {
      type: Sequelize.TINYINT,
      allowNull: false,
      defaultValue: 0
    },
    gender: {
      type: Sequelize.ENUM(['男', '女', '預設']), // 注意列舉型別為陣列
      allowNull: false,
      defaultValue: '男'
    }
  }, {
    // 用來設定欄位以外的其他資訊
    timestamps: false,
    paranoid: false,
    freezeTableName: true,
    tableName: 'user',
    indexes: [{
      name: 'uname',
      fields: ['username']
    }, {
      name: 'age',
      fields: ['age']
    }]
  })

attributes 設定

attributes是一個物件,裡面包含了對應的表中的欄位資訊,key表示欄位名稱,值是欄位的具體描述。

欄位值描述

type:			欄位型別,String|DataTypes
allowNull:		是否允許為空,預設為true
defaultValue:	預設值,預設為null
unique:			值唯一,預設為false
primaryKey:		是否為主鍵,預設為false
field:		    資料庫中欄位的實際名稱
autoIncrement:	是否自增,預設false
get:		   欄位的getter,函式
set:            欄位的setter,函式
validate:       物件,對當前欄位值發生改變的時候進行驗證

模型額外配置

timestamps:是否給每條記錄新增 createdAt 和 updatedAt 欄位,並在新增新資料和更新資料的時候自動設定這兩個欄位的值,預設為true

paranoid:設定 deletedAt 欄位,當刪除一條記錄的時候,並不是真的銷燬記錄,而是通過該欄位來標識,即保留資料,進行假刪除,預設為false

freezeTableName:禁用修改表名; 預設情況下,sequelize將自動將所有傳遞的模型名稱(define的第一個引數)轉換為複數。 預設為false

tableName:手動設定表的實際名稱

定義表的索引

indexes:Array

每個索引物件可以設定的值:
	name:索引名稱,預設模型名稱+欄位
	fields: Array<string>,索引欄位
	unique:唯一索引,預設false

增加索引的好處就是讓資料庫查詢資料的時候變得更快。

模型例項物件

模型例項物件的概念很好理解,模型物件的作用是操作某張資料表,模型例項物件的作用是操作這張資料表中的資料。

一個模型類對應一個表,一個模型例項物件就是一條對應的表記錄,通過操作這個物件來關聯操作對應的表中的資料,操作模型類就是操作表,操作模型類物件就是操作該表中的某條記錄。

模型類——表

模型例項——記錄

注意在資料庫中,我們通常會把記錄稱為資料,也會把資料稱為記錄,概念是一樣的。

建立模型例項物件

public static build(options: Object): Model | Model[]

options:一個物件,對應的就是表中的欄位(模型中定義的屬性),需要注意的是對該物件的操作不會立即反應到實際的資料庫中,需要通過後續的操作進行同步
  /**
   * 模型類 -> 表
   * 模型創建出來的物件 -> 表中某條記錄
   */
  // let Kimoo = new UserModel();    //建立了一個User的記錄

  let Kimoo = UserModel.build({    //和上面的new是一樣的
      // 欄位對應的值
      username: 'Kimoo',
      age: 30,
      gender: '男'
  });  

  // 通過new或者build出來的物件不會立即同步到資料庫中,需要使用後續的一些方法來同步

  await Kimoo.save();

模型例項物件CRUD操作

模型例項物件.get(key: String):獲取某個屬性(欄位)的值。

模型物件.set(key: String, value: any):設定某個屬性(欄位)的值。

模型物件.save():驗證該例項,如果通過驗證,則持久化到資料庫中。

模型物件.update(updates: Object):updates:要更新的欄位,呼叫該方法等同於呼叫.set()然後.save()。

模型物件.destroy():銷燬該例項(假刪除或真刪除)。

  // 建立資料模型例項物件 c4az6
  // let c4az6 = UserModel.build({
  //   username: 'c4az6',
  //   age: 20,
  //   gender: '男'
  // })

  // 獲取屬性
  /* console.log(`
  username: ${c4az6.get('username')}
  age: ${c4az6.get('age')}
  gender: ${c4az6.get('gender')}
  `); */

  // 設定屬性
  // let res = c4az6.set('age', 21);
  // console.log(res.dataValues);

  // 通過save方法同步資料到資料庫中
  // await c4az6.save();

  // 更新欄位資料
  let res = await UserModel.findById(17);
  console.log(res.dataValues);
  res.update({username: 'Alex'});

  let res2 = await UserModel.findById(18);
  res2.update({username: 'Elon', age: 40});
  console.log(res2.dataValues);
  // 銷燬記錄
  res2.destroy();

除了通過模型創建出來的例項對單條資料進行操作,也可以通過模型類對整個對應的表進行操作。

模型.findById(id: Number | String | Buffer):根據主鍵搜尋單條記錄,注意是根據主鍵。

findById這個API在6.x的版本中已經被替換為findByPk了

模型.findOne(options: Object):根據條件搜尋一條記錄 options.where:搜尋條件 Op操作。

模型.findOrCreate(options: Object):搜尋特定記錄或建立它(如果沒有對應記錄)options.where:搜尋條件。

模型.findAll(findOptions: Object):在資料庫中搜索多個記錄,返回所有資料。

  • findOptions.where:搜尋條件
  • findOptions.limit:記錄條數限制
  • findOptions.offset:記錄偏移
  • findOptions.order:記錄排序方式

模型.findAndCountAll(findOptions: Object):與findAll類似,但是返回值包含 count 屬性,返回資料與總計數。

  // 資料模型例項物件查詢相關操作
  // findById
  // let user = await UserModel.findById(17);
  // console.log(user.dataValues);

  // findOne 返回一個物件
  // let res = await UserModel.findOne({
  //   where: {
  //     id: 17
  //   }
  // })
  // console.log(res.dataValues);
  
  // 搜尋或建立特定記錄,如果不存在則建立,返回陣列
  // let res = await UserModel.findOrCreate({
  //   where: {
  //     id: 30,
  //     username: 'test'
  //   }
  // })
  // console.log(res[0].dataValues);

  // 搜尋多個記錄,返回資料和總記錄數, 返回陣列
  // 搜尋年齡大於30的所有記錄,這種物件巢狀物件的寫法真噁心,一旦條件變多程式碼可讀性會非常差
  // let res = await UserModel.findAll({
  //   where: {
  //     age: {
  //       [Op.gt]: 30
  //     }
  //   }
  // })
  // console.log(res.length);
  // res.map(item=>{console.log(item.dataValues)});

  // 與findAll一樣,但是在返回所有資料的基礎上添加了count統計總記錄數的欄位, 返回陣列
  let res = await UserModel.findAndCountAll()
  console.log(res.count)
  res.rows.map(item=>{console.log(item.dataValues)});

過濾查詢(Sequelize.Op)

更多Op規則參考sequelize API文件:https://sequelize.org/master/variable/index.html#static-variable-Op

  // 過濾查詢
  // let res = await UserModel.findAll({
  //   where: {
  //     // 單條件過濾
  //     // username: 'Alex',
  //     // 多條件 要麼年齡大於30,要麼性別為女
  //     [Sequelize.Op.or]: [
  //       {
  //         age: {
  //           [Sequelize.Op.gt]: 30
  //         }
  //       },
  //       {
  //         gender: '女'
  //       }
  //     ]
  //   }
  // })
  // res.map(item=>{console.log(item.dataValues)});

  // limit 限制記錄查詢
  // let res = await UserModel.findAll({
  //   limit: 5
  // });
  // for(let i=0; i<res.length; i++) {
  //   console.log(res[i].dataValues);
  // }

  // 分頁查詢 limit配合offset一起使用
  // let res = await UserModel.findAll({
  //   limit: 5,
  //   offset: 5
  // });
  // console.log(res);
  // res.some(item=>console.log(item.dataValues))

  // 排序查詢
  // 年齡按照降序規則來排序
  // let res = await UserModel.findAll({
  //   order: [['age', 'desc']]
  // });
  // res.forEach(item=>{console.log(item.dataValues)});

  // 返回總記錄數
  // let res = await UserModel.count();
  // console.log(res);

  // 返回2條記錄數和總記錄數
  // let res = await UserModel.findAndCountAll({
  //   limit: 2
  // });
  // console.log(res.count);
  // res.rows.forEach(item=>{console.log(item.dataValues)});

  // 計算gender欄位為男的年齡總和
  // let res = await UserModel.sum('age', {
  //   where: {
  //     gender: '男'
  //   }
  // });
  // console.log(res);

關聯查詢

1.首先給關聯的欄位定義外來鍵關係

references: {
	model: 關聯的外來鍵表,如User
	key: 關聯的外來鍵表的欄位,如id
}
  1. 在呼叫hasOne或hasMany等方法的時候,通過第二個引數設定物件:{foreignKey: 當前關聯表的欄位,如uid}
  2. 在查詢中使用 include 去設定關聯的外來鍵表模型,如:include: [MessageModel]
  // 關聯查詢
  // 注意:建立模型的前提是你已經有建好了這張表
  const MessageModel = sequelize.define('message', {
    id: {
      type: Sequelize.INTEGER(10),
      primaryKey: true,
      allowNull: true,
      autoIncrement: true
    },
    uid: { // 關聯其他表的欄位,把當前欄位定義為外來鍵
      type: Sequelize.INTEGER(10),
      defaultValue: 0,
      references: {
        model: UserModel,
        key: 'id'
      }
    },
    content: {
      type: Sequelize.STRING(255),
      allowNull: true,
      defaultValue: ''
    }
  }, {
    timestamps: false,
    freezeTableName: true, // 凍結表名稱
    tableName: 'message'
  })

  // Object.assign(data, {
  //   id: message.get('id'),
  //   uid: message.get('uid'),
  //   username: user.get('username'),
  //   age: user.get('age'),
  //   gender: user.get('gender'),
  //   content: message.get('content')
  // });
  // console.log(data);

  // MessageModel屬於UserModel模型物件
  // MessageModel.belongsTo(UserModel, {
  // // 關聯外來鍵
  //   foreignKey: 'uid'
  // });

  // let data2 = await MessageModel.findById(1, {
  //  // 設定查詢出來的資料包含UserModel資料
  //   include: [UserModel]
  // });
  // // console.log(data2);

  // console.log(`
  //   留言id:${data2.get('id')}
  //   留言人名稱:${data2.User.username}
  //   留言內容:${data2.get('content')}
  // `);

  // 關聯查詢與預載入
  // 首先給關聯的欄位定義外來鍵關係
  // UserModel包含MessageModel物件,hasMany表示包含多個
  UserModel.hasMany(MessageModel, {
    foreignKey: 'uid'
  });

  // 在查詢中使用include去設定關聯的外來鍵表模型,如:include:[MessageModel]
  let data3 = await UserModel.findById(5, {
    include: [MessageModel]
  });

  data3.messages.map(item => console.log(item.dataValues));