Nest 與數(shù)據(jù)庫(kù)無(wú)關(guān),允許您輕松地與任何 SQL 或 NoSQL 數(shù)據(jù)庫(kù)集成。根據(jù)您的偏好,您有許多可用的選項(xiàng)。一般來(lái)說(shuō),將 Nest 連接到數(shù)據(jù)庫(kù)只需為數(shù)據(jù)庫(kù)加載一個(gè)適當(dāng)?shù)?nbsp;Node.js 驅(qū)動(dòng)程序,就像使用 Express 或 Fastify 一樣。
您還可以直接使用任何通用的 Node.js 數(shù)據(jù)庫(kù)集成庫(kù)或 ORM ,例如 Sequelize (recipe)、knexjs (tutorial)`和 TypeORM ,以在更高的抽象級(jí)別上進(jìn)行操作。
為了方便起見(jiàn),Nest 還提供了與現(xiàn)成的 TypeORM 與 @nestjs/typeorm 的緊密集成,我們將在本章中對(duì)此進(jìn)行介紹,而與 @nestjs/mongoose 的緊密集成將在這一章中介紹。這些集成提供了附加的特定于 nestjs 的特性,比如模型/存儲(chǔ)庫(kù)注入、可測(cè)試性和異步配置,從而使訪問(wèn)您選擇的數(shù)據(jù)庫(kù)更加容易。
為了與 SQL和 NoSQL 數(shù)據(jù)庫(kù)集成,Nest 提供了 @nestjs/typeorm 包。Nest 使用TypeORM是因?yàn)樗?nbsp;TypeScript 中最成熟的對(duì)象關(guān)系映射器( ORM )。因?yàn)樗怯?nbsp;TypeScript 編寫的,所以可以很好地與 Nest 框架集成。
為了開始使用它,我們首先安裝所需的依賴項(xiàng)。在本章中,我們將演示如何使用流行的 Mysql , TypeORM 提供了對(duì)許多關(guān)系數(shù)據(jù)庫(kù)的支持,比如 PostgreSQL 、Oracle、Microsoft SQL Server、SQLite,甚至像 MongoDB這樣的 NoSQL 數(shù)據(jù)庫(kù)。我們?cè)诒菊轮薪榻B的過(guò)程對(duì)于 TypeORM 支持的任何數(shù)據(jù)庫(kù)都是相同的。您只需為所選數(shù)據(jù)庫(kù)安裝相關(guān)的客戶端 API 庫(kù)。
$ npm install --save @nestjs/typeorm typeorm mysql2
安裝過(guò)程完成后,我們可以將 TypeOrmModule 導(dǎo)入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()函數(shù)暴露出的配置屬性。其他一些額外的配置參數(shù)描述如下:
參數(shù) | 說(shuō)明 |
---|---|
retryAttempts | 重試連接數(shù)據(jù)庫(kù)的次數(shù)(默認(rèn):10) |
retryDelay | 兩次重試連接的間隔(ms)(默認(rèn):3000) |
autoLoadEntities | 如果為true ,將自動(dòng)加載實(shí)體(默認(rèn):false) |
keepConnectionAlive | 如果為true ,在應(yīng)用程序關(guān)閉后連接不會(huì)關(guān)閉(默認(rèn):false) |
更多連接選項(xiàng)見(jiàn)這里
另外,我們可以創(chuàng)建 ormconfig.json ,而不是將配置對(duì)象傳遞給 forRoot()。
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}
然后,我們可以不帶任何選項(xiàng)地調(diào)用 forRoot() :
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
})
export class AppModule {}
靜態(tài)全局路徑(例如 dist/**/*.entity{ .ts,.js} )不適用于 Webpack 熱重載。
注意,ormconfig.json 文件由typeorm庫(kù)載入,因此,任何上述參數(shù)之外的屬性都不會(huì)被應(yīng)用(例如由forRoot()方法內(nèi)部支持的屬性–例如autoLoadEntities和retryDelay())
一旦完成,TypeORM 的Connection和 EntityManager 對(duì)象就可以在整個(gè)項(xiàng)目中注入(不需要導(dǎo)入任何模塊),例如:
app.module.ts
import { Connection } from 'typeorm';
@Module({
imports: [TypeOrmModule.forRoot(), PhotoModule],
})
export class AppModule {
constructor(private readonly connection: Connection) {}
}
TypeORM 支持存儲(chǔ)庫(kù)設(shè)計(jì)模式,因此每個(gè)實(shí)體都有自己的存儲(chǔ)庫(kù)??梢詮臄?shù)據(jù)庫(kù)連接獲得這些存儲(chǔ)庫(kù)。
為了繼續(xù)這個(gè)示例,我們需要至少一個(gè)實(shí)體。我們來(lái)定義User 實(shí)體。
user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
}
關(guān)于實(shí)體的更多內(nèi)容見(jiàn)TypeORM 文檔。
該 User 實(shí)體在 users 目錄下。這個(gè)目錄包含了和 UsersModule模塊有關(guān)的所有文件。你可以決定在哪里保存模型文件,但我們推薦在他們的域中就近創(chuàng)建,即在相應(yīng)的模塊目錄中。
要開始使用 user 實(shí)體,我們需要在模塊的forRoot()方法的選項(xiàng)中(除非你使用一個(gè)靜態(tài)的全局路徑)將它插入entities數(shù)組中來(lái)讓 TypeORM知道它的存在。
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [User],
synchronize: true,
}),
],
})
export class AppModule {}
現(xiàn)在讓我們看一下 UsersModule:
user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
此模塊使用 forFeature() 方法定義在當(dāng)前范圍中注冊(cè)哪些存儲(chǔ)庫(kù)。這樣,我們就可以使用 @InjectRepository()裝飾器將 UsersRepository 注入到 UsersService 中:
users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: string): Promise<User> {
return this.usersRepository.findOne(id);
}
async remove(id: string): Promise<void> {
await this.usersRepository.delete(id);
}
}
不要忘記將 UsersModule 導(dǎo)入根 AppModule。
如果要在導(dǎo)入TypeOrmModule.forFeature 的模塊之外使用存儲(chǔ)庫(kù),則需要重新導(dǎo)出由其生成的提供程序。 您可以通過(guò)導(dǎo)出整個(gè)模塊來(lái)做到這一點(diǎn),如下所示:
users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
exports: [TypeOrmModule],
})
export class UsersModule {}
現(xiàn)在,如果我們?cè)?nbsp;UserHttpModule 中導(dǎo)入 UsersModule ,我們可以在后一個(gè)模塊的提供者中使用 @InjectRepository(User)。
users-http.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './user.module';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@Module({
imports: [UsersModule],
providers: [UsersService],
controllers: [UsersController],
})
export class UserHttpModule {}
關(guān)系是指兩個(gè)或多個(gè)表之間的聯(lián)系。關(guān)系基于每個(gè)表中的常規(guī)字段,通常包含主鍵和外鍵。
關(guān)系有三種:
名稱 | 說(shuō)明 |
---|---|
一對(duì)一 | 主表中的每一行在外部表中有且僅有一個(gè)對(duì)應(yīng)行。使用@OneToOne() 裝飾器來(lái)定義這種類型的關(guān)系 |
一對(duì)多/多對(duì)一 | 主表中的每一行在外部表中有一個(gè)或多的對(duì)應(yīng)行。使用@OneToMany() 和@ManyToOne() 裝飾器來(lái)定義這種類型的關(guān)系 |
多對(duì)多 | 主表中的每一行在外部表中有多個(gè)對(duì)應(yīng)行,外部表中的每個(gè)記錄在主表中也有多個(gè)行。使用@ManyToMany() 裝飾器來(lái)定義這種類型的關(guān)系 |
使用對(duì)應(yīng)的裝飾器來(lái)定義實(shí)體的關(guān)系。例如,要定義每個(gè)User可以有多個(gè)Photo,可以使用@OneToMany()裝飾器。
user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { Photo } from '../photos/photo.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
@Column({ default: true })
isActive: boolean;
@OneToMany((type) => Photo, (photo) => photo.user)
photos: Photo[];
}
要了解 TypeORM 中關(guān)系的內(nèi)容,可以查看TypeORM 文檔。
手動(dòng)將實(shí)體一一添加到連接選項(xiàng)的entities數(shù)組中的工作會(huì)很無(wú)聊。此外,在根模塊中涉及實(shí)體破壞了應(yīng)用的域邊界,并可能將應(yīng)用的細(xì)節(jié)泄露給應(yīng)用的其他部分。針對(duì)這一情況,可以使用靜態(tài)全局路徑(例如, dist/*/.entity{.ts,.js})。
注意,webpack不支持全局路徑,因此如果你要在單一倉(cāng)庫(kù)(Monorepo)中構(gòu)建應(yīng)用,可能不能使用全局路徑。針對(duì)這一問(wèn)題,有另外一個(gè)可選的方案。在配置對(duì)象的屬性中(傳遞給forRoot()方法的)設(shè)置autoLoadEntities屬性為true來(lái)自動(dòng)載入實(shí)體,示意如下:
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
...
autoLoadEntities: true,
}),
],
})
export class AppModule {}
通過(guò)配置這一選項(xiàng),每個(gè)通過(guò)forFeature()注冊(cè)的實(shí)體都會(huì)自動(dòng)添加到配置對(duì)象的entities數(shù)組中。
注意,那些沒(méi)有通過(guò)forFeature()方法注冊(cè),而僅僅是在實(shí)體中被引用(通過(guò)關(guān)系)的實(shí)體不能通過(guò)autoLoadEntities配置被包含。
數(shù)據(jù)庫(kù)事務(wù)代表在數(shù)據(jù)庫(kù)管理系統(tǒng)(DBMS)中針對(duì)數(shù)據(jù)庫(kù)的一組操作,這組操作是有關(guān)的、可靠的并且和其他事務(wù)相互獨(dú)立的。一個(gè)事務(wù)通??梢源頂?shù)據(jù)庫(kù)中的任何變更(了解更多)。
在TypeORM 事務(wù)中有很多不同策略來(lái)處理事務(wù),我們推薦使用QueryRunner類,因?yàn)樗鼘?duì)事務(wù)是完全可控的。
首先,我們需要將Connection對(duì)象以正常方式注入:
@Injectable()
export class UsersService {
constructor(private connection: Connection) {}
}
Connection類需要從typeorm包中導(dǎo)入
現(xiàn)在,我們可以使用這個(gè)對(duì)象來(lái)創(chuàng)建一個(gè)事務(wù)。
async createMany(users: User[]) {
const queryRunner = this.connection.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
await queryRunner.manager.save(users[0]);
await queryRunner.manager.save(users[1]);
await queryRunner.commitTransaction();
} catch (err) {
//如果遇到錯(cuò)誤,可以回滾事務(wù)
await queryRunner.rollbackTransaction();
} finally {
//你需要手動(dòng)實(shí)例化并部署一個(gè)queryRunner
await queryRunner.release();
}
}
注意connection僅用于創(chuàng)建QueryRunner。然而,要測(cè)試這個(gè)類,就需要模擬整個(gè)Connection對(duì)象(它暴露出來(lái)的幾個(gè)方法),因此,我們推薦采用一個(gè)幫助工廠類(也就是QueryRunnerFactory)并且定義一個(gè)包含僅限于維持事務(wù)需要的方法的接口。這一技術(shù)讓模擬這些方法變得非常直接。
可選地,你可以使用一個(gè)Connection對(duì)象的回調(diào)函數(shù)風(fēng)格的transaction方法(閱讀更多)。
async createMany(users: User[]) {
await this.connection.transaction(async manager => {
await manager.save(users[0]);
await manager.save(users[1]);
});
}
不推薦使用裝飾器來(lái)控制事務(wù)(@Transaction()和@TransactionManager())。
使用 TypeORM訂閱者,你可以監(jiān)聽(tīng)特定的實(shí)體事件。
import { Connection, EntitySubscriberInterface, EventSubscriber, InsertEvent } from 'typeorm';
import { User } from './user.entity';
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
constructor(connection: Connection) {
connection.subscribers.push(this);
}
listenTo() {
return User;
}
beforeInsert(event: InsertEvent<User>) {
console.log(`BEFORE USER INSERTED: `, event.entity);
}
}
事件訂閱者不能是請(qǐng)求范圍的。
現(xiàn)在,將UserSubscriber類添加到providers數(shù)組。
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { UserSubscriber } from './user.subscriber';
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService, UserSubscriber],
controllers: [UsersController],
})
export class UsersModule {}
更多實(shí)體訂閱者內(nèi)容見(jiàn)這里。
遷移提供了一個(gè)在保存數(shù)據(jù)庫(kù)中現(xiàn)有數(shù)據(jù)的同時(shí)增量升級(jí)數(shù)據(jù)庫(kù)使其與應(yīng)用中的數(shù)據(jù)模型保持同步的方法。TypeORM 提供了一個(gè)專用CLI 命令行工具用于生成、運(yùn)行以及回滾遷移。
遷移類和Nest應(yīng)用源碼是分開的。他們的生命周期由TypeORM CLI管理,因此,你不能在遷移中使用依賴注入和其他Nest專有特性。在TypeORM 文檔 中查看更多關(guān)于遷移的內(nèi)容。
某些項(xiàng)目可能需要多個(gè)數(shù)據(jù)庫(kù)連接。這也可以通過(guò)本模塊實(shí)現(xiàn)。要使用多個(gè)連接,首先要做的是創(chuàng)建這些連接。在這種情況下,連接命名成為必填項(xiàng)。
假設(shè)你有一個(gè)Album 實(shí)體存儲(chǔ)在他們自己的數(shù)據(jù)庫(kù)中。
const defaultOptions = {
type: 'postgres',
port: 5432,
username: 'user',
password: 'password',
database: 'db',
synchronize: true,
};
@Module({
imports: [
TypeOrmModule.forRoot({
...defaultOptions,
host: 'user_db_host',
entities: [User],
}),
TypeOrmModule.forRoot({
...defaultOptions,
name: 'albumsConnection',
host: 'album_db_host',
entities: [Album],
}),
],
})
export class AppModule {}
如果未為連接設(shè)置任何 name ,則該連接的名稱將設(shè)置為 default。請(qǐng)注意,不應(yīng)該有多個(gè)沒(méi)有名稱或同名的連接,否則它們會(huì)被覆蓋。
此時(shí),您的User 和 Album 實(shí)體中的每一個(gè)都已在各自的連接中注冊(cè)。通過(guò)此設(shè)置,您必須告訴 TypeOrmModule.forFeature() 方法和 @InjectRepository() 裝飾器應(yīng)該使用哪種連接。如果不傳遞任何連接名稱,則使用 default 連接。
@Module({
imports: [TypeOrmModule.forFeature([User]), TypeOrmModule.forFeature([Album], 'albumsConnection')],
})
export class AppModule {}
您也可以為給定的連接注入 Connection 或 EntityManager:
@Injectable()
export class AlbumsService {
constructor(
@InjectConnection('albumsConnection')
private connection: Connection,
@InjectEntityManager('albumsConnection')
private entityManager: EntityManager
) {}
}
在單元測(cè)試我們的應(yīng)用程序時(shí),我們通常希望避免任何數(shù)據(jù)庫(kù)連接,從而使我們的測(cè)試適合于獨(dú)立,并使它們的執(zhí)行過(guò)程盡可能快。但是我們的類可能依賴于從連接實(shí)例中提取的存儲(chǔ)庫(kù)。那是什么?解決方案是創(chuàng)建假存儲(chǔ)庫(kù)。為了實(shí)現(xiàn)這一點(diǎn),我們?cè)O(shè)置了[自定義提供者]。事實(shí)上,每個(gè)注冊(cè)的存儲(chǔ)庫(kù)都由 entitynamereposition 標(biāo)記表示,其中 EntityName 是實(shí)體類的名稱。
@nestjs/typeorm 包提供了基于給定實(shí)體返回準(zhǔn)備好 token 的 getRepositoryToken() 函數(shù)。
@Module({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: mockRepository,
},
],
})
export class UsersModule {}
現(xiàn)在, 將使用mockRepository 作為 UsersRepository。每當(dāng)任何提供程序使用 @InjectRepository() 裝飾器請(qǐng)求 UsersRepository 時(shí), Nest 會(huì)使用注冊(cè)的 mockRepository 對(duì)象。
TypeORM 提供稱為自定義存儲(chǔ)庫(kù)的功能。要了解有關(guān)它的更多信息,請(qǐng)?jiān)L問(wèn)此頁(yè)面?;旧希远x存儲(chǔ)庫(kù)允許您擴(kuò)展基本存儲(chǔ)庫(kù)類,并使用幾種特殊方法對(duì)其進(jìn)行豐富。
要?jiǎng)?chuàng)建自定義存儲(chǔ)庫(kù),請(qǐng)使用 @EntityRepository() 裝飾器和擴(kuò)展 Repository 類。
@EntityRepository(Author)
export class AuthorRepository extends Repository<Author> {}
@EntityRepository() 和 Repository 來(lái)自 typeorm 包。
創(chuàng)建類后,下一步是將實(shí)例化責(zé)任移交給 Nest。為此,我們必須將 AuthorRepository 類傳遞給 TypeOrm.forFeature() 函數(shù)。
@Module({
imports: [TypeOrmModule.forFeature([AuthorRepository])],
controller: [AuthorController],
providers: [AuthorService],
})
export class AuthorModule {}
之后,只需使用以下構(gòu)造注入存儲(chǔ)庫(kù):
@Injectable()
export class AuthorService {
constructor(private readonly authorRepository: AuthorRepository) {}
}
通常,您可能希望異步傳遞模塊選項(xiàng),而不是事先傳遞它們。在這種情況下,使用 forRootAsync() 函數(shù),提供了幾種處理異步數(shù)據(jù)的方法。
第一種可能的方法是使用工廠函數(shù):
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
});
我們的工廠的行為與任何其他異步提供者一樣(例如,它可以是異步的,并且它能夠通過(guò)inject注入依賴)。
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'mysql',
host: configService.get<string>('HOST'),
port: configService.get<string>('PORT'),
username: configService.get<string>('USERNAME'),
password: configService.get<string>('PASSWORD'),
database: configService.get<string>('DATABASE'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
inject: [ConfigService],
});
或者,您可以使用useClass語(yǔ)法。
TypeOrmModule.forRootAsync({
useClass: TypeOrmConfigService,
});
上面的構(gòu)造將 TypeOrmConfigService 在內(nèi)部進(jìn)行實(shí)例化 TypeOrmModule,并將利用它來(lái)創(chuàng)建選項(xiàng)對(duì)象。在 TypeOrmConfigService 必須實(shí)現(xiàn) TypeOrmOptionsFactory 的接口。
上面的構(gòu)造將在TypeOrmModule內(nèi)部實(shí)例化TypeOrmConfigService,并通過(guò)調(diào)用createTypeOrmOptions()
@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 中創(chuàng)建 TypeOrmConfigService 并使用從不同模塊導(dǎo)入的提供程序,可以使用 useExisting 語(yǔ)法。
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});
這個(gè)構(gòu)造與 useClass 的工作原理相同,但有一個(gè)關(guān)鍵的區(qū)別 — TypeOrmModule 將查找導(dǎo)入的模塊來(lái)重用現(xiàn)有的 ConfigService,而不是實(shí)例化一個(gè)新的 ConfigService。
這兒有一個(gè)可用的例子。
另一個(gè)使用TypeORM的選擇是使用@nestjs/sequelize包中的Sequelize ROM。額外地,我們使用sequelize-typescript包來(lái)提供一系列額外的裝飾器以聲明實(shí)體。
要開始使用它,我們首先安裝需要的依賴。在本章中,我們通過(guò)流行的MySQL關(guān)系數(shù)據(jù)庫(kù)來(lái)進(jìn)行說(shuō)明。Sequelize支持很多種關(guān)系數(shù)據(jù)庫(kù),例如PostgreSQL,MySQL,Microsoft SQL Server,SQLite以及MariaDB。本章中的步驟也適合其他任何Sequelize支持的數(shù)據(jù)庫(kù)。你只要簡(jiǎn)單地安裝所選數(shù)據(jù)庫(kù)相應(yīng)的客戶端 API 庫(kù)就可以。
$ npm install --save @nestjs/sequelize sequelize sequelize-typescript mysql2
$ npm install --save-dev @types/sequelize
安裝完成后,就可以將SequelizeModule導(dǎo)入到根AppModule中。
app.module.ts
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
@Module({
imports: [
SequelizeModule.forRoot({
dialect: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
models: [],
}),
],
})
export class AppModule {}
forRoot()方法支持所有Sequelize構(gòu)造器(了解更多)暴露的配置屬性。下面是一些額外的配置屬性。
名稱 | 說(shuō)明 |
---|---|
retryAttempts | 嘗試連接數(shù)據(jù)庫(kù)的次數(shù)(默認(rèn):10) |
retryDelay | 兩次連接之間間隔時(shí)間(ms)(默認(rèn):3000) |
autoLoadModels | 如果為true ,模型將自動(dòng)載入(默認(rèn):false) |
keepConnectionAlive | 如果為true ,在應(yīng)用關(guān)閉后連接將不會(huì)關(guān)閉(默認(rèn):false) |
synchronize | 如果為true ,自動(dòng)載入的模型將同步(默認(rèn):false) |
一旦這些完成了,Sequelize對(duì)象就可以注入到整個(gè)項(xiàng)目中(不需要在任何模塊中再引入),例如:
app.service.ts
import { Injectable } from '@nestjs/common';
import { Sequelize } from 'sequelize-typescript';
@Injectable()
export class AppService {
constructor(private sequelize: Sequelize) {}
}
Sequelize采用活動(dòng)記錄(Active Record)模式,在這一模式下,你可以使用模型類直接和數(shù)據(jù)庫(kù)交互。要繼續(xù)該示例,我們至少需要一個(gè)模型,讓我們定義這個(gè)User模型:
user.model.ts
import { Column, Model, Table } from 'sequelize-typescript';
@Table
export class User extends Model<User> {
@Column
firstName: string;
@Column
lastName: string;
@Column({ defaultValue: true })
isActive: boolean;
}
查看更多的可用裝飾器。
User模型文件在users目錄下。該目錄包含了和UsersModule有關(guān)的所有文件。你可以決定在哪里保存模型文件,但我們推薦在他們的域中就近創(chuàng)建,即在相應(yīng)的模塊目錄中。
要開始使用User模型,我們需要通過(guò)將其插入到forRoot()方法選項(xiàng)的models數(shù)組中來(lái)讓Sequelize知道它的存在。
app.module.ts
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { User } from './users/user.model';
@Module({
imports: [
SequelizeModule.forRoot({
dialect: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
models: [User],
}),
],
})
export class AppModule {}
接下來(lái)我們看看UsersModule:
users.module.ts
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { User } from './user.model';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [SequelizeModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
這個(gè)模塊使用forFeature()方法來(lái)定義哪個(gè)模型被注冊(cè)在當(dāng)前范圍中。我們可以使用@InjectModel()裝飾器來(lái)把UserModel注入到UsersService中。
users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { User } from './user.model';
@Injectable()
export class UsersService {
constructor(
@InjectModel(User)
private userModel: typeof User
) {}
async findAll(): Promise<User[]> {
return this.userModel.findAll();
}
findOne(id: string): Promise<User> {
return this.userModel.findOne({
where: {
id,
},
});
}
async remove(id: string): Promise<void> {
const user = await this.findOne(id);
await user.destroy();
}
}
不要忘記在根AppModule中導(dǎo)入U(xiǎn)sersModule。
如果你要在導(dǎo)入SequelizeModule.forFreature的模塊之外使用存儲(chǔ)庫(kù),你需要重新導(dǎo)出其生成的提供者。你可以像這樣將整個(gè)模塊導(dǎo)出:
users.module.ts
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { User } from './user.entity';
@Module({
imports: [SequelizeModule.forFeature([User])],
exports: [SequelizeModule],
})
export class UsersModule {}
現(xiàn)在如果我們?cè)赨serHttpModule中引入U(xiǎn)sersModule,我們可以在后一個(gè)模塊的提供者中使用@InjectModel(User)。
users-http.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './user.module';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@Module({
imports: [UsersModule],
providers: [UsersService],
controllers: [UsersController],
})
export class UserHttpModule {}
關(guān)系是指兩個(gè)或多個(gè)表之間的聯(lián)系。關(guān)系基于每個(gè)表中的常規(guī)字段,通常包含主鍵和外鍵。
關(guān)系有三種:
名稱 | 說(shuō)明 |
---|---|
一對(duì)一 | 主表中的每一行在外部表中有且僅有一個(gè)對(duì)應(yīng)行。使用@OneToOne() 裝飾器來(lái)定義這種類型的關(guān)系 |
一對(duì)多/多對(duì)一 | 主表中的每一行在外部表中有一個(gè)或多的對(duì)應(yīng)行。使用@OneToMany() 和@ManyToOne() 裝飾器來(lái)定義這種類型的關(guān)系 |
多對(duì)多 | 主表中的每一行在外部表中有多個(gè)對(duì)應(yīng)行,外部表中的每個(gè)記錄在主表中也有多個(gè)行。使用@ManyToMany() 裝飾器來(lái)定義這種類型的關(guān)系 |
使用對(duì)應(yīng)的裝飾器來(lái)定義實(shí)體的關(guān)系。例如,要定義每個(gè)User可以有多個(gè)Photo,可以使用@HasMany()裝飾器。
user.entity.ts
import { Column, Model, Table, HasMany } from 'sequelize-typescript';
import { Photo } from '../photos/photo.model';
@Table
export class User extends Model<User> {
@Column
firstName: string;
@Column
lastName: string;
@Column({ defaultValue: true })
isActive: boolean;
@HasMany(() => Photo)
photos: Photo[];
}
閱讀本章了解更多關(guān)于Sequelize的內(nèi)容。
手動(dòng)將模型一一添加到連接選項(xiàng)的models數(shù)組中的工作會(huì)很無(wú)聊。此外,在根模塊中涉及實(shí)體破壞了應(yīng)用的域邊界,并可能將應(yīng)用的細(xì)節(jié)泄露給應(yīng)用的其他部分。針對(duì)這一情況,在配置對(duì)象的屬性中(傳遞給forRoot()方法的)設(shè)置autoLoadModels和synchronize屬性來(lái)自動(dòng)載入模型,示意如下:
app.module.ts
import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
@Module({
imports: [
SequelizeModule.forRoot({
...
autoLoadModels: true,
synchronize: true,
}),
],
})
export class AppModule {}
通過(guò)配置這一選項(xiàng),每個(gè)通過(guò)forFeature()注冊(cè)的實(shí)體都會(huì)自動(dòng)添加到配置對(duì)象的models數(shù)組中。
注意,這不包含那些沒(méi)有通過(guò)forFeature()方法注冊(cè),而僅僅是在實(shí)體中被引用(通過(guò)關(guān)系)的模型。
數(shù)據(jù)庫(kù)事務(wù)代表在數(shù)據(jù)庫(kù)管理系統(tǒng)(DBMS)中針對(duì)數(shù)據(jù)庫(kù)的一組操作,這組操作是有關(guān)的、可靠的并且和其他事務(wù)相互獨(dú)立的。一個(gè)事務(wù)通??梢源頂?shù)據(jù)庫(kù)中的任何變更(了解更多)。
在Sequelize事務(wù)中有很多不同策略來(lái)處理事務(wù),下面是一個(gè)管理事務(wù)的示例(自動(dòng)回調(diào))。
首先,我們需要將Sequelize對(duì)象以正常方式注入:
@Injectable()
export class UsersService {
constructor(private sequelize: Sequelize) {}
}
Sequelize類需要從sequelize-typescript包中導(dǎo)入
現(xiàn)在,我們可以使用這個(gè)對(duì)象來(lái)創(chuàng)建一個(gè)事務(wù)。
async createMany() {
try {
await this.sequelize.transaction(async t => {
const transactionHost = { transaction: t };
await this.userModel.create(
{ firstName: 'Abraham', lastName: 'Lincoln' },
transactionHost,
);
await this.userModel.create(
{ firstName: 'John', lastName: 'Boothe' },
transactionHost,
);
});
} catch (err) {
// 一旦發(fā)生錯(cuò)誤,事務(wù)會(huì)回滾
}
}
注意Sequelize僅用于開始一個(gè)事務(wù)。然而,要測(cè)試這個(gè)類,就需要模擬整個(gè)Sequelize對(duì)象(它暴露出來(lái)的幾個(gè)方法),因此,我們推薦采用一個(gè)幫助工廠類(也就是TransactionRunner)并且定義一個(gè)包含僅限于維持事務(wù)需要的方法的接口。這一技術(shù)讓模擬這些方法變得非常直接。
可選地,你可以使用一個(gè)Connection對(duì)象的回調(diào)函數(shù)風(fēng)格的transaction方法(閱讀更多)。
async createMany(users: User[]) {
await this.connection.transaction(async manager => {
await manager.save(users[0]);
await manager.save(users[1]);
});
}
不推薦使用裝飾器來(lái)控制事務(wù)(@Transaction()和@TransactionManager())。
遷移提供了一個(gè)在保存數(shù)據(jù)庫(kù)中現(xiàn)有數(shù)據(jù)的同時(shí)增量升級(jí)數(shù)據(jù)庫(kù)使其與應(yīng)用中的數(shù)據(jù)模型保持同步的方法。Sequelize提供了一個(gè)專用CLI 命令行工具用于生成、運(yùn)行以及回滾遷移。
遷移類和Nest應(yīng)用源碼是分開的。他們的生命周期由TypeORM CLI管理,因此,你不能在遷移中使用依賴注入和其他Nest專有特性。在Sequelize文檔 中查看更多關(guān)于遷移的內(nèi)容。
某些項(xiàng)目可能需要多個(gè)數(shù)據(jù)庫(kù)連接。這也可以通過(guò)本模塊實(shí)現(xiàn)。要使用多個(gè)連接,首先要做的是創(chuàng)建這些連接。在這種情況下,連接命名成為必填項(xiàng)。
假設(shè)你有一個(gè)Album 實(shí)體存儲(chǔ)在他們自己的數(shù)據(jù)庫(kù)中。
const defaultOptions = {
dialect: 'postgres',
port: 5432,
username: 'user',
password: 'password',
database: 'db',
synchronize: true,
};
@Module({
imports: [
SequelizeModule.forRoot({
...defaultOptions,
host: 'user_db_host',
models: [User],
}),
SequelizeModule.forRoot({
...defaultOptions,
name: 'albumsConnection',
host: 'album_db_host',
models: [Album],
}),
],
})
export class AppModule {}
如果未為連接設(shè)置任何 name ,則該連接的名稱將設(shè)置為 default。請(qǐng)注意,不應(yīng)該有多個(gè)沒(méi)有名稱或同名的連接,否則它們會(huì)被覆蓋。
此時(shí),您的User 和 Album 實(shí)體中的每一個(gè)都已在各自的連接中注冊(cè)。通過(guò)此設(shè)置,您必須告訴 SequelizeModule.forFeature() 方法和 @InjectRepository() 裝飾器應(yīng)該使用哪種連接。如果不傳遞任何連接名稱,則使用 default 連接。
@Module({
imports: [SequelizeModule.forFeature([User]), SequelizeModule.forFeature([Album], 'albumsConnection')],
})
export class AppModule {}
您也可以為給定的連接注入 Sequelize:
@Injectable()
export class AlbumsService {
constructor(
@InjectConnection('albumsConnection')
private sequelize: Sequelize
) {}
}
在單元測(cè)試我們的應(yīng)用程序時(shí),我們通常希望避免任何數(shù)據(jù)庫(kù)連接,從而使我們的測(cè)試適合于獨(dú)立,并使它們的執(zhí)行過(guò)程盡可能快。但是我們的類可能依賴于從連接實(shí)例中提取的存儲(chǔ)庫(kù)。那是什么?解決方案是創(chuàng)建假模型。為了實(shí)現(xiàn)這一點(diǎn),我們?cè)O(shè)置了[自定義提供者]。事實(shí)上,每個(gè)注冊(cè)的模型都由 <ModelName>Model 令牌自動(dòng)表示,其中 ModelName 是模型類的名稱。
@nestjs/sequelize 包提供了基于給定模型返回準(zhǔn)備好 token 的 getModelToken() 函數(shù)。
@Module({
providers: [
UsersService,
{
provide: getModelToken(User),
useValue: mockModel,
},
],
})
export class UsersModule {}
現(xiàn)在, 將使用mockModel 作為 UsersModel。每當(dāng)任何提供程序使用 @InjectModel() 裝飾器請(qǐng)求 UserModel 時(shí), Nest 會(huì)使用注冊(cè)的 mockModel 對(duì)象。
通常,您可能希望異步傳遞SequelizeModule選項(xiàng),而不是事先靜態(tài)傳遞它們。在這種情況下,使用 forRootAsync() 函數(shù),提供了幾種處理異步數(shù)據(jù)的方法。
第一種可能的方法是使用工廠函數(shù):
SequelizeModule.forRootAsync({
useFactory: () => ({
dialect: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
models: [],
}),
});
我們的工廠的行為與任何其他異步提供者一樣(例如,它可以是異步的,并且它能夠通過(guò)inject注入依賴)。
SequelizeModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
dialect: 'mysql',
host: configService.get<string>('HOST'),
port: configService.get<string>('PORT'),
username: configService.get<string>('USERNAME'),
password: configService.get<string>('PASSWORD'),
database: configService.get<string>('DATABASE'),
models: [],
}),
inject: [ConfigService],
});
或者,您可以使用useClass語(yǔ)法。
SequelizeModule.forRootAsync({
useClass: SequelizeConfigService,
});
上面的構(gòu)造將 SequelizeConfigService 在SequelizeModule內(nèi)部進(jìn)行實(shí)例化 ,并通過(guò)調(diào)用createSequelizeOptions()來(lái)創(chuàng)建一個(gè)選項(xiàng)對(duì)象。注意,這意味著 SequelizeConfigService 必須實(shí)現(xiàn) SequelizeOptionsFactory 的接口。如下所示:
@Injectable()
class SequelizeConfigService implements SequelizeOptionsFactory {
createSequelizeOptions(): SequelizeModuleOptions {
return {
dialect: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
models: [],
};
}
}
為了防止在 SequelizeModule 中創(chuàng)建 SequelizeConfigService 并使用從不同模塊導(dǎo)入的提供程序,可以使用 useExisting 語(yǔ)法。
SequelizeModule.forRootAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});
這個(gè)構(gòu)造與 useClass 的工作原理相同,但有一個(gè)關(guān)鍵的區(qū)別 — SequelizeModule 將查找導(dǎo)入的模塊來(lái)重用現(xiàn)有的 ConfigService,而不是實(shí)例化一個(gè)新的 ConfigService。
更多建議: