摘要:哈哈,這又是為什么呢細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn),的返回值是一個(gè)類型的,所以上邊并沒(méi)有屬性,的兩個(gè)屬性也是如此。我們通過(guò)在函數(shù)上邊添加一個(gè)范型的定義,并且添加限制保證傳入的范型類型一定是繼承自的,在返回值轉(zhuǎn)換其類型為,就可以實(shí)現(xiàn)功能了。
如果是經(jīng)常使用Node來(lái)做服務(wù)端開(kāi)發(fā)的童鞋,肯定不可避免的會(huì)操作數(shù)據(jù)庫(kù),做一些增刪改查(CRUD,Create Read Update Delete)的操作,如果是一些簡(jiǎn)單的操作,類似定時(shí)腳本什么的,可能就直接生寫(xiě)SQL語(yǔ)句來(lái)實(shí)現(xiàn)功能了,而如果是在一些大型項(xiàng)目中,數(shù)十張、上百?gòu)埖谋?,之間還會(huì)有一些(一對(duì)多,多對(duì)多)的映射關(guān)系,那么引入一個(gè)ORM(Object Relational Mapping)工具來(lái)幫助我們與數(shù)據(jù)庫(kù)打交道就可以減輕一部分不必要的工作量,Sequelize就是其中比較受歡迎的一個(gè)。
CRUD原始版 手動(dòng)拼接SQL先來(lái)舉例說(shuō)明一下直接拼接SQL語(yǔ)句這樣比較“底層”的操作方式:
CREATE TABLE animal ( id INT AUTO_INCREMENT, name VARCHAR(14) NOT NULL, weight INT NOT NULL, PRIMARY KEY (`id`) );
創(chuàng)建這樣的一張表,三個(gè)字段,自增ID、name以及weight。
如果使用mysql這個(gè)包來(lái)直接操作數(shù)據(jù)庫(kù)大概是這樣的:
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}) `) // 或者通過(guò)傳入一個(gè)Object的方式也可以做到 await connection.query(`INSERT INTO ${tableName} SET ?`, { name, weight }) connection.end()
看起來(lái)也還算是比較清晰,但是這樣帶來(lái)的問(wèn)題就是,開(kāi)發(fā)人員需要對(duì)表結(jié)構(gòu)足夠的了解。
如果表中有十幾個(gè)字段,對(duì)于開(kāi)發(fā)人員來(lái)說(shuō)這會(huì)是很大的記憶成本,你需要知道某個(gè)字段是什么類型,拼接SQL時(shí)還要注意插入時(shí)的順序及類型,WHERE條件對(duì)應(yīng)的查詢參數(shù)類型,如果修改某個(gè)字段的類型,還要去處理對(duì)應(yīng)的傳參。
這樣的項(xiàng)目尤其是在進(jìn)行交接的時(shí)候更是一件恐怖的事情,新人又需要從頭學(xué)習(xí)這些表結(jié)構(gòu)。
以及還有一個(gè)問(wèn)題,如果有哪天需要更換數(shù)據(jù)庫(kù)了,放棄了MySQL,那么所有的SQL語(yǔ)句都要進(jìn)行修改(因?yàn)楦鱾€(gè)數(shù)據(jù)庫(kù)的方言可能有區(qū)別)
關(guān)于記憶這件事情,機(jī)器肯定會(huì)比人腦更靠譜兒,所以就有了ORM,這里就用到了在Node中比較流行的Sequelize。
ORM是干嘛的首先可能需要解釋下ORM是做什么使的,可以簡(jiǎn)單地理解為,使用面向?qū)ο蟮姆绞?,通過(guò)操作對(duì)象來(lái)實(shí)現(xiàn)與數(shù)據(jù)庫(kù)之前的交流,完成CRUD的動(dòng)作。
開(kāi)發(fā)者并不需要關(guān)心數(shù)據(jù)庫(kù)的類型,也不需要關(guān)心實(shí)際的表結(jié)構(gòu),而是根據(jù)當(dāng)前編程語(yǔ)言中對(duì)象的結(jié)構(gòu)與數(shù)據(jù)庫(kù)中表、字段進(jìn)行映射。
就好比針對(duì)上邊的animal表進(jìn)行操作,不再需要在代碼中去拼接SQL語(yǔ)句,而是直接調(diào)用類似Animal.create,Animal.find就可以完成對(duì)應(yīng)的動(dòng)作。
Sequelize的使用方式首先我們要先下載Sequelize的依賴:
npm i sequelize npm i mysql2 # 以及對(duì)應(yīng)的我們需要的數(shù)據(jù)庫(kù)驅(qū)動(dòng)
然后在程序中創(chuàng)建一個(gè)Sequelize的實(shí)例:
const Sequelize = require("Sequelize") const sequelize = new Sequelize("mysql://root:jarvis@127.0.0.1:3306/ts_test") // dialect://username:password@host:port/db_name // 針對(duì)上述的表,我們需要先建立對(duì)應(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)會(huì)在animal后邊添加一個(gè)字母`s`表示負(fù)數(shù) freezeTableName: true, // 禁止自動(dòng)添加時(shí)間戳相關(guān)屬性 timestamps: false, }) // 然后就可以開(kāi)始使用咯 // 還是假設(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
拋開(kāi)模型定義的部分,使用Sequelize無(wú)疑減輕了很多使用上的成本,因?yàn)槟P偷亩x一般不太會(huì)去改變,一次定義多次使用,而使用手動(dòng)拼接SQL的方式可能就需要將一段SQL改來(lái)改去的。
而且可以幫助進(jìn)行字段類型的轉(zhuǎn)換,避免出現(xiàn)類型強(qiáng)制轉(zhuǎn)換出錯(cuò)NaN或者數(shù)字被截?cái)嗟纫恍┐中膶?dǎo)致的錯(cuò)誤。
通過(guò)定義模型的方式來(lái)告訴程序,有哪些模型,模型的字段都是什么,讓程序來(lái)幫助我們記憶,而非讓我們自己去記憶。
我們只需要拿到對(duì)應(yīng)的模型進(jìn)行操作就好了。
But,雖說(shuō)切換為ORM工具已經(jīng)幫助我們減少了很大一部分的記憶成本,但是依然還不夠,我們?nèi)匀恍枰滥P椭卸加心男┳侄?,才能在業(yè)務(wù)邏輯中進(jìn)行使用,如果新人接手項(xiàng)目,仍然需要去翻看模型的定義才能知道有什么字段,所以就有了今天要說(shuō)的真正的主角兒:sequelize-typescript
CRUD終極版 裝飾器實(shí)現(xiàn)模型定義Sequelize-typescript是基于Sequelize針對(duì)TypeScript所實(shí)現(xiàn)的一個(gè)增強(qiáng)版本,拋棄了之前繁瑣的模型定義,使用裝飾器直接達(dá)到我們想到的目的。
Sequelize-typescript的使用方式首先因?yàn)槭怯玫搅?b>TS,所以環(huán)境依賴上要安裝的東西會(huì)多一些:
# 這里采用ts-node來(lái)完成舉例 npm i ts-node typescript npm i sequelize reflect-metadata sequelize-typescript
其次,還需要修改TS項(xiàng)目對(duì)應(yīng)的tsconfig.json文件,用來(lái)讓TS支持裝飾器的使用:
{ "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true } }
然后就可以開(kāi)始編寫(xiě)腳本來(lái)進(jìn)行開(kāi)發(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ù)庫(kù)的鏈接、初始化模型 // app.ts import path from "path" import { Sequelize } from "sequelize-typescript" import Animal from "./models/animal" const sequelize = new Sequelize("mysql://root:jarvis@127.0.0.1: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不同的有這么幾點(diǎn):
模型的定義采用裝飾器的方式來(lái)定義
實(shí)例化Sequelize對(duì)象時(shí)需要指定對(duì)應(yīng)的model路徑
模型相關(guān)的一系列方法都是支持Promise的
如果在使用過(guò)程中遇到提示XXX used before model init,可以嘗試在實(shí)例化前邊添加一個(gè)await操作符,等到與數(shù)據(jù)庫(kù)的連接建立完成以后再進(jìn)行操作
但是好像看起來(lái)這樣寫(xiě)的代碼相較于Sequelize多了不少呢,而且至少需要兩個(gè)文件來(lái)配合,那么這么做的意義是什么的?
答案就是OOP中一個(gè)重要的理念:__繼承__。
因?yàn)?b>TypeScript的核心開(kāi)發(fā)人員中包括C#的架構(gòu)師,所以TypeScript中可以看到很多類似C#的痕跡,在模型的這方面,我們可以嘗試?yán)美^承減少一些冗余的代碼。
比如說(shuō)我們基于animal表又有了兩張新表,dog和bird,這兩者之間肯定是有區(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我們有一個(gè)腿leg數(shù)量的描述,關(guān)于bird我們有了翅膀wing和爪子claw數(shù)量的描述。
特意讓兩者的特殊字段數(shù)量不同,省的有杠精說(shuō)可以通過(guò)添加type字段區(qū)分兩種不同的動(dòng)物 :p
如果要用Sequelize的方式,我們就要將一些相同的字段定義define三遍才能實(shí)現(xiàn),或者說(shuō)寫(xiě)得靈活一些,將define時(shí)使用的Object抽出來(lái)使用Object.assign的方式來(lái)實(shí)現(xiàn)類似繼承的效果。
但是在Sequelize-typescript就可以直接使用繼承來(lái)實(shí)現(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 } // 接下來(lái)就是繼承的使用了 // /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 }
有一點(diǎn)需要注意的:每一個(gè)模型需要多帶帶占用一個(gè)文件,并且采用export default的方式來(lái)導(dǎo)出
也就是說(shuō)目前我們的文件結(jié)構(gòu)是這樣的:
├── models │?? ├── animal.ts │?? ├── bird.ts │?? └── dog.ts └── app.ts
得益于TypeScript的靜態(tài)類型,我們能夠很方便地得知這些模型之間的關(guān)系,以及都存在哪些字段。
在結(jié)合著VS Code開(kāi)發(fā)時(shí)可以得到很多動(dòng)態(tài)提示,類似findAll,create之類的操作都會(huì)有提示:
Animal.create通過(guò)繼承來(lái)復(fù)用一些行為({ abc: 1, // ^ abc不是Animal已知的屬性 })
上述的例子也只是說(shuō)明了如何復(fù)用模型,但是如果是一些封裝好的方法呢?
類似的獲取表中所有的數(shù)據(jù),可能一般情況下獲取JSON數(shù)據(jù)就夠了,也就是findAll({raw: true})
所以我們可以針對(duì)類似這樣的操作進(jìn)行一次簡(jiǎn)單的封裝,不需要開(kāi)發(fā)者手動(dòng)去調(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`來(lái)實(shí)現(xiàn)類似的效果了 await Animal.getList() // 返回一個(gè)JSON數(shù)組
同理,因?yàn)樯线呂覀兊膬蓚€(gè)Dog和Bird繼承自Animal,所以代碼不用改動(dòng)就可以直接使用getList了。
const results = await Dog.getList() results[0].leg // TS提示錯(cuò)誤
但是如果你像上邊那樣使用的話,TS會(huì)提示錯(cuò)誤的:[ts] 類型“Animal”上不存在屬性“l(fā)eg”。。
哈哈,這又是為什么呢?細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn),getList的返回值是一個(gè)Animal[]類型的,所以上邊并沒(méi)有leg屬性,Bird的兩個(gè)屬性也是如此。
所以我們需要教TS認(rèn)識(shí)我們的數(shù)據(jù)結(jié)構(gòu),這樣就需要針對(duì)Animal的定義進(jìn)行修改了,用到了 __范型__。
我們通過(guò)在函數(shù)上邊添加一個(gè)范型的定義,并且添加限制保證傳入的范型類型一定是繼承自Animal的,在返回值轉(zhuǎn)換其類型為T,就可以實(shí)現(xiàn)功能了。
class Animal { static async getList() { const results = await this.findAll({ raw: true, }) return results as T[] } } const dogList = await Dog.getList () // 或者不作任何修改,直接在外邊手動(dòng)as也可以實(shí)現(xiàn)類似的效果 // 但是這樣還是不太靈活,因?yàn)槟阋A(yù)先知道返回值的具體類型結(jié)構(gòu),將預(yù)期類型傳遞給函數(shù),由函數(shù)去組裝返回的類型還是比較推薦的 const dogList = await Dog.getList() as Dog[] console.log(dogList[0].leg) // success
這時(shí)再使用leg屬性就不會(huì)出錯(cuò)了,如果要使用范型,一定要記住添加extends Animal的約束,不然TS會(huì)認(rèn)為這里可以傳入任意類型,那么很難保證可以正確的兼容Animal,但是繼承自Animal的一定是可以兼容的。
當(dāng)然如果連這里的范型或者as也不想寫(xiě)的話,還可以在子類中針對(duì)父類方法進(jìn)行重寫(xiě)。
并不需要完整的實(shí)現(xiàn)邏輯,只需要獲取返回值,然后修改為我們想要的類型即可:
class Dog extends Animal { static async getList() { // 調(diào)用父類方法,然后將返回值指定為某個(gè)類型 const results = await super.getList() return results as Dog[] } } // 這樣就可以直接使用方法,而不用擔(dān)心返回值類型了 const dogList = await Dog.getList() console.log(dogList[0].leg) // success小結(jié)
本文只是一個(gè)引子,一些簡(jiǎn)單的示例,只為體現(xiàn)出三者(SQL、Sequelize和Sequelize-typescript)之間的區(qū)別,Sequelize中有更多高階的操作,類似映射關(guān)系之類的,這些在Sequelize-typescript中都有對(duì)應(yīng)的體現(xiàn),而且因?yàn)槭褂昧搜b飾器,實(shí)現(xiàn)這些功能所需的代碼會(huì)減少很多,看起來(lái)也會(huì)更清晰。
當(dāng)然了,ORM這種東西也不是說(shuō)要一股腦的上,如果是初學(xué)者,從個(gè)人層面上我不建議使用,因?yàn)檫@樣會(huì)少了一個(gè)接觸SQL的機(jī)會(huì)
如果項(xiàng)目結(jié)構(gòu)也不是很復(fù)雜,或者可預(yù)期的未來(lái)也不會(huì)太復(fù)雜,那么使用ORM也沒(méi)有什么意義,還讓項(xiàng)目結(jié)構(gòu)變得復(fù)雜起來(lái)
以及,一定程度上來(lái)說(shuō),通用就意味著妥協(xié),為了保證多個(gè)數(shù)據(jù)庫(kù)之間的效果都一致,可能會(huì)拋棄一些數(shù)據(jù)庫(kù)獨(dú)有的特性,如果明確的需要使用這些特性,那么ORM也不會(huì)太適合
選擇最合適的,要知道使用某樣?xùn)|西的意義
最終的一個(gè)示例放在了GitHub上:notebook | typescript/sequelize
參考資料:
mysql | npm
sequelize
sequelize-typescript | npm
waht are the advantages of using an orm
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/97621.html
摘要:如何簡(jiǎn)潔優(yōu)雅的訪問(wèn)數(shù)據(jù)庫(kù)一前言誕生以來(lái)出現(xiàn)了一大批的框架如等等,前端可以不再依賴后端可以自己控制服務(wù)端的邏輯。今天我們就來(lái)說(shuō)說(shuō)前端在中如何操作數(shù)據(jù)庫(kù)。 nodejs如何簡(jiǎn)潔優(yōu)雅的訪問(wèn)mysql數(shù)據(jù)庫(kù)一、前言nodejs誕生以來(lái)出現(xiàn)了一大批的web框架如express koa2 egg等等,前端可以不再依賴后端可以自己控制服務(wù)端的邏輯。今天我們就來(lái)說(shuō)說(shuō)前端在nodejs中如何操作mysq...
摘要:最近在公司接觸到了的框架,研究了一下官方文檔,做了以下整理其他定義方法字段類型是否允許為字段是否自定義表名是否需要增加字段不需要字段將字段改個(gè)名將字段改名同時(shí)需要設(shè)置為此種模式下,刪除數(shù)據(jù)時(shí)不會(huì)進(jìn)行物理刪除,而是設(shè)置為當(dāng)前時(shí)間 最近在公司接觸到了sequelize(Nodejs的ORM框架),研究了一下官方文檔,做了以下整理 Models Definition let DeviceIn...
閱讀 2308·2021-11-17 09:33
閱讀 2840·2021-11-12 10:36
閱讀 3476·2021-09-27 13:47
閱讀 963·2021-09-22 15:10
閱讀 3563·2021-09-09 11:51
閱讀 1489·2021-08-25 09:38
閱讀 2810·2019-08-30 15:55
閱讀 2665·2019-08-30 15:53