TypeORM

Nest.js 中的数据库操作

为了与 SQL 和 NoSQL 数据库集成,Nest 提供了@ nestjs / typeorm 软件包。Nest 使用 TypeORM,因为它是 TypeScript 可用的最成熟的对象关系映射器(ORM)。由于它是用 TypeScript 编写的,因此可以与 Nest 框架很好地集成。

要开始使用它,我们首先安装所需的依赖项。在本章中,我们将演示如何使用流行的 MySQL Relational DBMS,但是 TypeORM 提供了对许多关系数据库的支持,例如 PostgreSQL,Oracle,Microsoft SQL Server,SQLite,甚至是 NoSQL 数据库,例如 MongoDB。对于 TypeORM 支持的任何数据库。

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: [],
      synchronize: true
    })
  ]
})
export class AppModule {}

forRoot() 方法从 TypeORM 包接受与 createConnection() 相同的配置对象。另外,我们可以在项目根目录中创建 ormconfig.json 文件,而不是将配置对象传递给 forRoot()。

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

然后在 forRoot 中不传入任何参数:

import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";

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

TyperOrm 也支持异步配置:

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]
});

数据查询

完成此操作后,TypeORM Connection 和 EntityManager 对象将可用于在整个项目中注入(而无需导入任何模块),例如:

import { Connection } from "typeorm";

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

Repository

TypeORM 支持存储库设计模式,因此每个实体都有自己的存储库。这些存储库可以从数据库连接中获取,首先在 Module 中需要声明依赖:

@Module({
  imports: [TypeOrmModule.forFeature([Photo])],
  providers: [PhotoService],
  controllers: [PhotoController]
})
export class PhotoModule {}

然后在 Service 中引入 Repository:

@Injectable()
export class PhotoService {
  constructor(
    @InjectRepository(Photo)
    private readonly photoRepository: Repository<Photo>
  ) {}

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

如果要在导入 TypeOrmModule.forFeature 的模块之外使用存储库,则需要重新导出由其生成的提供程序。您可以通过导出整个模块来做到这一点,如下所示:

@Module({
  imports: [TypeOrmModule.forFeature([Photo])],
  exports: [TypeOrmModule]
})
export class PhotoModule {}

多数据库

一些项目需要多个数据库连接。这也可以通过该模块来实现。要使用多个连接,请首先创建连接。在这种情况下,连接命名成为强制性的。假设您有一个 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 AppModule {}

此时,您已将每个 Photo,Person 和 Album 实体注册为各自的连接。使用此设置,您必须告诉 TypeOrmModule.forFeature()函数和@InjectRepository()装饰器应使用哪个连接。如果未传递任何连接名称,则使用默认连接。

@Module({
  imports: [
    TypeOrmModule.forFeature([Photo]),
    TypeOrmModule.forFeature([Person], "personsConnection"),
    TypeOrmModule.forFeature([Album], "albumsConnection")
  ]
})
export class AppModule {}

然后可以注入 Connection 或 EntityManager:

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

Testing

在对应用程序进行单元测试时,我们通常希望避免建立数据库连接,使我们的测试套件保持独立,并尽可能快地执行它们。但是我们的类可能取决于从连接实例中拉出的存储库。我们该如何处理?解决方案是创建模拟存储库。为此,我们设置了自定义提供程序。每个注册的存储库都由 <EntityName>Repository 标记自动表示,其中 EntityName 是您的实体类的名称。

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

现在,替代的嘲讽存储库将用作 PhotoRepository。每当任何类使用@InjectRepository() 装饰器要求提供 PhotoRepository 时,Nest 都会使用已注册的 mockRepository 对象。

自定义 Repository

TypeORM 提供了一种称为自定义存储库的功能。自定义存储库允许您扩展基本存储库类,并使用几种特殊方法来丰富它。

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

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

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

然后用如下方式注入:

@Injectable()
export class AuthorService {
  constructor(private readonly authorRepository: AuthorRepository) {}
}
上一页