成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

使用TS+Sequelize實現(xiàn)更簡潔的CRUD

JayChen / 2173人閱讀

摘要:哈哈,這又是為什么呢細(xì)心的同學(xué)可能會發(fā)現(xiàn),的返回值是一個類型的,所以上邊并沒有屬性,的兩個屬性也是如此。我們通過在函數(shù)上邊添加一個范型的定義,并且添加限制保證傳入的范型類型一定是繼承自的,在返回值轉(zhuǎn)換其類型為,就可以實現(xiàn)功能了。

如果是經(jīng)常使用Node來做服務(wù)端開發(fā)的童鞋,肯定不可避免的會操作數(shù)據(jù)庫,做一些增刪改查(CRUD,Create Read Update Delete)的操作,如果是一些簡單的操作,類似定時腳本什么的,可能就直接生寫SQL語句來實現(xiàn)功能了,而如果是在一些大型項目中,數(shù)十張、上百張的表,之間還會有一些(一對多,多對多)的映射關(guān)系,那么引入一個ORMObject Relational Mapping)工具來幫助我們與數(shù)據(jù)庫打交道就可以減輕一部分不必要的工作量,Sequelize就是其中比較受歡迎的一個。

CRUD原始版 手動拼接SQL

先來舉例說明一下直接拼接SQL語句這樣比較“底層”的操作方式:

CREATE TABLE animal (
  id INT AUTO_INCREMENT,
  name VARCHAR(14) NOT NULL,
  weight INT NOT NULL, 
  PRIMARY KEY (`id`)
);

創(chuàng)建這樣的一張表,三個字段,自增ID、name以及weight。
如果使用mysql這個包來直接操作數(shù)據(jù)庫大概是這樣的:

const connection = mysql.createConnection({})
const tableName = "animal"

connection.connect()

// 我們假設(shè)已經(jīng)支持了Promise

// 查詢
const [results] = await connection.query(`
  SELECT 
    id,
    name,
    weight
  FROM ${tableName}
`)

// 新增
const name = "Niko"
const weight = 70
await connection.query(`
  INSERT INTO ${tableName} (name, weight)
  VALUES ("${name}", ${weight})
`)
// 或者通過傳入一個Object的方式也可以做到
await connection.query(`INSERT INTO ${tableName} SET ?`, {
  name,
  weight
})

connection.end()

看起來也還算是比較清晰,但是這樣帶來的問題就是,開發(fā)人員需要對表結(jié)構(gòu)足夠的了解。
如果表中有十幾個字段,對于開發(fā)人員來說這會是很大的記憶成本,你需要知道某個字段是什么類型,拼接SQL時還要注意插入時的順序及類型,WHERE條件對應(yīng)的查詢參數(shù)類型,如果修改某個字段的類型,還要去處理對應(yīng)的傳參。
這樣的項目尤其是在進(jìn)行交接的時候更是一件恐怖的事情,新人又需要從頭學(xué)習(xí)這些表結(jié)構(gòu)。
以及還有一個問題,如果有哪天需要更換數(shù)據(jù)庫了,放棄了MySQL,那么所有的SQL語句都要進(jìn)行修改(因為各個數(shù)據(jù)庫的方言可能有區(qū)別)

CRUD進(jìn)階版 Sequelize的使用

關(guān)于記憶這件事情,機器肯定會比人腦更靠譜兒,所以就有了ORM,這里就用到了在Node中比較流行的Sequelize。

ORM是干嘛的

首先可能需要解釋下ORM是做什么使的,可以簡單地理解為,使用面向?qū)ο蟮姆绞?,通過操作對象來實現(xiàn)與數(shù)據(jù)庫之前的交流,完成CRUD的動作。
開發(fā)者并不需要關(guān)心數(shù)據(jù)庫的類型,也不需要關(guān)心實際的表結(jié)構(gòu),而是根據(jù)當(dāng)前編程語言中對象的結(jié)構(gòu)與數(shù)據(jù)庫中表、字段進(jìn)行映射。

就好比針對上邊的animal表進(jìn)行操作,不再需要在代碼中去拼接SQL語句,而是直接調(diào)用類似Animal.create,Animal.find就可以完成對應(yīng)的動作。

Sequelize的使用方式

首先我們要先下載Sequelize的依賴:

npm i sequelize
npm i mysql2    # 以及對應(yīng)的我們需要的數(shù)據(jù)庫驅(qū)動

然后在程序中創(chuàng)建一個Sequelize的實例:

const Sequelize = require("Sequelize")
const sequelize = new Sequelize("mysql://root:[email protected]:3306/ts_test")
//                             dialect://username:password@host:port/db_name

// 針對上述的表,我們需要先建立對應(yīng)的模型:
const Animal = sequelize.define("animal", {
  id: { type: Sequelize.INTEGER, autoIncrement: true },
  name: { type: Sequelize.STRING, allowNull: false },
  weight: { type: Sequelize.INTEGER, allowNull: false },
}, {
  // 禁止sequelize修改表名,默認(rèn)會在animal后邊添加一個字母`s`表示負(fù)數(shù)
  freezeTableName: true,
  // 禁止自動添加時間戳相關(guān)屬性
  timestamps: false,
})

// 然后就可以開始使用咯
// 還是假設(shè)方法都已經(jīng)支持了Promise

// 查詢
const results = await Animal.findAll({
  raw: true,
})

// 新增
const name = "Niko"
const weight = 70

await Animal.create({
  name,
  weight,
})
sequelize定義模型相關(guān)的各種配置:docs

拋開模型定義的部分,使用Sequelize無疑減輕了很多使用上的成本,因為模型的定義一般不太會去改變,一次定義多次使用,而使用手動拼接SQL的方式可能就需要將一段SQL改來改去的。

而且可以幫助進(jìn)行字段類型的轉(zhuǎn)換,避免出現(xiàn)類型強制轉(zhuǎn)換出錯NaN或者數(shù)字被截斷等一些粗心導(dǎo)致的錯誤。

通過定義模型的方式來告訴程序,有哪些模型,模型的字段都是什么,讓程序來幫助我們記憶,而非讓我們自己去記憶。
我們只需要拿到對應(yīng)的模型進(jìn)行操作就好了。

這還不夠

But,雖說切換為ORM工具已經(jīng)幫助我們減少了很大一部分的記憶成本,但是依然還不夠,我們?nèi)匀恍枰滥P椭卸加心男┳侄?,才能在業(yè)務(wù)邏輯中進(jìn)行使用,如果新人接手項目,仍然需要去翻看模型的定義才能知道有什么字段,所以就有了今天要說的真正的主角兒:sequelize-typescript

CRUD終極版 裝飾器實現(xiàn)模型定義

Sequelize-typescript是基于Sequelize針對TypeScript所實現(xiàn)的一個增強版本,拋棄了之前繁瑣的模型定義,使用裝飾器直接達(dá)到我們想到的目的。

Sequelize-typescript的使用方式

首先因為是用到了TS,所以環(huán)境依賴上要安裝的東西會多一些:

# 這里采用ts-node來完成舉例
npm i ts-node typescript
npm i sequelize reflect-metadata sequelize-typescript

其次,還需要修改TS項目對應(yīng)的tsconfig.json文件,用來讓TS支持裝飾器的使用:

{
  "compilerOptions": {
+   "experimentalDecorators": true,
+   "emitDecoratorMetadata": true
  }
}

然后就可以開始編寫腳本來進(jìn)行開發(fā)了,與Sequelize不同之處基本在于模型定義的地方:

// /modles/animal.ts
import { Table, Column, Model } from "sequelize-typescript"

@Table({
  tableName: "animal"
})
export class Animal extends Model {
  @Column({
    primaryKey: true,
    autoIncrement: true,
  })
  id: number

  @Column
  name: string

  @Column
  weight: number
}

// 創(chuàng)建與數(shù)據(jù)庫的鏈接、初始化模型
// app.ts
import path from "path"
import { Sequelize } from "sequelize-typescript"
import Animal from "./models/animal"

const sequelize = new Sequelize("mysql://root:[email protected]:3306/ts_test")
sequelize.addModels([path.resolve(__dirname, `./models/`)])

// 查詢
const results = await Animal.findAll({
  raw: true,
})

// 新增
const name = "Niko"
const weight = 70

await Animal.create({
  name,
  weight,
})

與普通的Sequelize不同的有這么幾點:

模型的定義采用裝飾器的方式來定義

實例化Sequelize對象時需要指定對應(yīng)的model路徑

模型相關(guān)的一系列方法都是支持Promise

如果在使用過程中遇到提示XXX used before model init,可以嘗試在實例化前邊添加一個await操作符,等到與數(shù)據(jù)庫的連接建立完成以后再進(jìn)行操作

但是好像看起來這樣寫的代碼相較于Sequelize多了不少呢,而且至少需要兩個文件來配合,那么這么做的意義是什么的?
答案就是OOP中一個重要的理念:__繼承__。

使用Sequelize-typescript實現(xiàn)模型的繼承

