数据库(TypeORM)

数据库(TypeORM)

为了减少使用任何数据库开始冒险所需的样板,Nest附带了随时可用的@nestjs/typeorm软件包。我们选择了TypeORM,因为它绝对是迄今为止最成熟的对象关系映射器(ORM)。由于它是用TypeScript编写的,因此它与Nest框架非常兼容。

首先,我们需要安装所有必需的依赖项:

$ npm install --save @nestjs/typeorm typeorm mysql

注意在本章中我们将使用MySQL数据库,但TypeORM为许多不同的数据库提供支持,例如PostgreSQL,SQLite甚至MongoDB(NoSQL)。

安装过程完成后,我们可以将其TypeOrmModule导入到根目录中ApplicationModule

app.module.ts

JS

import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module{ imports: [ TypeOrmModule.forRoot{ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'root', database: 'test', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, }), ], }) export class ApplicationModule {}

forRoot()方法接受相同的配置对象作为createConnection()从TypeORM包。此外,forRoot()我们可以ormconfig.json在项目根目录中创建一个文件,而不是传递任何内容。

ormconfig.json

{ "type": "mysql", "host": "localhost", "port": 3306, "username": "root", "password": "root", "database": "test", "entities": ["src/**/**.entity{.ts,.js}"], "synchronize": true }

然后,我们可以简单地将括号留空:

app.module.ts

JS

import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module{ imports: [TypeOrmModule.forRoot()], }) export class ApplicationModule {}

随后,Connection并且EntityManager将可跨整个项目注入(没有导入任何其他地方的模块),例如,以这种方式:

app.module.ts

JS

import { Connection } from 'typeorm'; @Module{ imports: [TypeOrmModule.forRoot(), PhotoModule], }) export class ApplicationModule { constructor(private readonly connection: Connection) {} }

存储库模式

该TypeORM支持库的设计模式,使每个实体都有自己的仓库。可以从数据库连接获取这些存储库。

首先,我们至少需要一个实体。我们Photo将从官方文档中重用该实体。

照片/ photo.entity.ts

JS

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。这是您决定保存模型文件的地方。从我们的角度来看,在相应的模块目录中,最好的方法是将它们保存在自己的域中

我们来看看PhotoModule

照片/ photo.module.ts

JS

import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PhotoService } from './photo.service'; import { PhotoController } from './photo.controller'; import { Photo } from './photo.entity'; @Module{ imports: [TypeOrmModule.forFeature([Photo])], providers: [PhotoService], controllers: [PhotoController], }) export class PhotoModule {}

该模块使用forFeature()方法来定义应在当前范围中注册的存储库。感谢大家能注入PhotoRepository的以PhotoService使用@InjectRepository()装饰:

照片/ photo.service.ts

JS

import { Injectable, Inject } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Photo } from './photo.entity'; @Injectable() export class PhotoService { constructor( @InjectRepository(Photo) private readonly photoRepository: Repository<Photo>, ) {} async findAll(): Promise<Photo[]> { return await this.photoRepository.find( } }

注意不要忘记将其导入PhotoModule根目录ApplicationModule

多个数据库

您的某些项目可能需要多个数据库连接。幸运的是,这个模块也可以实现。要使用多个连接,首先要做的是创建这些连接。在这种情况下,连接命名变为必需

假设您有一个Person实体和一个Album实体,每个实体都存储在自己的数据库中。

const defaultOptions = { type: 'postgres', port: 5432, username: 'user', password: 'password', database: 'db', synchronize: true, }; @Module{ imports: [ TypeOrmModule.forRoot{ ...defaultOptions, host: 'photo_db_host', entities: [Photo], }), TypeOrmModule.forRoot{ ...defaultOptions, name: 'personsConnection', host: 'person_db_host', entities: [Person], }), TypeOrmModule.forRoot{ ...defaultOptions, name: 'albumsConnection', host: 'album_db_host', entities: [Album], }) ] }) export class ApplicationModule {}

注意如果没有name为连接设置任何连接,则其名称设置为default。请注意,如果没有名称或名称相同,则不应该有多个连接,否则它们只会被覆盖。

在这一点上,你有你的每一个的PhotoPersonAlbum登记在自己的连接实体。使用此设置,您必须告诉TypeOrmModule.forFeature()函数和@InjectRepository()装饰器应该使用哪个连接。如果未传递任何连接名称,default则使用连接。

