Appearance
NestJS 数据库集成 (Database Integration)
NestJS 提供了多种方式来集成数据库,支持关系型数据库和非关系型数据库。NestJS 本身不强制使用特定的数据库或 ORM,但提供了与流行数据库库的集成方式,如 TypeORM、Sequelize、Mongoose 等。
基础概念
NestJS 中的数据库集成涉及:
- 数据源管理 - 配置和管理数据库连接
- 实体/模型定义 - 定义数据结构
- 仓储模式 - 数据访问层的实现
- 事务处理 - 确保数据一致性
- 查询构建 - 构建复杂查询
TypeORM 集成
TypeORM 是 NestJS 推荐的关系型数据库 ORM,支持多种数据库系统。
安装依赖
bash
npm install @nestjs/typeorm typeorm mysql2
# 或者使用其他数据库驱动
npm install @nestjs/typeorm typeorm pg # PostgreSQL
npm install @nestjs/typeorm typeorm sqlite3 # SQLite
npm install @nestjs/typeorm typeorm mongodb # MongoDB
配置数据库连接
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // 注意:生产环境中不建议使用
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
定义实体
typescript
// cat.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, OneToMany } from 'typeorm';
import { User } from './user.entity';
@Entity()
export class Cat {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ type: 'int' })
age: number;
@Column({ unique: true })
breed: string;
@ManyToOne(() => User, user => user.cats)
owner: User;
}
使用 Repository
typescript
// cats.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Cat } from './cat.entity';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
imports: [TypeOrmModule.forFeature([Cat])],
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
typescript
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cat } from './cat.entity';
@Injectable()
export class CatsService {
constructor(
@InjectRepository(Cat)
private catsRepository: Repository<Cat>,
) {}
create(cat: Partial<Cat>): Promise<Cat> {
return this.catsRepository.save(cat);
}
findAll(): Promise<Cat[]> {
return this.catsRepository.find();
}
findOne(id: number): Promise<Cat> {
return this.catsRepository.findOne({ where: { id } });
}
async remove(id: number): Promise<void> {
await this.catsRepository.delete(id);
}
}
高级查询
typescript
@Injectable()
export class CatsService {
constructor(
@InjectRepository(Cat)
private catsRepository: Repository<Cat>,
) {}
// 使用查询构建器
findCatsByName(name: string): Promise<Cat[]> {
return this.catsRepository
.createQueryBuilder('cat')
.where('cat.name = :name', { name })
.getMany();
}
// 关联查询
findCatsWithOwner(): Promise<Cat[]> {
return this.catsRepository
.createQueryBuilder('cat')
.leftJoinAndSelect('cat.owner', 'owner')
.getMany();
}
// 复杂查询
findCatsByAgeRange(minAge: number, maxAge: number): Promise<Cat[]> {
return this.catsRepository
.createQueryBuilder('cat')
.where('cat.age BETWEEN :minAge AND :maxAge', { minAge, maxAge })
.orderBy('cat.age', 'ASC')
.getMany();
}
}
Mongoose 集成 (MongoDB)
安装依赖
bash
npm install @nestjs/mongoose mongoose
npm install --save-dev @types/mongoose
配置 MongoDB 连接
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/nest'),
CatsModule,
],
})
export class AppModule {}
定义 Schema
typescript
// schemas/cat.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type CatDocument = Cat & Document;
@Schema()
export class Cat {
@Prop()
name: string;
@Prop()
age: number;
@Prop()
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
使用 Mongoose Model
typescript
// cats.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { Cat, CatSchema } from './schemas/cat.schema';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
imports: [MongooseModule.forFeature([{ name: Cat.name, schema: CatSchema }])],
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
typescript
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Cat, CatDocument } from './schemas/cat.schema';
@Injectable()
export class CatsService {
constructor(@InjectModel(Cat.name) private catModel: Model<CatDocument>) {}
async create(createCatDto: any): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return createdCat.save();
}
async findAll(): Promise<Cat[]> {
return this.catModel.find().exec();
}
async findOne(id: string): Promise<Cat> {
return this.catModel.findById(id).exec();
}
async remove(id: string) {
const result = await this.catModel.deleteOne({ _id: id }).exec();
if (result.deletedCount === 0) {
throw new Error('Cat not found');
}
}
}
Sequelize 集成
安装依赖
bash
npm install @nestjs/sequelize sequelize mysql2
# 或者使用其他数据库驱动
npm install pg pg-hstore # PostgreSQL
npm install sqlite3 # SQLite
配置 Sequelize
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [
SequelizeModule.forRoot({
dialect: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
autoLoadModels: true,
synchronize: true,
}),
CatsModule,
],
})
export class AppModule {}
定义模型
typescript
// cat.model.ts
import { Table, Column, Model, DataType } from 'sequelize-typescript';
@Table
export class Cat extends Model<Cat> {
@Column({
type: DataType.STRING,
allowNull: false,
})
name: string;
@Column({
type: DataType.INTEGER,
allowNull: false,
})
age: number;
@Column({
type: DataType.STRING,
allowNull: false,
})
breed: string;
}
使用 Sequelize Model
typescript
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { Cat } from './cat.model';
@Injectable()
export class CatsService {
constructor(@InjectModel(Cat) private catModel: typeof Cat) {}
async create(createCatDto: any): Promise<Cat> {
const cat = new this.catModel(createCatDto);
return cat.save();
}
async findAll(): Promise<Cat[]> {
return this.catModel.findAll();
}
async findOne(id: number): Promise<Cat> {
return this.catModel.findByPk(id);
}
async remove(id: number) {
const cat = await this.catModel.findByPk(id);
if (cat) {
await cat.destroy();
}
}
}
数据库连接配置
环境配置
typescript
// config/typeorm.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const typeOrmConfig: TypeOrmModuleOptions = {
type: process.env.DB_TYPE as any,
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: process.env.NODE_ENV !== 'production',
logging: process.env.NODE_ENV !== 'production',
};
typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { typeOrmConfig } from './config/typeorm.config';
@Module({
imports: [TypeOrmModule.forRoot(typeOrmConfig)],
})
export class AppModule {}
多数据库连接
typescript
@Module({
imports: [
TypeOrmModule.forRoot({
name: 'default',
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'main',
entities: [User, Post],
}),
TypeOrmModule.forRoot({
name: 'slave',
type: 'mysql',
host: 'slave_host',
port: 3306,
username: 'root',
password: 'root',
database: 'slave',
entities: [Analytics],
}),
],
})
export class AppModule {}
typescript
// 使用多连接
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
@InjectRepository(Analytics, 'slave')
private analyticsRepository: Repository<Analytics>,
) {}
}
事务处理
TypeORM 事务
typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import { Cat } from './cat.entity';
@Injectable()
export class CatsService {
constructor(
@InjectRepository(Cat)
private catsRepository: Repository<Cat>,
private dataSource: DataSource,
) {}
async createWithTransaction(cats: Partial<Cat>[]): Promise<Cat[]> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const results: Cat[] = [];
for (const cat of cats) {
const savedCat = await queryRunner.manager.save(Cat, cat);
results.push(savedCat);
}
await queryRunner.commitTransaction();
return results;
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
}
使用 @Transactional 装饰器
typescript
import { Transaction, TransactionRepository } from 'typeorm';
import { Repository } from 'typeorm';
@Injectable()
export class CatsService {
@Transaction()
async transferCats(
@TransactionRepository(Cat) catRepository: Repository<Cat>,
catIds: number[],
newOwnerId: number,
): Promise<void> {
for (const catId of catIds) {
const cat = await catRepository.findOne({ where: { id: catId } });
if (cat) {
cat.ownerId = newOwnerId;
await catRepository.save(cat);
}
}
}
}
迁移管理
TypeORM 迁移
typescript
// 创建迁移文件
// 1. 配置 CLI
{
"typeOrmConfig": {
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": ["src/**/*.entity{.ts,.js}"],
"migrations": ["src/migrations/*{.ts,.js}"],
"cli": {
"migrationsDir": "src/migrations"
}
}
}
// 2. 创建迁移
// npx typeorm migration:create src/migrations/CreateCatsTable
typescript
// src/migrations/CreateCatsTable.ts
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class CreateCatsTable1620000000000 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(new Table({
name: 'cat',
columns: [
{
name: 'id',
type: 'int',
isPrimary: true,
isGenerated: true,
generationStrategy: 'increment',
},
{
name: 'name',
type: 'varchar',
length: '255',
},
{
name: 'age',
type: 'int',
},
{
name: 'breed',
type: 'varchar',
length: '255',
},
],
}));
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('cat');
}
}
查询优化
索引定义
typescript
// 使用装饰器定义索引
@Entity()
@Index(['name', 'breed']) // 复合索引
@Index('cat_name_index', { synchronize: false }) // 自定义索引名
export class Cat {
@PrimaryGeneratedColumn()
id: number;
@Column({ index: true }) // 单列索引
name: string;
@Column({ type: 'int' })
age: number;
@Column({ unique: true }) // 唯一索引
breed: string;
}
分页查询
typescript
@Injectable()
export class CatsService {
async findWithPagination(
page: number,
limit: number,
): Promise<{ data: Cat[]; total: number; page: number; limit: number }> {
const [data, total] = await this.catsRepository.findAndCount({
skip: (page - 1) * limit,
take: limit,
});
return {
data,
total,
page,
limit,
};
}
}
数据库测试
测试配置
typescript
// test/cat.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CatsModule } from '../src/cats/cats.module';
import { Cat } from '../src/cats/cat.entity';
describe('CatsController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
CatsModule,
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
entities: [Cat],
synchronize: true,
}),
],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
// 测试用例...
});
数据库最佳实践
1. 连接池配置
typescript
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
// 连接池配置
poolSize: 10,
acquireTimeout: 30000,
connectionTimeout: 2000,
extra: {
connectionLimit: 10,
queueLimit: 0,
acquireTimeout: 60000,
},
// 其他配置
synchronize: false,
logging: ['error'],
}),
2. 实体设计最佳实践
typescript
@Entity('cats') // 明确指定表名
@Unique(['name', 'breed']) // 唯一约束
export class Cat {
@PrimaryGeneratedColumn('increment')
id: number;
@Column({
type: 'varchar',
length: 255,
nullable: false,
unique: true,
})
name: string;
@Column({
type: 'int',
nullable: false,
default: 0,
})
age: number;
@Column({
type: 'varchar',
length: 255,
nullable: false,
})
breed: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn() // 软删除
deletedAt?: Date;
}
3. 仓储模式实现
typescript
export interface ICatRepository {
create(cat: Partial<Cat>): Promise<Cat>;
findById(id: number): Promise<Cat>;
findByName(name: string): Promise<Cat>;
findAll(): Promise<Cat[]>;
update(id: number, updateData: Partial<Cat>): Promise<Cat>;
delete(id: number): Promise<void>;
}
@Injectable()
export class CatRepository implements ICatRepository {
constructor(
@InjectRepository(Cat)
private catsRepository: Repository<Cat>,
) {}
async create(cat: Partial<Cat>): Promise<Cat> {
const newCat = this.catsRepository.create(cat);
return this.catsRepository.save(newCat);
}
async findById(id: number): Promise<Cat> {
return this.catsRepository.findOne({ where: { id } });
}
async findByName(name: string): Promise<Cat> {
return this.catsRepository.findOne({ where: { name } });
}
async findAll(): Promise<Cat[]> {
return this.catsRepository.find();
}
async update(id: number, updateData: Partial<Cat>): Promise<Cat> {
await this.catsRepository.update(id, updateData);
return this.findById(id);
}
async delete(id: number): Promise<void> {
await this.catsRepository.softDelete(id); // 使用软删除
}
}
性能优化
1. 查询优化
typescript
@Injectable()
export class CatsService {
// 使用 select 只获取需要的字段
async findNamesOnly(): Promise<{ name: string }[]> {
return this.catsRepository.find({
select: ['name'],
});
}
// 使用 relations 预加载关联数据
async findWithOwner(): Promise<Cat[]> {
return this.catsRepository.find({
relations: ['owner'],
});
}
// 使用 query builder 进行复杂查询
async findCatsWithAgeAndOwner(minAge: number): Promise<Cat[]> {
return this.catsRepository
.createQueryBuilder('cat')
.leftJoinAndSelect('cat.owner', 'owner')
.where('cat.age >= :minAge', { minAge })
.getMany();
}
}
2. 缓存策略
typescript
import { CacheService } from '@nestjs/cache-manager';
@Injectable()
export class CatsService {
constructor(
@InjectRepository(Cat)
private catsRepository: Repository<Cat>,
private cacheManager: CacheService,
) {}
async findAllWithCache(): Promise<Cat[]> {
const cacheKey = 'cats:all';
let cats = await this.cacheManager.get<Cat[]>(cacheKey);
if (!cats) {
cats = await this.catsRepository.find();
await this.cacheManager.set(cacheKey, cats, 60000); // 缓存1分钟
}
return cats;
}
}
总结
NestJS 提供了灵活的数据库集成方案,支持多种流行的数据库和 ORM。通过适当的配置和最佳实践,可以构建高性能、可维护的数据库驱动应用程序。
数据库集成的主要特点:
- 支持多种数据库系统和 ORM
- 提供依赖注入集成
- 支持事务处理
- 支持迁移管理
- 提供查询优化工具
- 支持连接池配置
- 包含测试工具