因為TypeScript的核心開發(fā)人員中包括C#的架構(gòu)師,所以TypeScript中可以看到很多類似C#的痕跡,在模型的這方面,我們可以嘗試?yán)美^承減少一些冗余的代碼。

比如說我們基于animal表又有了兩張新表,dogbird,這兩者之間肯定是有區(qū)別的,所以就有了這樣的定義:

CREATE TABLE dog (
  id INT AUTO_INCREMENT,
  name VARCHAR(14) NOT NULL,
  weight INT NOT NULL, 
  leg INT NOT NULL,
  PRIMARY KEY (`id`)
);

CREATE TABLE bird (
  id INT AUTO_INCREMENT,
  name VARCHAR(14) NOT NULL,
  weight INT NOT NULL, 
  wing INT NOT NULL,
  claw INT NOT NULL,
  PRIMARY KEY (`id`)
);

關(guān)于dog我們有一個腿leg數(shù)量的描述,關(guān)于bird我們有了翅膀wing和爪子claw數(shù)量的描述。
特意讓兩者的特殊字段數(shù)量不同,省的有杠精說可以通過添加type字段區(qū)分兩種不同的動物 :p

如果要用Sequelize的方式,我們就要將一些相同的字段定義define三遍才能實現(xiàn),或者說寫得靈活一些,將define時使用的Object抽出來使用Object.assign的方式來實現(xiàn)類似繼承的效果。

但是在Sequelize-typescript就可以直接使用繼承來實現(xiàn)我們想要的效果:

// 首先還是我們的Animal模型定義
// /models/animal.ts
import { Table, Column, Model } from "sequelize-typescript"

@Table({
  tableName: "animal"
})
export default class Animal extends Model {
  @Column({
    primaryKey: true,
    autoIncrement: true,
  })
  id: number

  @Column
  name: string

  @Column
  weight: number
}

// 接下來就是繼承的使用了
// /models/dog.ts
import { Table, Column, Model } from "sequelize-typescript"
import Animal from "./animal"

@Table({
  tableName: "dog"
})
export default class Dog extends Animal {
  @Column
  leg: number
}

// /models/bird.ts
import { Table, Column, Model } from "sequelize-typescript"
import Animal from "./animal"

@Table({
  tableName: "bird"
})
export default class Bird extends Animal {
  @Column
  wing: number

  @Column
  claw: number
}

有一點需要注意的:每一個模型需要多帶帶占用一個文件,并且采用export default的方式來導(dǎo)出
也就是說目前我們的文件結(jié)構(gòu)是這樣的:

├── models
│?? ├── animal.ts
│?? ├── bird.ts
│?? └── dog.ts
└── app.ts

得益于TypeScript的靜態(tài)類型,我們能夠很方便地得知這些模型之間的關(guān)系,以及都存在哪些字段。
在結(jié)合著VS Code開發(fā)時可以得到很多動態(tài)提示,類似findAllcreate之類的操作都會有提示:

Animal.create({
  abc: 1,
// ^ abc不是Animal已知的屬性  
})
通過繼承來復(fù)用一些行為

上述的例子也只是說明了如何復(fù)用模型,但是如果是一些封裝好的方法呢?
類似的獲取表中所有的數(shù)據(jù),可能一般情況下獲取JSON數(shù)據(jù)就夠了,也就是findAll({raw: true})
所以我們可以針對類似這樣的操作進(jìn)行一次簡單的封裝,不需要開發(fā)者手動去調(diào)用findAll

// /models/animal.ts
import { Table, Column, Model } from "sequelize-typescript"

@Table({
  tableName: "animal"
})
export default class Animal extends Model {
  @Column({
    primaryKey: true,
    autoIncrement: true,
  })
  id: number

  @Column
  name: string

  @Column
  weight: number

  static async getList () {
    return this.findAll({raw: true})
  }
}

// /app.ts
// 這樣就可以直接調(diào)用`getList`來實現(xiàn)類似的效果了
await Animal.getList() // 返回一個JSON數(shù)組

同理,因為上邊我們的兩個DogBird繼承自Animal,所以代碼不用改動就可以直接使用getList了。

const results = await Dog.getList()

results[0].leg // TS提示錯誤

但是如果你像上邊那樣使用的話,TS會提示錯誤的:[ts] 類型“Animal”上不存在屬性“l(fā)eg”。
哈哈,這又是為什么呢?細(xì)心的同學(xué)可能會發(fā)現(xiàn),getList的返回值是一個Animal[]類型的,所以上邊并沒有leg屬性,Bird的兩個屬性也是如此。

