NestJS + TypeORM:使用两个或多个数据库?
NestJS + TypeORM: Use two or more databases?
我已经尝试了 2 天来解决这个问题,也许我只是忽略了这里的重点。
我的目标是编写一个 NestJS 应用程序(包含 TypeORM),它为我的 2 个或 3 个小项目提供 RestAPI,而不是为每个项目编写一个 NestJS-App。
到目前为止一切顺利,应用程序已准备就绪,可以很好地与单个项目(位于具有实体、控制器、服务、模块的子文件夹中)配合使用,但我无法将其下载到 运行与他们所有人。
重点好像是配置,我用的是ormconfig.json
:
[ {
"name": "Project1",
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "<username>",
"password": "<pwd>",
"database": "<database>",
"synchronize": false,
"entities": ["project1/*.entity.ts"],
"subscribers": ["project1/*.subscriber.ts"],
"migrations": ["project1/migrations/*.ts"],
"cli": { "migrationsDir": "project1/migrations" }
}, {
"name": "project2",
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "<another-username>",
"password": "<another-pwd>",
"database": "<another-database>",
"synchronize": false,
"entities": ["project2/*.entity.ts"],
"subscribers": ["project2/*.subscriber.ts"],
"migrations": ["project2/migrations/*.ts"],
"cli": { "migrationsDir": "project2/migrations"
} ]
错误消息说:
[ExceptionHandler] Cannot find connection default because its not defined in any orm configuration files
当然找不到 "default",因为我提供了两个配置,其名称与 "default" 不同。
在 ApplicationModule 中,我可以提供连接的名称,如下所示:
TypeOrmModule.forRoot( { name: "project1" } ),
但它只能用于一个项目。
我可以将所有内容混合在一个配置中,但这样我就会将所有内容都放在一个数据库中,所有用户都使用同一个用户,并且可能会混淆实体...
谁能告诉我如何解决这个问题?
也许在每个模块中都有 getConnection(<name>)
,但是如何启动 ApplicationModule 呢?
亲切的问候,
萨格罗伯特
我刚刚尝试使用多个数据库和一个 ormconfig.json
设置 TypeORM,但它对我根本不起作用。它似乎总是使用 default
连接,当没有发现默认(= 没有显式名称)连接时,它会抛出相应的错误。
当我在 app.module.ts
中定义连接时它确实有效(我删除了 ormconfig.json
):
imports: [
...,
TypeOrmModule.forRoot({
name: 'Project1',
type: 'mysql',
host: 'localhost',
port: 3306,
username: '<username>',
password: '<pwd>',
database: '<database>',
synchronize: false,
entities: ['project1/*.entity.ts'],
subscribers: ['project1/*.subscriber.ts'],
migrations: ['project1/migrations/*.ts'],
cli: { migrationsDir: 'project1/migrations' },
}),
TypeOrmModule.forRoot({
name: 'project2',
type: 'mysql',
host: 'localhost',
port: 3306,
username: '<another-username>',
password: '<another-pwd>',
database: '<another-database>',
synchronize: false,
entities: ['project2/*.entity.ts'],
subscribers: ['project2/*.subscriber.ts'],
migrations: ['project2/migrations/*.ts'],
cli: { migrationsDir: 'project2/migrations' },
})
]
您需要在 TypeOrmModule.forRoot({ name: 'db1Connection' }) 中显式传递同一级别的连接名称,以防您使用多个数据库连接。
TypeOrmModule.forRootAsync({
name: DB1_CONNECTION,
imports: [ConfigModule],
useClass: TypeormDb1ConfigService,
}),
TypeOrmModule.forRootAsync({
name: DB2_CONNECTION,
imports: [ConfigModule],
useClass: TypeormDb2ConfigService,
})
为清楚起见并让其他开发人员参与此 post:
If you don't set any name for a connection, its name is set to default. Please note that you shouldn't have multiple connections without a name, or with the same name, otherwise they simply get overridden.
您的一个连接必须具有以下之一:
"name":"default"
- 没有任何名字。
我建议在 ormconfig.json
中声明所有连接,而不是在代码中声明。
从 ormconfig.json
导入连接的示例:
@Module({
imports: [TypeOrmModule.forFeature([Entity1, Entity2]), //This will use default connection
TypeOrmModule.forRoot({name: 'con1'}), // This will register globaly con1
TypeOrmModule.forRoot({name: 'con2'}), // This will register globaly con2
controllers: [...],
providers: [...],
exports: [...]
})
在你的模块中(不一定是根模块,只有你需要连接的模块)。
这就是我设法修复它的方法。使用单个配置文件,我可以 运行 在应用程序 boostrap 上或使用 TypeOrm 的 CLI 进行迁移。
src/config/ormconfig.ts
import parseBoolean from '@eturino/ts-parse-boolean';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import * as dotenv from 'dotenv';
import { join } from 'path';
dotenv.config();
export = [
{
//name: 'default',
type: 'mssql',
host: process.env.DEFAULT_DB_HOST,
username: process.env.DEFAULT_DB_USERNAME,
password: process.env.DEFAULT_DB_PASSWORD,
database: process.env.DEFAULT_DB_NAME,
options: {
instanceName: process.env.DEFAULT_DB_INSTANCE,
enableArithAbort: false,
},
logging: parseBoolean(process.env.DEFAULT_DB_LOGGING),
dropSchema: false,
synchronize: false,
migrationsRun: parseBoolean(process.env.DEFAULT_DB_RUN_MIGRATIONS),
migrations: [join(__dirname, '..', 'model/migration/*.{ts,js}')],
cli: {
migrationsDir: 'src/model/migration',
},
entities: [
join(__dirname, '..', 'model/entity/default/**/*.entity.{ts,js}'),
],
} as TypeOrmModuleOptions,
{
name: 'other',
type: 'mssql',
host: process.env.OTHER_DB_HOST,
username: process.env.OTHER_DB_USERNAME,
password: process.env.OTHER_DB_PASSWORD,
database: process.env.OTHER_DB_NAME,
options: {
instanceName: process.env.OTHER_DB_INSTANCE,
enableArithAbort: false,
},
logging: parseBoolean(process.env.OTHER_DB_LOGGING),
dropSchema: false,
synchronize: false,
migrationsRun: false,
entities: [],
} as TypeOrmModuleOptions,
];
src/app.module.ts
import configuration from '@config/configuration';
import validationSchema from '@config/validation';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoggerService } from '@shared/logger/logger.service';
import { UsersModule } from '@user/user.module';
import { AppController } from './app.controller';
import ormconfig = require('./config/ormconfig'); //path mapping doesn't work here
@Module({
imports: [
ConfigModule.forRoot({
cache: true,
isGlobal: true,
validationSchema: validationSchema,
load: [configuration],
}),
TypeOrmModule.forRoot(ormconfig[0]), //default
TypeOrmModule.forRoot(ormconfig[1]), //other db
LoggerService,
UsersModule,
],
controllers: [AppController],
})
export class AppModule {}
package.json
"scripts": {
...
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config ./src/config/ormconfig.ts",
"typeorm:migration:generate": "npm run typeorm -- migration:generate -n",
"typeorm:migration:run": "npm run typeorm -- migration:run"
},
项目结构
src/
├── app.controller.ts
├── app.module.ts
├── config
│ ├── configuration.ts
│ ├── ormconfig.ts
│ └── validation.ts
├── main.ts
├── model
│ ├── entity
│ ├── migration
│ └── repository
├── route
│ └── user
└── shared
└── logger
对于遇到这个问题的人,这是我的解决方案
应用模块
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [
database,
databaseAllo
]
}),
TypeOrmModule.forRootAsync({
useFactory: (configs: ConfigService) => configs.get("db_config"),
inject: [ConfigService],
}),
TypeOrmModule.forRootAsync({
name:"db_allo", <= create connection to my second db
useFactory: (configs: ConfigService) => configs.get("db_config_allo"),
inject: [ConfigService],
}),
AuthModule,
JwtAuthModule
],
controllers: []
})
export class AppModule {}
我的项目模块(包含来自第二个数据库的 table)
@Module({
imports: [
TypeOrmModule.forFeature([AlloMpcTable], "db_allo" <= call connection again),
],
providers: [
AlloRepository
],
exports: [AlloRepository],
controllers: [],
})
export class AlloModule {}
我的项目库
@Injectable()
export class AlloRepository extends BaseRepository<AlloMpcTable> {
constructor(
@InjectRepository(AlloMpcTable, "db_allo") <= you need to call connection again
private readonly allo: Repository<AlloMpcTable>,
) {
super(allo)
}
public async Find(id: number): Promise<AlloMpcTable> {
return await this.allo.findOne(id)
}
}
@DamarOwen 的回答对我有用,在我偶然发现另一个陷阱后:如果你想将连接名称存储为变量,不要从 app.module.ts
[=39 中导出该常量=].将变量存储在另一个文件中,例如 constants.ts
.
这是我试过的:
app.module.ts
export const DB_CONNECTION_1 = 'conn1';
@Module({
imports: [
TypeOrmModule.forRootAsync({
name: DB_CONNECTION_1,
...
})
],
...
)
export class AppModule {}
database.module.ts
@Module({
imports: [
TypeOrmModule.forFeature(
[MyRepo],
DB_CONNECTION_1,
),
],
providers: [MyRepo],
})
my.repo.ts
@Injectable()
export class MyRepo {
constructor(
@InjectRepository(MyOrmEntity, DB_CONNECTION_1)
private readonly repo: Repository<MyOrmEntity>,
) {}
}
这引发了错误 No repository found for
MyOrmEntity was found. Looks like this entity is not registered in current "default" connection.
(注意:我的应用程序有另一个名为“default”的连接)。
我不得不将 export const DB_CONNECTION_1 = 'conn1';
从 app.module.ts
移到它自己的文件 constants.ts
中。然后就成功了。
我已经尝试了 2 天来解决这个问题,也许我只是忽略了这里的重点。
我的目标是编写一个 NestJS 应用程序(包含 TypeORM),它为我的 2 个或 3 个小项目提供 RestAPI,而不是为每个项目编写一个 NestJS-App。
到目前为止一切顺利,应用程序已准备就绪,可以很好地与单个项目(位于具有实体、控制器、服务、模块的子文件夹中)配合使用,但我无法将其下载到 运行与他们所有人。
重点好像是配置,我用的是ormconfig.json
:
[ {
"name": "Project1",
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "<username>",
"password": "<pwd>",
"database": "<database>",
"synchronize": false,
"entities": ["project1/*.entity.ts"],
"subscribers": ["project1/*.subscriber.ts"],
"migrations": ["project1/migrations/*.ts"],
"cli": { "migrationsDir": "project1/migrations" }
}, {
"name": "project2",
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "<another-username>",
"password": "<another-pwd>",
"database": "<another-database>",
"synchronize": false,
"entities": ["project2/*.entity.ts"],
"subscribers": ["project2/*.subscriber.ts"],
"migrations": ["project2/migrations/*.ts"],
"cli": { "migrationsDir": "project2/migrations"
} ]
错误消息说:
[ExceptionHandler] Cannot find connection default because its not defined in any orm configuration files
当然找不到 "default",因为我提供了两个配置,其名称与 "default" 不同。
在 ApplicationModule 中,我可以提供连接的名称,如下所示:
TypeOrmModule.forRoot( { name: "project1" } ),
但它只能用于一个项目。
我可以将所有内容混合在一个配置中,但这样我就会将所有内容都放在一个数据库中,所有用户都使用同一个用户,并且可能会混淆实体...
谁能告诉我如何解决这个问题?
也许在每个模块中都有 getConnection(<name>)
,但是如何启动 ApplicationModule 呢?
亲切的问候,
萨格罗伯特
我刚刚尝试使用多个数据库和一个 ormconfig.json
设置 TypeORM,但它对我根本不起作用。它似乎总是使用 default
连接,当没有发现默认(= 没有显式名称)连接时,它会抛出相应的错误。
当我在 app.module.ts
中定义连接时它确实有效(我删除了 ormconfig.json
):
imports: [
...,
TypeOrmModule.forRoot({
name: 'Project1',
type: 'mysql',
host: 'localhost',
port: 3306,
username: '<username>',
password: '<pwd>',
database: '<database>',
synchronize: false,
entities: ['project1/*.entity.ts'],
subscribers: ['project1/*.subscriber.ts'],
migrations: ['project1/migrations/*.ts'],
cli: { migrationsDir: 'project1/migrations' },
}),
TypeOrmModule.forRoot({
name: 'project2',
type: 'mysql',
host: 'localhost',
port: 3306,
username: '<another-username>',
password: '<another-pwd>',
database: '<another-database>',
synchronize: false,
entities: ['project2/*.entity.ts'],
subscribers: ['project2/*.subscriber.ts'],
migrations: ['project2/migrations/*.ts'],
cli: { migrationsDir: 'project2/migrations' },
})
]
您需要在 TypeOrmModule.forRoot({ name: 'db1Connection' }) 中显式传递同一级别的连接名称,以防您使用多个数据库连接。
TypeOrmModule.forRootAsync({
name: DB1_CONNECTION,
imports: [ConfigModule],
useClass: TypeormDb1ConfigService,
}),
TypeOrmModule.forRootAsync({
name: DB2_CONNECTION,
imports: [ConfigModule],
useClass: TypeormDb2ConfigService,
})
为清楚起见并让其他开发人员参与此 post:
If you don't set any name for a connection, its name is set to default. Please note that you shouldn't have multiple connections without a name, or with the same name, otherwise they simply get overridden.
您的一个连接必须具有以下之一:
"name":"default"
- 没有任何名字。
我建议在 ormconfig.json
中声明所有连接,而不是在代码中声明。
从 ormconfig.json
导入连接的示例:
@Module({
imports: [TypeOrmModule.forFeature([Entity1, Entity2]), //This will use default connection
TypeOrmModule.forRoot({name: 'con1'}), // This will register globaly con1
TypeOrmModule.forRoot({name: 'con2'}), // This will register globaly con2
controllers: [...],
providers: [...],
exports: [...]
})
在你的模块中(不一定是根模块,只有你需要连接的模块)。
这就是我设法修复它的方法。使用单个配置文件,我可以 运行 在应用程序 boostrap 上或使用 TypeOrm 的 CLI 进行迁移。
src/config/ormconfig.ts
import parseBoolean from '@eturino/ts-parse-boolean';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import * as dotenv from 'dotenv';
import { join } from 'path';
dotenv.config();
export = [
{
//name: 'default',
type: 'mssql',
host: process.env.DEFAULT_DB_HOST,
username: process.env.DEFAULT_DB_USERNAME,
password: process.env.DEFAULT_DB_PASSWORD,
database: process.env.DEFAULT_DB_NAME,
options: {
instanceName: process.env.DEFAULT_DB_INSTANCE,
enableArithAbort: false,
},
logging: parseBoolean(process.env.DEFAULT_DB_LOGGING),
dropSchema: false,
synchronize: false,
migrationsRun: parseBoolean(process.env.DEFAULT_DB_RUN_MIGRATIONS),
migrations: [join(__dirname, '..', 'model/migration/*.{ts,js}')],
cli: {
migrationsDir: 'src/model/migration',
},
entities: [
join(__dirname, '..', 'model/entity/default/**/*.entity.{ts,js}'),
],
} as TypeOrmModuleOptions,
{
name: 'other',
type: 'mssql',
host: process.env.OTHER_DB_HOST,
username: process.env.OTHER_DB_USERNAME,
password: process.env.OTHER_DB_PASSWORD,
database: process.env.OTHER_DB_NAME,
options: {
instanceName: process.env.OTHER_DB_INSTANCE,
enableArithAbort: false,
},
logging: parseBoolean(process.env.OTHER_DB_LOGGING),
dropSchema: false,
synchronize: false,
migrationsRun: false,
entities: [],
} as TypeOrmModuleOptions,
];
src/app.module.ts
import configuration from '@config/configuration';
import validationSchema from '@config/validation';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { LoggerService } from '@shared/logger/logger.service';
import { UsersModule } from '@user/user.module';
import { AppController } from './app.controller';
import ormconfig = require('./config/ormconfig'); //path mapping doesn't work here
@Module({
imports: [
ConfigModule.forRoot({
cache: true,
isGlobal: true,
validationSchema: validationSchema,
load: [configuration],
}),
TypeOrmModule.forRoot(ormconfig[0]), //default
TypeOrmModule.forRoot(ormconfig[1]), //other db
LoggerService,
UsersModule,
],
controllers: [AppController],
})
export class AppModule {}
package.json
"scripts": {
...
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config ./src/config/ormconfig.ts",
"typeorm:migration:generate": "npm run typeorm -- migration:generate -n",
"typeorm:migration:run": "npm run typeorm -- migration:run"
},
项目结构
src/
├── app.controller.ts
├── app.module.ts
├── config
│ ├── configuration.ts
│ ├── ormconfig.ts
│ └── validation.ts
├── main.ts
├── model
│ ├── entity
│ ├── migration
│ └── repository
├── route
│ └── user
└── shared
└── logger
对于遇到这个问题的人,这是我的解决方案
应用模块
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [
database,
databaseAllo
]
}),
TypeOrmModule.forRootAsync({
useFactory: (configs: ConfigService) => configs.get("db_config"),
inject: [ConfigService],
}),
TypeOrmModule.forRootAsync({
name:"db_allo", <= create connection to my second db
useFactory: (configs: ConfigService) => configs.get("db_config_allo"),
inject: [ConfigService],
}),
AuthModule,
JwtAuthModule
],
controllers: []
})
export class AppModule {}
我的项目模块(包含来自第二个数据库的 table)
@Module({
imports: [
TypeOrmModule.forFeature([AlloMpcTable], "db_allo" <= call connection again),
],
providers: [
AlloRepository
],
exports: [AlloRepository],
controllers: [],
})
export class AlloModule {}
我的项目库
@Injectable()
export class AlloRepository extends BaseRepository<AlloMpcTable> {
constructor(
@InjectRepository(AlloMpcTable, "db_allo") <= you need to call connection again
private readonly allo: Repository<AlloMpcTable>,
) {
super(allo)
}
public async Find(id: number): Promise<AlloMpcTable> {
return await this.allo.findOne(id)
}
}
@DamarOwen 的回答对我有用,在我偶然发现另一个陷阱后:如果你想将连接名称存储为变量,不要从 app.module.ts
[=39 中导出该常量=].将变量存储在另一个文件中,例如 constants.ts
.
这是我试过的:
app.module.ts
export const DB_CONNECTION_1 = 'conn1';
@Module({
imports: [
TypeOrmModule.forRootAsync({
name: DB_CONNECTION_1,
...
})
],
...
)
export class AppModule {}
database.module.ts
@Module({
imports: [
TypeOrmModule.forFeature(
[MyRepo],
DB_CONNECTION_1,
),
],
providers: [MyRepo],
})
my.repo.ts
@Injectable()
export class MyRepo {
constructor(
@InjectRepository(MyOrmEntity, DB_CONNECTION_1)
private readonly repo: Repository<MyOrmEntity>,
) {}
}
这引发了错误 No repository found for
MyOrmEntity was found. Looks like this entity is not registered in current "default" connection.
(注意:我的应用程序有另一个名为“default”的连接)。
我不得不将 export const DB_CONNECTION_1 = 'conn1';
从 app.module.ts
移到它自己的文件 constants.ts
中。然后就成功了。