1、typeorm在nest.js中的使用
环境依赖安装
npm install --save @nestjs/typeorm typeorm mysql2
在app.module.ts进行数据连接配置
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { snakeNameStrategy } from './shared/typeorm-snake-name-strategy';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: '127.0.0.1',
port: 3306,
username: 'root',
password: '',
database: 'even',
namingStrategy: snakeNameStrategy, // 定义表名与字段的命令策略
entities: [__dirname + '/**/*.entity{.ts,.js}'], // 定义表的数据模型
synchronize: false, // 是否关闭同步,通常建议关闭
}),
CourseModule,
]
})
export class AppModule {}
定义数据模型
import { Entity, Column, PrimaryColumn } from 'typeorm';
@Entity()
export class Tag {
@PrimaryColumn('char', { length: 36, nullable: false, comment: 'tag id' })
public id: string;
@Column('varchar', { length: 100, comment: 'tag title', unique: true })
public tagName: string;
@Column('int', { comment: 'create time', unsigned: true, nullable: false })
public createTime: number;
@Column('int', { comment: 'update time', unsigned: true, nullable: false })
public updateTime: number;
}
注意:如果需要在数据模型中定义表的关联,可以进行如下配置
在user表中进行定义
@OneToMany((type) => Info, (info) => info.user)
@JoinColumn({ name: 'user_id' })
public infos: Array<Info>;
在info表中进行定义
@ManyToOne((type) => User, (user) => user.infos)
@JoinColumn({ name: 'id' })
public user: User;
如果表与表中的字段定义的比较有规律的话,可以使用表的命名策略进行定义
定义表的命名策略
import { DefaultNamingStrategy, NamingStrategyInterface } from 'typeorm';
import { snakeCase } from 'typeorm/util/StringUtils';
class SnakeNameStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
public readonly name: 'SnakeName';
/**
* 表名
* @param entityClassName: 实体类名 `@Entity() class EntityClassName` 里的 `EntityClassName`
* @param userSpecifiedName: 实体 `@Entity({ name })` 里的 `name`
*/
public tableName(entityClassName: string, userSpecifiedName?: string): string {
// 自定义类名,不需要更改
return userSpecifiedName || snakeCase(entityClassName);
}
/**
* 字段名
* @param propertyName `@Column({ name }) fieldName` 里的 `fieldName`
* @param customName `@Column({ name })` 里的 `name`
* @param embeddedPrefixes
*/
public columnName(propertyName: string, customName: string, embeddedPrefixes: string[]): string {
const prefix = snakeCase(embeddedPrefixes ? embeddedPrefixes.concat('').join('_') : '');
// 自定义字段名,不需要更改
return prefix + (customName || snakeCase(propertyName));
}
}
export const snakeNameStrategy = new SnakeNameStrategy();
更多的命名策略可以参考: https://github.com/tonivj5/typeorm-naming-strategies
在服务中进行引入使用
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '@app/entities/user.entity'; // 引入entity数据模型
@Injectable()
export class CourseService {
// 注入user表的数据类,这时就可以使用userRepository这个句柄进行查询
public constructor(@InjectRepository(User) private userRepository: Repository<User>) {}
}
查询可以使用typeorm中的find选项进行查询, 也可以使用createQueryBuilder进行查询,具体如下
const subList = await this.courseLessonRepository
.createQueryBuilder('lesson')
.select(['lesson.folder as folderId', 'GROUP_CONCAT(lesson.id) as lessons'])
.where('lesson.deleteSign = :status')
.groupBy('folderId')
.getQuery();
const result = await this.courseFolderRepository
.createQueryBuilder('folder')
.select([
'folder.id as id',
'folder.folderName as name',
'folder.parent as parentId',
'lessonGroup.lessons',
])
.leftJoin(`(${subList})`, 'lessonGroup', 'lessonGroup.folderId = folder.id')
.where('folder.deleteSign = :status')
.setParameter('status', COURSE_DELETE_TAG_ALIVE)
.getRawMany();
// 注意: 以上使用的是关联查询,建议都使用数据模型里的字段,但是如果需要使用数据模型里的字段,需要设置别名,这样,使用别名来访问实现表与访问的字段解耦
在createQueryBuilder中insert多关系模型时可以用以下写法
public async addLesson(
id: string,
name: string,
folder: string,
parentId: string,
entrance: string,
) {
const folderEntity = await this.courseFolderRepository.findOne(parentId);
if (!folderEntity) throw new Error(`id 为 ${parentId} 的父级目录不存在`);
const folderMeta = await this.fileStoreMetaRepository.findOne(folder);
if (!folderMeta) throw new Error(`${folder} 课程文件夹meta不存在`);
await this.courseLessonRepository
.createQueryBuilder()
.insert()
.into(CourseLesson)
.values({
id,
lessonName: name,
folderAssetsId: folder,
folder: folderEntity, // 这里可以使用find出来的模型
entrancePage: () => `'${entrance}'`, // 也可以使用返回关联字段的值
createTime: getCurrentTime(),
updateTime: getCurrentTime(),
})
.execute();
}
除了以上的方法也可以使用以下的方法
const data = this.courseLessonRepository.create({
id,
lessionName: name,
folderAssetsId: folder,
folder: {id: folderId} // 这部份是做了关联了
createTime: getCurrentTime(),
updateTime: getCurrentTime()
})
await this.courseLessonRepository.save(data)
upsert的使用
public async addLesson(
id: string,
name: string,
folder: string,
parentId: string,
entrance: string,
) {
const folderEntity = await this.courseFolderRepository.findOne(parentId);
if (!folderEntity) throw new Error(`id 为 ${parentId} 的父级目录不存在`);
const folderMeta = await this.fileStoreMetaRepository.findOne(folder);
if (!folderMeta) throw new Error(`${folder} 课程文件夹meta不存在`);
await this.courseLessonRepository.upsert(
{
id,
lessonName: name,
folderAssetsId: folder,
folder: folderEntity,
entrancePage: () => `'${entrance}'`,
deleteSign: false,
createTime: getCurrentTime(),
updateTime: getCurrentTime(),
},
['deleteSign'],
);
}
await manager.upsert(User, [
{ externalId:"abc123", firstName: "Rizzrak" },
{ externalId:"bca321", firstName: "Karzzir" },
], ["externalId"]);
在服务中使用事务(这里使用的是装饰器的事务)
@Transaction()
public async deleteCourseFolder(
@TransactionManager() manage: EntityManager,
id: string,
): Promise<void> {
const list = await manage
.createQueryBuilder(CourseFolder, 'folder')
.select(['folder.id as id', 'folder.parent as parentId'])
.getRawMany();
const relationList: Array<string> = this.getAllRelationList(list, id);
if (relationList.length < 1) return;
await manage
.createQueryBuilder(CourseFolder, 'folder')
.update()
.set({ deleteSign: COURSE_DELETE_TAG_DELETE, updateTime: getCurrentTime() })
.where({ id: In(relationList) })
.execute();
await manage
.createQueryBuilder(CourseLesson, 'lesson')
.update()
.set({ deleteSign: COURSE_DELETE_TAG_DELETE, updateTime: getCurrentTime() })
.where({ folder: In(relationList) })
.execute();
}
注意:在调用的时候会报缺少参数的错误,这个时候就需要进行忽略, 还有其他的事务方法具体可以参考官网
在进行where中条件筛选的时候,可以使用关键字,比如IN
.where("user.name IN (:...names)", { names: [ "Timber", "Cristal", "Lina" ] })
注意:在使用findOption相关语法进行操作数据库时,如果需要看生成的mysql语句,那么需要在then或者catch中进行log出来
对upsert进行扩展
import { Injectable } from '@nestjs/common';
import { createQueryBuilder, EntityTarget, getConnection, EntityManager } from 'typeorm';
@Injectable()
export class OrmExtends {
// 根据模型印射数据的实际字段
public mapPropertyPathsToColumns(
target: EntityTarget<any>,
list: Array<string>,
): Array<string> {
return getConnection()
.getMetadata(target)
.mapPropertyPathsToColumns(list)
.map((col) => col.databaseName);
}
// 扩展的upsert方法
public extendUpsert(target: EntityTarget<any>, values: any, updateInclude: Array<string>) {
const mapList = this.mapPropertyPathsToColumns(target, updateInclude);
return createQueryBuilder().insert().into(target).values(values).orUpdate(mapList);
}
// 扩展事务性的upsert方法
public extendTransactionUpsert(
manage: EntityManager,
target: EntityTarget<any>,
values: any,
updateInclude: Array<string>,
) {
const mapList = this.mapPropertyPathsToColumns(target, updateInclude);
return manage.createQueryBuilder().insert().into(target).values(values).orUpdate(mapList);
}
2、如果需要在request中添加自定义属性,那么可以进行接口声明
import { Injectable, NestMiddleware, Module } from '@nestjs/common';
import { Request, Response } from 'express';
import { v4 } from 'uuid';
// 往request声明中添加requestId
declare module 'express' {
interface Request {
requestId: string;
}
}
@Injectable()
export class PreRequestMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void) {
// 往所有的request那添加requestId
req.requestId = v4();
next();
}
}
3、typeorm中使用tree结构,可以使用mpath物理路径,可以优化mysql的查询
import {Entity, Tree, Column, PrimaryGeneratedColumn, TreeChildren, TreeParent, TreeLevelColumn} from "typeorm";
@Entity()
@Tree("materialized-path")
export class Category {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@TreeChildren()
children: Category[];
@TreeParent()
parent: Category;
}
mysql表
DROP TABLE IF EXISTS `Category`;
CREATE TABLE `Category`(
`id` int PRIMARY KEY AUTO INCREMENT ,
`name` varchar(30) NOT NULL COMMENT '文件夹名称',
`parent_id` int NOT NULL COMMENT '文件夹父级的id',
`mpath` varchar(500) DEFAULT '' NOT NULL COMMENT '类别的层级路径',
)ENGINE = InnoDB CHARACTER SET = utf8mb4;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!