even

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  161 随笔 :: 0 文章 :: 5 评论 :: 10万 阅读
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

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;

 

posted on   even_blogs  阅读(553)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示