首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

数据库(TypeORM)

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

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

代码语言:javascript
复制
$ npm install --save @nestjs/typeorm typeorm mysql

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

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

app.module.ts

JS

代码语言:javascript
复制
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

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

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

app.module.ts

JS

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [TypeOrmModule.forRoot()],
})
export class ApplicationModule {}

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

app.module.ts

JS

代码语言:javascript
复制
import { Connection } from 'typeorm';

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

存储库模式

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

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

照片/ photo.entity.ts

JS

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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实体,每个实体都存储在自己的数据库中。

代码语言:javascript
复制
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则使用连接。

代码语言:javascript
复制
@Module({
  TypeOrmModule.forFeature([Photo]),
  TypeOrmModule.forFeature([Person], 'personsConnection'),
  TypeOrmModule.forFeature([Album], 'albumsConnection')
})
export class ApplicationModule {}

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

代码语言:javascript
复制
@Injectable()
export class PersonService {
  constructor(
    @InjectConnection('personsConnection')
    private readonly connection: Connection,
    @InjectEntityManager('personsConnection')
    private readonly entityManager: EntityManager
  ) {}
}

测试

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

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

代码语言:javascript
复制
@Module({
  providers: [
    PhotoService,
    {
      provide: getRepositoryToken(Photo),
      useValue: mockRepository,
    },
  ],
})
export class PhotoModule {}

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

定制存储库

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

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

代码语言:javascript
复制
@EntityRepository(Author)
export class AuthorRepository extends Repository<Author> {}

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

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

代码语言:javascript
复制
@Module({
  imports: [TypeOrmModule.forFeature([Author, AuthorRepository])],
  controller: [AuthorController],
  providers: [AuthorService],
})
export class AuthorModule {}

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

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

代码语言:javascript
复制
@Injectable()
export class AuthorService {
  constructor(
    @InjectRepository(AuthorRepository)
    private readonly authorRepository: AuthorRepository,
  ) {}
}

异步配置

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

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

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

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

代码语言:javascript
复制
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],
})

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

代码语言:javascript
复制
TypeOrmModule.forRootAsync({
  useClass: TypeOrmConfigService,
})

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

代码语言:javascript
复制
@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语法。

代码语言:javascript
复制
TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useExisting: ConfigService,
})

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

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com