所以我們需要教TS認(rèn)識我們的數(shù)據(jù)結(jié)構(gòu),這樣就需要針對Animal的定義進(jìn)行修改了,用到了 __范型__。
我們通過在函數(shù)上邊添加一個范型的定義,并且添加限制保證傳入的范型類型一定是繼承自Animal的,在返回值轉(zhuǎn)換其類型為T,就可以實現(xiàn)功能了。

class Animal {
  static async getList() {
    const results = await this.findAll({
      raw: true,
    })
    return results as T[]
  }
}

const dogList = await Dog.getList()
// 或者不作任何修改,直接在外邊手動as也可以實現(xiàn)類似的效果
// 但是這樣還是不太靈活,因為你要預(yù)先知道返回值的具體類型結(jié)構(gòu),將預(yù)期類型傳遞給函數(shù),由函數(shù)去組裝返回的類型還是比較推薦的
const dogList = await Dog.getList() as Dog[]

console.log(dogList[0].leg) // success

這時再使用leg屬性就不會出錯了,如果要使用范型,一定要記住添加extends Animal的約束,不然TS會認(rèn)為這里可以傳入任意類型,那么很難保證可以正確的兼容Animal,但是繼承自Animal的一定是可以兼容的。

當(dāng)然如果連這里的范型或者as也不想寫的話,還可以在子類中針對父類方法進(jìn)行重寫。
并不需要完整的實現(xiàn)邏輯,只需要獲取返回值,然后修改為我們想要的類型即可:

class Dog extends Animal {
  static async getList() {
    // 調(diào)用父類方法,然后將返回值指定為某個類型
    const results = await super.getList()
    return results as Dog[]
  }
}

// 這樣就可以直接使用方法,而不用擔(dān)心返回值類型了
const dogList = await Dog.getList()

console.log(dogList[0].leg) // success
小結(jié)

本文只是一個引子,一些簡單的示例,只為體現(xiàn)出三者(SQLSequelizeSequelize-typescript)之間的區(qū)別,Sequelize中有更多高階的操作,類似映射關(guān)系之類的,這些在Sequelize-typescript中都有對應(yīng)的體現(xiàn),而且因為使用了裝飾器,實現(xiàn)這些功能所需的代碼會減少很多,看起來也會更清晰。

當(dāng)然了,ORM這種東西也不是說要一股腦的上,如果是初學(xué)者,從個人層面上我不建議使用,因為這樣會少了一個接觸SQL的機會
如果項目結(jié)構(gòu)也不是很復(fù)雜,或者可預(yù)期的未來也不會太復(fù)雜,那么使用ORM也沒有什么意義,還讓項目結(jié)構(gòu)變得復(fù)雜起來
以及,一定程度上來說,通用就意味著妥協(xié),為了保證多個數(shù)據(jù)庫之間的效果都一致,可能會拋棄一些數(shù)據(jù)庫獨有的特性,如果明確的需要使用這些特性,那么ORM也不會太適合
選擇最合適的,要知道使用某樣?xùn)|西的意義

最終的一個示例放在了GitHub上:notebook | typescript/sequelize

參考資料:

mysql | npm

sequelize

sequelize-typescript | npm

waht are the advantages of using an orm

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97621.html

相關(guān)文章

  • nodejs如何簡潔優(yōu)雅訪問mysql數(shù)據(jù)庫

    摘要:如何簡潔優(yōu)雅的訪問數(shù)據(jù)庫一前言誕生以來出現(xiàn)了一大批的框架如等等,前端可以不再依賴后端可以自己控制服務(wù)端的邏輯。今天我們就來說說前端在中如何操作數(shù)據(jù)庫。 nodejs如何簡潔優(yōu)雅的訪問mysql數(shù)據(jù)庫一、前言nodejs誕生以來出現(xiàn)了一大批的web框架如express koa2 egg等等,前端可以不再依賴后端可以自己控制服務(wù)端的邏輯。今天我們就來說說前端在nodejs中如何操作mysq...

    jlanglang 評論0 收藏0
  • sequelize入門

    摘要:最近在公司接觸到了的框架,研究了一下官方文檔,做了以下整理其他定義方法字段類型是否允許為字段是否自定義表名是否需要增加字段不需要字段將字段改個名將字段改名同時需要設(shè)置為此種模式下,刪除數(shù)據(jù)時不會進(jìn)行物理刪除,而是設(shè)置為當(dāng)前時間 最近在公司接觸到了sequelize(Nodejs的ORM框架),研究了一下官方文檔,做了以下整理 Models Definition let DeviceIn...

    kidsamong 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<