数据库(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
。请注意
,如果没有名称或名称相同,则不应该有多个连接,否则它们只会被覆盖。
在这一点上,你有你的每一个的Photo
,Person
并Album
登记在自己的连接实体。使用此设置,您必须告诉TypeOrmModule.forFeature()
函数和@InjectRepository()
装饰器应该使用哪个连接。如果未传递任何连接名称,default
则使用连接。
@Module{
TypeOrmModule.forFeature([Photo]),
TypeOrmModule.forFeature([Person], 'personsConnection'),
TypeOrmModule.forFeature([Album], 'albumsConnection')
})
export class ApplicationModule {}
您还可以为给定连接注入Connection
或EntityManager
:
@Injectable()
export class PersonService {
constructor(
@InjectConnection('personsConnection')
private readonly connection: Connection,
@InjectEntityManager('personsConnection')
private readonly entityManager: EntityManager
) {}
}
测试
在单元测试我们的应用程序时,我们通常希望避免任何数据库连接,使我们的测试套件独立并尽可能快地执行它们。但是我们的类可能依赖于从连接实例中提取的存储库。那是什么?解决方案是创建虚假存储库。为了实现这一点,我们应该设置自定义提供程序。实际上,每个注册的存储库都由EntityNameRepository
token表示,其中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
,而不是单独实例化它。