@Module{ TypeOrmModule.forFeature([Photo]), TypeOrmModule.forFeature([Person], 'personsConnection'), TypeOrmModule.forFeature([Album], 'albumsConnection') }) export class ApplicationModule {}

您还可以为给定连接注入ConnectionEntityManager

@Injectable() export class PersonService { constructor( @InjectConnection('personsConnection') private readonly connection: Connection, @InjectEntityManager('personsConnection') private readonly entityManager: EntityManager ) {} }

测试

在单元测试我们的应用程序时,我们通常希望避免任何数据库连接,使我们的测试套件独立并尽可能快地执行它们。但是我们的类可能依赖于从连接实例中提取的存储库。那是什么?解决方案是创建虚假存储库。为了实现这一点,我们应该设置自定义提供程序。实际上,每个注册的存储库都由EntityNameRepositorytoken表示,其中EntityName是您的实体类的名称。

@nestjs/typeorm包公开了getRepositoryToken()基于给定实体返回准备好的令牌的函数。

@Module{ providers: [ PhotoService, { provide: getRepositoryToken(Photo), useValue: mockRepository, }, ], }) export class PhotoModule {}

现在硬编码mockRepository将被用作PhotoRepository。每当任何提供者要求PhotoRepository使用@InjectRepository()装饰器时,Nest将使用注册的mockRepository对象。

定制存储库

TypeORM提供称为自定义存储库的功能。要了解有关它的更多信息,请访问此页面。基本上,自定义存储库允许您扩展基本存储库类,并使用几种特殊方法对其进行丰富。

要创建自定义存储库,请使用@EntityRepository()装饰器和扩展Repository类。

@EntityRepository(Author) export class AuthorRepository extends Repository<Author> {}

暗示这两个@EntityRepository()Repository从露出typeorm包。

创建类后,下一步是将实例化责任移交给Nest。为此,我们必须将AuthorRepository类传递给TypeOrm.forFeature()方法。

@Module{ imports: [TypeOrmModule.forFeature([Author, AuthorRepository])], controller: [AuthorController], providers: [AuthorService], }) export class AuthorModule {}

注意尽管AuthorRepository已通过,但尚不足以创建自定义存储库。Author在这种情况下,也需要相应的实体类。

然后,只需使用@InjectRepository()装饰器注入存储库。

@Injectable() export class AuthorService { constructor( @InjectRepository(AuthorRepository) private readonly authorRepository: AuthorRepository, ) {} }

异步配置

通常,您可能希望异步传递模块选项,而不是事先传递它们。在这种情况下,使用forRootAsync()方法,提供了几种处理异步数据的方法。

第一种可能的方法是使用工厂功能:

TypeOrmModule.forRootAsync{ useFactory: () => { type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'root', database: 'test', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, }), })

显然,我们的工厂表现得像其他每一个(可能async并且能够通过注入依赖关系inject)。

TypeOrmModule.forRootAsync{ imports: [ConfigModule], useFactory: async (configService: ConfigService) => { type: 'mysql', host: configService.getString('HOST'), port: configService.getString('PORT'), username: configService.getString('USERNAME'), password: configService.getString('PASSWORD'), database: configService.getString('DATABASE'), entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, }), inject: [ConfigService], })

或者,您可以使用类而不是工厂。

TypeOrmModule.forRootAsync{ useClass: TypeOrmConfigService, })

上面的构造将TypeOrmConfigService在内部实例化TypeOrmModule,并将利用它来创建选项对象。在TypeOrmConfigService必须实现TypeOrmOptionsFactory的接口。

@Injectable() class TypeOrmConfigService implements TypeOrmOptionsFactory { createTypeOrmOptions(): TypeOrmModuleOptions { return { type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'root', database: 'test', entities: [__dirname + '/**/*.entity{.ts,.js}'], synchronize: true, }; } }

为了防止TypeOrmConfigService内部创建TypeOrmModule并使用从不同模块导入的提供程序,您可以使用useExisting语法。

TypeOrmModule.forRootAsync{ imports: [ConfigModule], useExisting: ConfigService, })

它的作用useClass与一个关键区别相同 - TypeOrmModule将查找导入的模块以重新使用已创建的ConfigService,而不是单独实例化它。