Skip to content

SQL(TypeORM)

本章仅适用于 TypeScript

警告

在本文中,你将学习如何基于 TypeORM 包,通过自定义 provider 机制从零创建一个 DatabaseModule。因此,这种方案会包含较多额外负担,而这些内容其实可以通过现成可用的 @nestjs/typeorm 包省略掉。要了解更多内容,请参见这里

TypeORM 无疑是 node.js 世界中最成熟的对象关系映射器(ORM)之一。由于它本身使用 TypeScript 编写,因此与 Nest 框架配合得非常好。

入门

要开始使用这个库,我们需要安装所有必需依赖:

bash
$ npm install --save typeorm mysql2

第一步是使用从 typeorm 包导入的 new DataSource().initialize() 来与数据库建立连接。initialize() 函数会返回一个 Promise,因此我们需要创建一个异步提供者

typescript
// database.providers.ts
import { DataSource } from 'typeorm';

export const databaseProviders = [
  {
    provide: 'DATA_SOURCE',
    useFactory: async () => {
      const dataSource = new DataSource({
        type: 'mysql',
        host: 'localhost',
        port: 3306,
        username: 'root',
        password: 'root',
        database: 'test',
        entities: [
            __dirname + '/../**/*.entity{.ts,.js}',
        ],
        synchronize: true,
      });

      return dataSource.initialize();
    },
  },
];

警告

不应在生产环境中使用 synchronize: true,否则你可能会丢失生产数据。

提示

遵循最佳实践,我们将自定义 provider 声明在一个独立文件中,并使用 *.providers.ts 后缀。

然后,我们需要导出这些 provider,以便应用的其他部分也能访问它们。

typescript
// database.module.ts
import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';

@Module({
  providers: [...databaseProviders],
  exports: [...databaseProviders],
})
export class DatabaseModule {}

现在我们就可以使用 @Inject() 装饰器注入 DATA_SOURCE 对象。任何依赖 DATA_SOURCE 异步 provider 的类,都会等待对应的 Promise 被解析。

Repository 模式

TypeORM 支持 repository 设计模式,因此每个实体都有自己的 Repository。这些 repository 可以从数据库连接中获取。

不过首先,我们至少需要一个实体。这里我们将复用官方文档中的 Photo 实体。

typescript
// photo.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Photo {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ length: 500 })
  name: string;

  @Column('text')
  description: string;

  @Column()
  filename: string;

  @Column('int')
  views: number;

  @Column()
  isPublished: boolean;
}

Photo 实体属于 photo 目录,而该目录代表 PhotoModule。现在,让我们创建一个 Repository provider:

typescript
// photo.providers.ts
import { DataSource } from 'typeorm';
import { Photo } from './photo.entity';

export const photoProviders = [
  {
    provide: 'PHOTO_REPOSITORY',
    useFactory: (dataSource: DataSource) => dataSource.getRepository(Photo),
    inject: ['DATA_SOURCE'],
  },
];

警告

在真实应用中,应避免使用魔法字符串PHOTO_REPOSITORYDATA_SOURCE 都应该放在单独的 constants.ts 文件中。

现在我们就可以在 PhotoService 中使用 @Inject() 装饰器注入 Repository<Photo>

typescript
// photo.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';

@Injectable()
export class PhotoService {
  constructor(
    @Inject('PHOTO_REPOSITORY')
    private photoRepository: Repository<Photo>,
  ) {}

  async findAll(): Promise<Photo[]> {
    return this.photoRepository.find();
  }
}

数据库连接是异步的,但 Nest 让整个过程对最终用户几乎完全透明。PhotoRepository 会等待数据库连接完成,而 PhotoService 也会延迟到 repository 准备就绪之后才实例化。整个应用会在所有类都完成实例化后再启动。

下面是最终的 PhotoModule

typescript
// photo.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from '../database/database.module';
import { photoProviders } from './photo.providers';
import { PhotoService } from './photo.service';

@Module({
  imports: [DatabaseModule],
  providers: [
    ...photoProviders,
    PhotoService,
  ],
})
export class PhotoModule {}

提示

别忘了在根 AppModule 中导入 PhotoModule

基于 NestJS 官方文档翻译