NestJS中集成TypeORM进行数据库操作

 更新时间:2024年12月28日 11:03:54   作者:乘风远洋  
本文深入探讨了如何在NestJS中集成TypeORM进行数据库操作,包括TypeORM的配置和集成、实体设计和关系映射、Repository模式的应用、事务处理方案、数据库迁移管理、性能优化策略

本文深入探讨了如何在NestJS中集成TypeORM进行数据库操作,包括TypeORM的配置和集成、实体设计和关系映射、Repository模式的应用、事务处理方案、数据库迁移管理、性能优化策略。

TypeORM 集成配置

1. 安装依赖

首先安装必要的依赖包:

npm install @nestjs/typeorm typeorm pg
# 如果使用 MySQL
# npm install @nestjs/typeorm typeorm mysql2

2. 数据库配置

// src/config/database.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const databaseConfig: TypeOrmModuleOptions = {
  type: 'postgres',
  host: process.env.DB_HOST || 'localhost',
  port: parseInt(process.env.DB_PORT) || 5432,
  username: process.env.DB_USERNAME || 'postgres',
  password: process.env.DB_PASSWORD || 'postgres',
  database: process.env.DB_DATABASE || 'nestjs_db',
  entities: ['dist/**/*.entity{.ts,.js}'],
  synchronize: process.env.NODE_ENV !== 'production',
  logging: process.env.NODE_ENV !== 'production',
  ssl: process.env.DB_SSL === 'true',
};

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { databaseConfig } from './config/database.config';

@Module({
  imports: [
    TypeOrmModule.forRoot(databaseConfig),
    // 其他模块
  ],
})
export class AppModule {}

实体设计与关系映射

1. 基础实体设计

// src/entities/base.entity.ts
import { 
  PrimaryGeneratedColumn, 
  CreateDateColumn, 
  UpdateDateColumn,
  DeleteDateColumn 
} from 'typeorm';

export abstract class BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @CreateDateColumn()
  createdAt: Date;

  @UpdateDateColumn()
  updatedAt: Date;

  @DeleteDateColumn()
  deletedAt: Date;
}

// src/users/entities/user.entity.ts
import { Entity, Column, OneToMany } from 'typeorm';
import { BaseEntity } from '../entities/base.entity';
import { Post } from './post.entity';

@Entity('users')
export class User extends BaseEntity {
  @Column({ length: 100 })
  name: string;

  @Column({ unique: true })
  email: string;

  @Column({ select: false })
  password: string;

  @OneToMany(() => Post, post => post.author)
  posts: Post[];
}

// src/posts/entities/post.entity.ts
import { Entity, Column, ManyToOne, JoinColumn } from 'typeorm';
import { BaseEntity } from '../entities/base.entity';
import { User } from './user.entity';

@Entity('posts')
export class Post extends BaseEntity {
  @Column()
  title: string;

  @Column('text')
  content: string;

  @Column({ default: false })
  published: boolean;

  @ManyToOne(() => User, user => user.posts)
  @JoinColumn({ name: 'author_id' })
  author: User;
}

2. 关系映射策略

// src/users/entities/profile.entity.ts
import { Entity, Column, OneToOne, JoinColumn } from 'typeorm';
import { BaseEntity } from '../entities/base.entity';
import { User } from './user.entity';

@Entity('profiles')
export class Profile extends BaseEntity {
  @Column()
  avatar: string;

  @Column('text')
  bio: string;

  @OneToOne(() => User)
  @JoinColumn({ name: 'user_id' })
  user: User;
}

// src/posts/entities/tag.entity.ts
import { Entity, Column, ManyToMany } from 'typeorm';
import { BaseEntity } from '../entities/base.entity';
import { Post } from './post.entity';

@Entity('tags')
export class Tag extends BaseEntity {
  @Column({ unique: true })
  name: string;

  @ManyToMany(() => Post, post => post.tags)
  posts: Post[];
}

// 更新 Post 实体,添加标签关系
@Entity('posts')
export class Post extends BaseEntity {
  // ... 其他字段

  @ManyToMany(() => Tag, tag => tag.posts)
  @JoinTable({
    name: 'posts_tags',
    joinColumn: { name: 'post_id' },
    inverseJoinColumn: { name: 'tag_id' }
  })
  tags: Tag[];
}

数据库操作实现

1. Repository 模式

// src/users/users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto, UpdateUserDto } from './dto';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>
  ) {}

  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(createUserDto);
    return await this.usersRepository.save(user);
  }

  async findAll(): Promise<User[]> {
    return await this.usersRepository.find({
      relations: ['posts', 'profile']
    });
  }

  async findOne(id: string): Promise<User> {
    const user = await this.usersRepository.findOne({
      where: { id },
      relations: ['posts', 'profile']
    });

    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }

    return user;
  }

  async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
    const user = await this.findOne(id);
    Object.assign(user, updateUserDto);
    return await this.usersRepository.save(user);
  }

  async remove(id: string): Promise<void> {
    const user = await this.findOne(id);
    await this.usersRepository.softRemove(user);
  }
}

2. 查询构建器

// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Post } from './entities/post.entity';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private postsRepository: Repository<Post>
  ) {}

  async findPublishedPosts() {
    return await this.postsRepository
      .createQueryBuilder('post')
      .leftJoinAndSelect('post.author', 'author')
      .leftJoinAndSelect('post.tags', 'tags')
      .where('post.published = :published', { published: true })
      .orderBy('post.createdAt', 'DESC')
      .getMany();
  }

  async searchPosts(query: string) {
    return await this.postsRepository
      .createQueryBuilder('post')
      .leftJoinAndSelect('post.author', 'author')
      .where('post.title ILIKE :query OR post.content ILIKE :query', {
        query: `%${query}%`
      })
      .orderBy('post.createdAt', 'DESC')
      .getMany();
  }

  async getPostStats() {
    return await this.postsRepository
      .createQueryBuilder('post')
      .select('author.name', 'authorName')
      .addSelect('COUNT(*)', 'postCount')
      .leftJoin('post.author', 'author')
      .groupBy('author.name')
      .getRawMany();
  }
}

事务处理

1. 事务装饰器

// src/common/decorators/transaction.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { getManager } from 'typeorm';

export const Transaction = createParamDecorator(
  async (data: unknown, ctx: ExecutionContext) => {
    const queryRunner = getManager().connection.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();
    return queryRunner;
  }
);

// 使用示例
@Post('transfer')
async transfer(
  @Transaction() queryRunner,
  @Body() transferDto: TransferDto
) {
  try {
    // 执行转账操作
    await queryRunner.manager.update(Account, 
      transferDto.fromId, 
      { balance: () => `balance - ${transferDto.amount}` }
    );

    await queryRunner.manager.update(Account, 
      transferDto.toId, 
      { balance: () => `balance + ${transferDto.amount}` }
    );

    await queryRunner.commitTransaction();
  } catch (err) {
    await queryRunner.rollbackTransaction();
    throw err;
  } finally {
    await queryRunner.release();
  }
}

2. 事务管理器

// src/common/services/transaction.service.ts
import { Injectable } from '@nestjs/common';
import { Connection, QueryRunner } from 'typeorm';

@Injectable()
export class TransactionService {
  constructor(private connection: Connection) {}

  async executeInTransaction<T>(
    callback: (queryRunner: QueryRunner) => Promise<T>
  ): Promise<T> {
    const queryRunner = this.connection.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const result = await callback(queryRunner);
      await queryRunner.commitTransaction();
      return result;
    } catch (err) {
      await queryRunner.rollbackTransaction();
      throw err;
    } finally {
      await queryRunner.release();
    }
  }
}

// 使用示例
@Injectable()
export class PaymentService {
  constructor(
    private transactionService: TransactionService,
    private ordersService: OrdersService
  ) {}

  async processPayment(paymentDto: PaymentDto) {
    return await this.transactionService.executeInTransaction(async queryRunner => {
      const order = await this.ordersService.findOne(paymentDto.orderId);
      
      // 更新订单状态
      await queryRunner.manager.update(Order, order.id, {
        status: 'paid'
      });

      // 创建支付记录
      const payment = queryRunner.manager.create(Payment, {
        order,
        amount: paymentDto.amount
      });
      await queryRunner.manager.save(payment);

      return payment;
    });
  }
}

数据库迁移

1. 迁移配置

// ormconfig.js
module.exports = {
  type: 'postgres',
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  entities: ['dist/**/*.entity{.ts,.js}'],
  migrations: ['dist/migrations/*{.ts,.js}'],
  cli: {
    migrationsDir: 'src/migrations'
  }
};

// package.json
{
  "scripts": {
    "typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
    "migration:create": "npm run typeorm migration:create -- -n",
    "migration:generate": "npm run typeorm migration:generate -- -n",
    "migration:run": "npm run typeorm migration:run",
    "migration:revert": "npm run typeorm migration:revert"
  }
}

2. 迁移示例

// src/migrations/1642340914321-CreateUsersTable.ts
import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class CreateUsersTable1642340914321 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'users',
        columns: [
          {
            name: 'id',
            type: 'uuid',
            isPrimary: true,
            generationStrategy: 'uuid',
            default: 'uuid_generate_v4()'
          },
          {
            name: 'name',
            type: 'varchar',
            length: '100'
          },
          {
            name: 'email',
            type: 'varchar',
            isUnique: true
          },
          {
            name: 'password',
            type: 'varchar'
          },
          {
            name: 'created_at',
            type: 'timestamp',
            default: 'now()'
          },
          {
            name: 'updated_at',
            type: 'timestamp',
            default: 'now()'
          },
          {
            name: 'deleted_at',
            type: 'timestamp',
            isNullable: true
          }
        ]
      })
    );
  }

  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('users');
  }
}

性能优化

1. 查询优化

