NestJS中集成TypeORM進(jìn)行數(shù)據(jù)庫(kù)操作
本文深入探討了如何在NestJS中集成TypeORM進(jìn)行數(shù)據(jù)庫(kù)操作,包括TypeORM的配置和集成、實(shí)體設(shè)計(jì)和關(guān)系映射、Repository模式的應(yīng)用、事務(wù)處理方案、數(shù)據(jù)庫(kù)遷移管理、性能優(yōu)化策略。
TypeORM 集成配置
1. 安裝依賴(lài)
首先安裝必要的依賴(lài)包:
npm install @nestjs/typeorm typeorm pg # 如果使用 MySQL # npm install @nestjs/typeorm typeorm mysql2
2. 數(shù)據(jù)庫(kù)配置
// 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 {}
實(shí)體設(shè)計(jì)與關(guān)系映射
1. 基礎(chǔ)實(shí)體設(shè)計(jì)
// 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. 關(guān)系映射策略
// 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 實(shí)體,添加標(biāo)簽關(guān)系
@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[];
}
數(shù)據(jù)庫(kù)操作實(shí)現(xiàn)
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. 查詢(xún)構(gòu)建器
// 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();
}
}
事務(wù)處理
1. 事務(wù)裝飾器
// 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 {
// 執(zhí)行轉(zhuǎn)賬操作
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. 事務(wù)管理器
// 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);
// 更新訂單狀態(tài)
await queryRunner.manager.update(Order, order.id, {
status: 'paid'
});
// 創(chuàng)建支付記錄
const payment = queryRunner.manager.create(Payment, {
order,
amount: paymentDto.amount
});
await queryRunner.manager.save(payment);
return payment;
});
}
}
數(shù)據(jù)庫(kù)遷移
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');
}
}
性能優(yōu)化
1. 查詢(xún)優(yōu)化
// src/posts/posts.service.ts
@Injectable()
export class PostsService {
constructor(
@InjectRepository(Post)
private postsRepository: Repository<Post>
) {}
// 使用分頁(yè)和緩存
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)
}
};
}
// 使用子查詢(xún)優(yōu)化
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. 索引優(yōu)化
// src/posts/entities/post.entity.ts
@Entity('posts')
@Index(['title', 'content']) // 復(fù)合索引
export class Post extends BaseEntity {
@Column()
@Index() // 單列索引
title: string;
@Column('text')
content: string;
@Column()
@Index()
authorId: string;
// ... 其他字段
}
寫(xiě)在最后
本文詳細(xì)介紹了 NestJS 中的數(shù)據(jù)庫(kù)操作實(shí)踐:
- TypeORM 的配置和集成
- 實(shí)體設(shè)計(jì)和關(guān)系映射
- Repository 模式的應(yīng)用
- 事務(wù)處理方案
- 數(shù)據(jù)庫(kù)遷移管理
- 性能優(yōu)化策略
到此這篇關(guān)于NestJS中集成TypeORM進(jìn)行數(shù)據(jù)庫(kù)操作的文章就介紹到這了,更多相關(guān)NestJS集成TypeORM數(shù)據(jù)庫(kù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于npm?i幾種常見(jiàn)命令的區(qū)別詳解
npm(Node.js Package Manager)是一個(gè)Node.js的包管理工具,用來(lái)解決Node.js代碼部署問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于npm?i幾種常見(jiàn)命令的那點(diǎn)事,需要的朋友可以參考下2023-03-03
node操作mysql數(shù)據(jù)庫(kù)實(shí)例詳解
這篇文章主要介紹了node操作mysql數(shù)據(jù)庫(kù),結(jié)合實(shí)例形式較為詳細(xì)的分析了node操作數(shù)據(jù)庫(kù)的連接、增刪改查、事務(wù)處理及錯(cuò)誤處理相關(guān)操作技巧,需要的朋友可以參考下2017-03-03
Windows系統(tǒng)下Node.js安裝以及環(huán)境配置的完美教程
相信對(duì)于很多關(guān)注javascript發(fā)展的同學(xué)來(lái)說(shuō),nodejs已經(jīng)不是一個(gè)陌生的詞眼,下面這篇文章主要給大家介紹了關(guān)于Windows系統(tǒng)下Node.js安裝以及環(huán)境配置的完美教程,需要的朋友可以參考下2022-06-06
總結(jié)幾道關(guān)于Node.js的面試問(wèn)題
這篇文章主要總結(jié)了幾道關(guān)于Node.js的面試問(wèn)題,通過(guò)這些問(wèn)題就來(lái)判斷一個(gè)人的Node.js水平是不太嚴(yán)謹(jǐn)?shù)模撬茏屇銓?duì)面試者在Node.js上的經(jīng)驗(yàn)如何有個(gè)大概的了解。有需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-01-01

