Nestjs操作mysql数据库_Nestjs typeOrm
NestJs仿小米商城项目实战视频教程(视频+课件+源码): https://www.itying.com/goods-1139.html
Nestjs数据库
Nest与数据库无关,允许您轻松地与任何SQL或NoSQL数据库集成。根据您的偏好,您有许多可用的选项。一般来说,将Nest连接到数据库只需为数据库加载一个适当的Node.js驱动程序,就像使用 Express 或Fastify一样。
您还可以直接使用任何通用的Node.js数据库集成库或ORM,例如 Sequelize (recipe)、knexjs (tutorial)`和 TypeORM ,以在更高的抽象级别上进行操作。
为了方便起见,Nest还提供了与现成的TypeORM与@nestjs/typeorm的紧密集成,我们将在本章中对此进行介绍,而与@nestjs/mongoose的紧密集成将在本章中介绍。这些集成提供了附加的特定于nestjs的特性,比如模型/存储库注入、可测试性和异步配置,从而使访问您选择的数据库更加容易。
Nestjs TypeORM 集成
为了与SQL和NoSQL数据库集成,Nest提供了@nestjs/typeorm包。Nest使用TypeORM是因为它是TypeScript中最成熟的对象关系映射器(ORM)。因为它是用TypeScript编写的,所以可以很好地与Nest框架集成。
为了开始使用它,我们首先安装所需的依赖项。在本章中,我们将演示如何使用流行的 Mysql ,TypeORM提供了对许多关系数据库的支持,比如PostgreSQL、Oracle、Microsoft SQL Server、SQLite,甚至像MongoDB这样的NoSQL数据库。我们在本章中介绍的过程对于TypeORM支持的任何数据库都是相同的。您只需为所选数据库安装相关的客户端API库。
$ npm install --save @nestjs/typeorm typeorm mysql
安装过程完成后,我们可以将TypeOrmModule导入AppModule。
app.module.ts
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 }
?> 静态全局路径(例如dist/**/*.entity{ .ts,.js})不适用于Webpack热重载。
然后,我们可以调用forRoot()没有任何选项:
app.module.ts
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ imports: [TypeOrmModule.forRoot()], }) export class AppModule {}
一旦完成,TypeORM连接和EntityManager对象就可以在整个项目中注入(不需要导入任何模块),例如:
app.module.ts
import { Connection } from 'typeorm'; @Module({ imports: [TypeOrmModule.forRoot(), PhotoModule], }) export class AppModule { constructor(private readonly connection: Connection) {} }
存储库模式
TypeORM支持存储库设计模式,因此每个实体都有自己的存储库。可以从数据库连接获得这些存储库。
为了继续这个示例,我们需要至少一个实体。我们将使用官方TypeORM文档中的Photo实体。
photo/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。这是你决定在哪里保留你的模型文件。从我的观点来看,最好的方法是将它们放在他们的域中, 放在相应的模块目录中。
开始使用photo实体,我们需要让TypeORM知道它插入实体数组:
app.module.ts
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Photo } from './photo/photo.entity'; @Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'root', database: 'test', entities: [Photo], synchronize: true, }), ], }) export class AppModule {}
现在让我们看一下PhotoModule:
photo.module.ts
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()方法定义在当前范围中注册哪些存储库。这样,我们就可以使用@InjectRepository()装饰器将PhotoRepository注入到PhotoService中:
photo.service.ts
import { Injectable } 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>, ) {} findAll(): Promise<Photo[]> { return this.photoRepository.find(); } }
?> 不要忘记将PhotoModule导入根ApplicationModule。
如果要在导入TypeOrmModule.forFeature的模块之外使用存储库,则需要重新导出由其生成的提供程序。 您可以通过导出整个模块来做到这一点,如下所示:
photo.module.ts
import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Photo } from './photo.entity'; @Module({ imports: [TypeOrmModule.forFeature([Photo])], exports: [TypeOrmModule] }) export class PhotoModule {}
现在,如果我们在PhotoHttpModule中导入PhotoModule,我们可以在后一个模块的提供者中使用@InjectRepository(Photo)。
photo-http.module.ts
import { Module } from '@nestjs/common'; import { PhotoModule } from './photo.module'; import { PhotoService } from './photo.service'; import { PhotoController } from './photo.controller'; @Module({ imports: [PhotoModule], providers: [PhotoService], controllers: [PhotoController] }) export class PhotoHttpModule {}
多个数据库
某些项目可能需要多个数据库连接。幸运的是,这也可以通过本模块实现。要使用多个连接,首先要做的是创建这些连接。在这种情况下,连接命名成为必填项。
假设你有一个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 {}
?> 如果未为连接设置任何name,则该连接的名称将设置为default。请注意,不应该有多个没有名称或同名的连接,否则它们会被覆盖。
此时,您的Photo、Person和Album实体中的每一个都已在各自的连接中注册。通过此设置,您必须告诉TypeOrmModule.forFeature()函数和@InjectRepository()装饰器应该使用哪种连接。如果不传递任何连接名称,则使用default连接。
@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, ) {} }
测试
在单元测试我们的应用程序时,我们通常希望避免任何数据库连接,从而使我们的测试适合于独立,并使它们的执行过程尽可能快。但是我们的类可能依赖于从连接实例中提取的存储库。那是什么?解决方案是创建假存储库。为了实现这一点,我们设置了自定义提供者。事实上,每个注册的存储库都由entitynamereposition标记表示,其中EntityName是实体类的名称。
@nestjs/typeorm包提供了基于给定实体返回准备好token的getRepositoryToken()函数。
@Module({ providers: [ PhotoService, { provide: getRepositoryToken(Photo), useValue: mockRepository, }, ], }) export class PhotoModule {}
现在, 将使用硬编码mockRepository作为PhotoRepository。每当任何提供程序使用@InjectRepository()装饰器请求PhotoRepository时,Nest会使用注册的mockRepository对象。
定制存储库
TypeORM提供称为自定义存储库的功能。要了解有关它的更多信息,请访问此页面。基本上,自定义存储库允许您扩展基本存储库类,并使用几种特殊方法对其进行丰富。
要创建自定义存储库,请使用@EntityRepository()装饰器和扩展Repository类。
@EntityRepository(Author) export class AuthorRepository extends Repository<Author> {}
?>@EntityRepository()和Repository来自typeorm包。
创建类后,下一步是将实例化责任移交给Nest。为此,我们必须将AuthorRepository类传递给TypeOrm.forFeature()函数。
@Module({ imports: [TypeOrmModule.forFeature([AuthorRepository])], controller: [AuthorController], providers: [AuthorService], }) export class AuthorModule {}
之后,只需使用以下构造注入存储库:
@Injectable() export class AuthorService { constructor(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, }), });
我们的工厂的行为与任何其他异步提供者一样(例如,它可以是异步的,并且它能够通过注入注入依赖)。
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, }; } }
为了防止在TypeOrmModule中创建TypeOrmConfigService并使用从不同模块导入的提供程序,可以使用useExisting语法。
TypeOrmModule.forRootAsync({ imports: [ConfigModule], useExisting: ConfigService, });
这个构造与useClass的工作原理相同,但有一个关键的区别 —TypeOrmModule将查找导入的模块来重用现有的ConfigService,而不是实例化一个新的ConfigService。
示例
这儿有一个可用的例子。