NestJS中集成TypeORM进行数据库操作
作者:乘风远洋
本文深入探讨了如何在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 中的数据库操作实践:
- TypeORM 的配置和集成
- 实体设计和关系映射
- Repository 模式的应用
- 事务处理方案
- 数据库迁移管理
- 性能优化策略
到此这篇关于NestJS中集成TypeORM进行数据库操作的文章就介绍到这了,更多相关NestJS集成TypeORM数据库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!