// src/posts/posts.service.ts
@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private postsRepository: Repository<Post>
  ) {}

  // 使用分页和缓存
  async findAll(page = 1, limit = 10) {
    const [posts, total] = await this.postsRepository.findAndCount({
      relations: ['author', 'tags'],
      skip: (page - 1) * limit,
      take: limit,
      cache: {
        id: `posts_page_${page}`,
        milliseconds: 60000 // 1分钟缓存
      }
    });

    return {
      data: posts,
      meta: {
        total,
        page,
        lastPage: Math.ceil(total / limit)
      }
    };
  }

  // 使用子查询优化
  async findPopularPosts() {
    return await this.postsRepository
      .createQueryBuilder('post')
      .leftJoinAndSelect('post.author', 'author')
      .addSelect(subQuery => {
        return subQuery
          .select('COUNT(*)', 'commentCount')
          .from('comments', 'comment')
          .where('comment.postId = post.id');
      }, 'commentCount')
      .orderBy('commentCount', 'DESC')
      .limit(10)
      .getMany();
  }
}

2. 索引优化

// src/posts/entities/post.entity.ts
@Entity('posts')
@Index(['title', 'content']) // 复合索引
export class Post extends BaseEntity {
  @Column()
  @Index() // 单列索引
  title: string;

  @Column('text')
  content: string;

  @Column()
  @Index()
  authorId: string;

  // ... 其他字段
}

写在最后

本文详细介绍了 NestJS 中的数据库操作实践:

  1. TypeORM 的配置和集成
  2. 实体设计和关系映射
  3. Repository 模式的应用
  4. 事务处理方案
  5. 数据库迁移管理
  6. 性能优化策略

到此这篇关于NestJS中集成TypeORM进行数据库操作的文章就介绍到这了,更多相关NestJS集成TypeORM数据库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Node.js中npx命令的使用方法及场景分析

    Node.js中npx命令的使用方法及场景分析

    NPM(Node Package Manager) 是Node.js提供的一个包管理器, 可以使用 NPM 来安装 node.js 包 ,npm 是从5.2版开始, 增加(自带)了 npx 命令,本文给大家分享Node.js npx命令使用,需要的朋友一起看看吧
    2021-08-08
  • NodeJS 中Stream 的基本使用

    NodeJS 中Stream 的基本使用

    在 NodeJS 中,我们对文件的操作需要依赖核心模块 fs , fs 中有很基本 API 可以帮助我们读写占用内存较小的文件,这篇文章主要介绍了NodeJS 中Stream 的基本使用,需要的朋友可以参考下
    2018-07-07
  • express搭建的nodejs项目使用webpack进行压缩打包

    express搭建的nodejs项目使用webpack进行压缩打包

    对于打包这个问题它并不是难点,但是对于我们这种初学者来说,根本就不知道应该怎么做,下面这篇文章主要给大家介绍了关于express搭建的nodejs项目使用webpack进行压缩打包的相关资料,需要的朋友可以参考下
    2022-12-12
  • Nodejs监控事件循环异常示例详解

    Nodejs监控事件循环异常示例详解

    这篇文章主要给大家介绍了关于Nodejs监控事件循环异常的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Nodejs具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-09-09
  • nodejs判断文件、文件夹是否存在及删除的方法

    nodejs判断文件、文件夹是否存在及删除的方法

    这篇文章主要介绍了nodejs判断文件、文件夹是否存在及删除的方法,结合实例形式分析了nodejs基于文件模块针对文件与文件夹的存在判断、删除等操作技巧,需要的朋友可以参考下
    2017-11-11
  • Node.js中Express框架的使用教程详解

    Node.js中Express框架的使用教程详解

    这篇文章主要为大家详细介绍了Node.js中的开发框架Express,利用Express框架可以快速的进行Web后端开发,感兴趣的小伙伴可以了解一下
    2022-04-04
  • Node js 中的 Multer中间件(文件上传)

    Node js 中的 Multer中间件(文件上传)

    本文介绍了Node.js中用于处理文件上传的Multer中间件,详细说明了项目设置、安装依赖的步骤,以及如何使用Multer上传单个和多个文件,还介绍了DiskStorage引擎、其他配置选项和错误处理方法,感兴趣的朋友跟随小编一起看看吧
    2025-03-03
  • 使用nodeJS中的fs模块对文件及目录进行读写,删除,追加,等操作详解

    使用nodeJS中的fs模块对文件及目录进行读写,删除,追加,等操作详解

    nodeJS中fs模块对系统文件及目录进行读写操作,本文将详细介绍nodejs中的文件操作模块fs的使用方法
    2020-02-02
  • Node.js中同步和异步编程的区别及使用方法

    Node.js中同步和异步编程的区别及使用方法

    在Node.js中,同步和异步编程是两种不同的处理方式。同步方式会阻塞程序的执行,而异步方式则不会。通过掌握它们的区别和使用方法,可以更好地实现程序的性能优化和功能扩展。同时,需要注意异步编程中的回调地狱问题,使用Promise可以更好地处理异步编程
    2023-05-05
  • 详解axios在node.js中的post使用

    详解axios在node.js中的post使用

    最近因为工作的原因在学习使用网络请求库,因为这个项目用的是Promise,所以就选择了axios,下面这篇文章主要给大家介绍了关于axios在node.js中的post使用的相关资料,文中介绍的非常详细,需要的朋友可以参考借鉴,下面来一起学习学习吧。
    2017-04-04

最新评论