簡單理解他就是一款幫助咱們操做數據庫的工具, nest.js
對他作了很好的集成, 雖然它的官網寫的挺全的可是實際開發起來仍是不太夠, 而且裏面有大坑
我會把我知道的都列出來, 這篇也會把一些常見的解決方案寫出來。前端
yarn add @nestjs/typeorm typeorm mysql2 -S
/share/src/app.module.ts
mysql
import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [ TypeOrmModule.forRoot({ port: 3306, type: 'mysql', username: 'root', host: 'localhost', charset: 'utf8mb4', password: '19910909', database: 'learn_nest', synchronize: true, autoLoadEntities: true, }),], // ...
mysql
, database
是庫名。imports
裏面定義多個 TypeOrmModule.forRoot
能夠操做多個庫, 多個時還須要填寫不一樣的name
屬性。synchronize
自動載入的模型將同步。autoLoadModels
模型將自動載入。當前的數據庫:git
// 控制檯裏輸入建立命令 nest g module modules/goods nest g controller modules/goods nest g service modules/goods
/share/src/modules/goods/goods.controller.ts
sql
import { Controller, Get } from '@nestjs/common'; import { GoodsService } from './goods.service'; @Controller('goods') export class GoodsController { constructor( private readonly goodsService: GoodsService ) {} @Get() getList() { return this.goodsService.getList(); } }
實體其實就是對應了一張表, 這個實體的class名字必須與表名對應, 新建entity文件夾 /share/src/modules/goods/entity/goods.entity.ts
:數據庫
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Goods { @PrimaryGeneratedColumn() id: number; @Column() name: string; }
@PrimaryGeneratedColumn()
裝飾了id爲主鍵, 類型爲數字。@Column()
裝飾普通行, 類型爲字符串, 更多細節後面再講。nest
自身設計的還不是很好, 引入搞得好麻煩 /share/src/modules/goods/goods.module.ts
:數組
import { Module } from '@nestjs/common'; import { GoodsController } from './goods.controller'; import { GoodsService } from './goods.service'; import { Goods } from './entity/goods.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [TypeOrmModule.forFeature([Goods])], controllers: [GoodsController], providers: [GoodsService] }) export class GoodsModule { }
forFeature()
方法定義在當前範圍中註冊哪些存儲庫。/share/src/modules/goods/goods.service.ts
:app
import { Injectable, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Goods } from './entity/goods.entity' import { Repository } from 'typeorm'; @Injectable() export class GoodsService { constructor( @InjectRepository(Goods) private goodsRepository: Repository<Goods> ) { } getList() { return this.goodsRepository.find() } }
@InjectRepository()
裝飾器將goodsRepository
注入GoodsService
中。Repository
都自帶屬性, 這裏使用了自帶的find
方法後面會舉例出更多。重點
)滿紙荒唐言, 一把辛酸淚, 當時我被坑的不淺。async
以咱們上面設置的實體爲例:編輯器
export class Goods { @PrimaryGeneratedColumn() id: number; @Column() name: string; }
咱們初始化的表裏面name
字段對應的類型是varchar(45)
, 可是name: string;
這種方式初始化的類型是varchar(255)
, 此時類型是不一致的, typeorm
選擇清空咱們的name列
, 是的你沒聽錯name列
被清空了:ide
而且是隻要你運行nest
項目的時候就同步熱更新
了, 徹底無感, 甚至你都不知道被清空了, 若是此時是線上環境請準備點乾糧'跑路'吧。
不光是string
類型, 其餘任何類型只要對不上就全給你刪了, 毫無提示。
咱們不少時候數據庫都是已有數據的, 全新的空白數據庫空白表的狀況並非主流, 在typeorm
官網也並無找到很好的接入數據庫的方案, 所有都是冒着刪庫的危險在定義類型, 更有甚者你改到一半不當心自動保存
了, 那麼你的表就空了...
咱們不可能每次都是用空白數據庫開發, 這點真可貴很難人忍受。
第一種: 單獨定義
在/share/src/app.module.ts
配置連接數據庫時:
TypeOrmModule.forRoot({ //... entities: [Goods, User], }),],
你用到哪些實體, 就逐一在此處引入, 缺點就是咱們每寫一個實體就要引入一次不然使用實體時會報錯。
第二種:
自動加載咱們的實體,每一個經過forFeature()
註冊的實體都會自動添加到配置對象的entities數組中, forFeature()
就是在某個service
中的imports
裏面引入的, 這個是比較推薦的:
TypeOrmModule.forRoot({ //... autoLoadEntities: true, }),],
第三種:
自定義引入路徑, 這個竟然是官方推薦...
TypeOrmModule.forRoot({ //... entities: ['dist/**/*.entity{.ts,.js}'], }),],
當咱們使用上述第三種方式引入實體時, 一個超級bug出現了, 情景步驟以下:
user
的實體。goods.entity.ts
實體的文件更名爲user.entity.ts
。userName
, age
, status
等新屬性, 刪除了商品價格等舊屬性。Goods
類名改爲User
, 因爲編輯器失去焦點等緣由致使vscode
自動保存了。goods
表被清空了
, 是的你尚未在任何地方引用這個user.entity.ts
文件, 可是它已經生效了, 而且無聲無息的把你的goods
表清空了。 如此坑的配置方式, 居然在官網裏找到了3處推薦如此使用, 簡直無語。
這個多人開發簡直是噩夢, 互相刪表
的狀況逐漸出現, 一個實際的例子好比a同事
優化全部實體
的配置好比統一把varchar(255)
改爲varchar(45)
, 全部的相關數據都會被清空, 於此同時你發現了問題, 並把數據補充回來了, 但此時b同事
的電腦裏仍是varchar(255)
版本, 一塊兒開發時就會致使你無論怎麼改數據, 表裏的數據都會被反覆清除乾淨...
咱們團隊當時解決方案是, 每一個人都複製一份當前庫單獨進行開發, 幾我的開發就要有幾個不一樣的庫, 咱們的mysql
裏全是已本身姓名命名的庫。
每次git拉取代碼都要修改庫名, 不然會把其餘人的庫清空;
好比張三使用的是zhangsan_xxx
庫, 可是他同時開發幾個版本, 這幾個版本以前表的格式有差異, 那麼張三要使用zhangsan_xxx_1_1
, zhangsan_xxx_1_2
這種命名格式來進行多個庫的開發。
看完坑點別灰心, 該學還得學, 下面咱們介紹一下entity設置能夠設置的比較實用的類型:
import { Entity, Column, Timestamp, UpdateDateColumn, CreateDateColumn, PrimaryGeneratedColumn } from 'typeorm'; export enum GoodsStatus { NORMAL = 1, HOT = 2, OFFSHELF = 3, } @Entity() export class Goods { @PrimaryGeneratedColumn() id: number; @Column({ unique: true, nullable: false }) name: string; @Column({ length: 256, default: '暫無' }) remarks: string; @Column({ default: true }) isActive: boolean; @Column({ type: 'enum', enum: GoodsStatus, default: GoodsStatus.NORMAL, }) status: GoodsStatus; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) putDate: Timestamp; @CreateDateColumn() createDate: Timestamp; @UpdateDateColumn() updateDate: Timestamp; }
nullable: false
不能夠爲空。unique: true
惟一值, 不容許有重複的name
的值出現, 須要注意的是若是當前的表裏面已經有重複的name
了typeorm
會報錯, 因此若是設置失敗請檢查表內容。length: 256
限制字符的長度, 對應varchar(256)
。default: '暫無'
默認值, 要注意當你手動設置爲空字符串時並不會被設置爲默認值。type: 'enum
定義爲枚舉類型, enum: GoodsStatus
指定枚舉值, 當你賦予其非枚舉值時會報錯。type: 'timestamp'
定義類型爲時間格式, CURRENT_TIMESTAMP
默認就是建立時間。@CreateDateColumn()
這個自動就能夠爲咱們設置值爲建立時間。@UpdateDateColumn()
之後每次更新數據都會自動的更新這個時間值。 上面咱們已經將goodsRepository
注入到了GoodsService
裏面能夠直接使用:
constructor( @InjectRepository(Goods) private goodsRepository: Repository<Goods> ) { }
this.goodsRepository.find()
查詢goods
表的所有數據, 以及每條數據的信息。
name
, createDate
兩列數據:this.goodsRepository.find({ select: ['name', 'createDate'] })
this.goodsRepository.find({ where: { name: 'x2', isActive: false } })
或者
等於'x3'都會被匹配出來:this.goodsRepository.find({ where: [{ name: 'x2', }, { name: 'x3' }] })
this.goodsRepository.find({ order: { name: "DESC", createDate: "ASC" } })
skip
跳過1條, take
取出3條this.goodsRepository.find({ skip: 1, take: 3 })
like
模糊查詢名字裏帶有2的項, not
id不是1this.goodsRepository.find({ where: { id: Not(1), name: Like('%2%') } })
數據是數組形式, [0]
是匹配到的數組, [1]
是符合條件的總數可能與[0]
的長度不相同。
this.goodsRepository.findAndCount({ select: ['name'] });
只取配到的第一條, 而且返回形式爲對象而非數組:
this.goodsRepository.findOne({ select: ['name'] });
this.goodsRepository.findByIds([1, 2]);
這個就不展現了。
用戶傳入須要模糊匹配的name
值, 以及當前第n頁, 每頁s條, 總數total條。
async getList(query) { const { keyWords, page, pageSize } = query; const [list, total] = await this.goodsRepository.findAndCount({ select: ['name', 'createDate'], where: { name: Like(`%${keyWords}%`) }, skip: (page - 1) * pageSize, take: pageSize }) return { list, total } }
yarn add class-validator class-transformer -S
先創建一個簡單的新增dto模型/share/src/modules/goods/dto/create-goods.dto.ts
:
import { IsNotEmpty, IsOptional, MaxLength } from 'class-validator'; export class CreateGoodsDto { @IsNotEmpty() name: string; @IsOptional() @MaxLength(256) remarks: string; }
使用/share/src/modules/goods/goods.service.ts
create(body) { const { name, remarks } = body; const goodsDto = new CreateGoodsDto(); goodsDto.name = name; goodsDto.remarks = remarks; return this.goodsRepository.save(goodsDto) }
老樣子, 先創建一份更新的dto, 好比name是不能夠更新的就不寫name, /share/src/modules/goods/dto/updata-goods.dto.ts
:
import { MaxLength } from 'class-validator'; export class UpdataGoodsDto { @MaxLength(256) remarks: string; }
在控制器裏面就要限制用戶傳入的更新數據類型必須與dto相同/share/src/modules/goods/goods.controller.ts
:
@Put(':id') updata(@Param('id') id: string, @Body() updateRoleDto: UpdataGoodsDto) { return this.goodsService.updata(id, updateRoleDto); }
先找到對應的數據, 再進行數據的更新/share/src/modules/goods/goods.service.ts
async updata(id, updataGoodsDto: UpdataGoodsDto) { const goods = await this.goodsRepository.findOne(id) Object.assign(goods, updataGoodsDto) return this.goodsRepository.save(goods) }
同數據庫裏的一對一關係, 好比一個商品對應一個祕密廠家, 廠家是單獨一張表, 一塊兒來作下吧(這裏比喻不恰當, 當前現實意義不是重點):
nest g module modules/mfrs nest g controller modules/mfrs nest g service modules/mfrs
在 /share/src/modules/mfrs/entity/mfrs.entity.ts
import { Entity, Column, Timestamp, CreateDateColumn, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Mfrs { @PrimaryGeneratedColumn('uuid') id: number; @Column() msg: string; @CreateDateColumn() createDate: Timestamp; }
uuid
的加密類型。在咱們的商品表裏面/share/src/modules/goods/entity/goods.entity.ts
加上一個與mfrs
表對應的行:
@OneToOne(() => Mfrs) @JoinColumn() mfrs: Mfrs
mfrs
而是叫mfrsId
在goods
模塊引入mfrs
模塊:
mfrs
模塊文件導出exports: [MfrsService]
goods
的模塊文件中引入imports: [MfrsModule]
goods.service.ts
的class類中注入mfrs
的服務, private readonly mfrsService: MfrsService,
在咱們建立商品時, 把這個mfrs信息也插入進去:
async create(body) { const { name, remarks } = body; const goodsDto = new CreateGoodsDto(); goodsDto.name = name; goodsDto.remarks = remarks; const mfrs = await this.mfrsService.create({ msg: `${name}: 是正品` }); goodsDto.mfrs = mfrs; return this.goodsRepository.save(goodsDto) }
好比我直接用find
方法查找goods
表, 並無查找出mfrs
的信息, 由於咱們須要配置相關的參數才能夠:
this.goodsRepository.findAndCount({ relations: ['mfrs'] })
假設一個商品goods對應一個樣式style, 一個style對應多個商品就能夠寫成以下形式:
在goods.entity.dto
裏面添加設配置:
@ManyToOne(() => Style, style => style.goods) style: Style;
在style.entity.dto
裏面添加設配置:
@OneToMany(() => Goods, goods => goods.style) goods: Goods[];
在create-goods.dto.ts
裏面增長以下, 這樣才能正常的建立新的goods:
@IsOptional() style: Style;
建立goods時如此改動:
async create(body) { const { name, remarks, styleId } = body; const goodsDto = new CreateGoodsDto(); goodsDto.name = name; goodsDto.remarks = remarks; const mfrs = await this.mfrsService.create({ msg: `${name}: 是正品` }); goodsDto.mfrs = mfrs; // 此處新增關聯關係 goodsDto.style = await this.mtyleService.findOne(styleId) return this.goodsRepository.save(goodsDto) }
多對多與上面差異也不大, 但有一個細節值得注意, 好比你用a表與b表多對多關聯,則會產生一張名爲a_b的表, 當儲存的時候a.b = [b1, b2]
這個樣子。
find
很簡潔好看, 但它沒法應對全部的場景:
QueryBuilder是 TypeORM 最強大的功能之一 ,它容許你使用優雅便捷的語法構建 SQL 查詢,執行並得到自動轉換的實體, 簡單理解其就是一種美觀上不如find可是比find能作的事要多的方法。
this.goodsRepository.createQueryBuilder('goods')
就能夠建立出來。
goods
商品name名稱
, keywords關鍵字
兩種屬性, 而且這兩個屬性都是單獨的表咱們須要去關聯, 此時咱們須要模糊匹配功能。重點
)goods有一個屬性maintainers
是一個維護者的集合, 爲數組類型, 大概長這樣[{id:1, name:'張三'}, {id:2, name:'李四'}]
。重點
) 好比當前用戶的id爲9,咱們須要剔除掉maintainers
數組中的id不爲9的數據。這個語句大概的樣子是這樣的:
const qb = this.goodsRepository .createQueryBuilder('goods') .leftJoinAndSelect('goods.keywords', 'goods_keyword') .leftJoinAndSelect('goods.name', 'goods_name') .leftJoinAndSelect('goods.maintainers', 'user'); const { keyword, name } = query; qb.where('goods.keyword LIKE :keyword', { keyword: `%${keyword}%` }); qb.orWhere('goods.name LIKE :name', { name: `%${name}%`, }); // 這裏的'user.id'指的是'user'表裏面查出的數據 qb.andWhere('user.id = :id', { id: 9 }); const [list, total] = await qb.getManyAndCount();
此次就是這樣, 快去突破自我吧, 但願和你一塊兒進步。