NestJS + TypeORM:如何使用两个或更多的数据库?

28

我已经尝试了两天来解决这个问题,也许我根本没有抓住重点。

我的目标是编写一个包含TypeORM的NestJS应用程序,为我小型项目中的2或3个提供RestAPI服务,而不是为每个项目编写一个NestJS应用程序。

到目前为止一切都很好,该应用程序已准备就绪,并与单个项目(这些项目位于子文件夹中,拥有其实体、控制器、服务和模块)良好配合,但我无法使它在所有项目中运行。

关键似乎是配置,我正在使用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] 因为没有在任何orm配置文件中定义,所以无法找到连接“default”

当然,“default”无法找到,因为我提供了两个具有唯一名称的配置,不同于“default”。

ApplicationModule中,我可以像这样提供连接的名称:

TypeOrmModule.forRoot( { name: "project1" } ),

但这样只适用于一个项目。

我可以将所有内容混合在一个配置中,但那么我就必须将所有内容放在一个数据库中,同一个用户对所有内容进行访问,可能会混淆实体......

有人能给我一些提示如何解决吗? 也许可以在每个模块中使用getConnection(<name>),但如何启动ApplicationModule呢?

此致
敬礼
sagerobert

7个回答

29

我刚试着用TypeORM设置多个数据库和一个ormconfig.json,但是没有成功。它似乎总是使用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' },
  })
]

1
你好,你是怎么运行迁移的呢? - diversemix
按照您所说的定义,我没有收到任何错误。但程序总是使用默认数据库运行。有什么其他需要检查的地方吗? - Soraya Anvari
4
当你使用 forFeature 导入时,你是否也传递了连接的 name: TypeOrmModule.forFeature([Album], 'albumsConnection')? - Kim Kern
我在app.module中编写了TypeOrmModule.forRoot,同时加入了TypeOrmModule.forFeature(Entities, 'connectionname')到每个分离的模块中。我的程序使用相同的主机但不同的数据库。我创建了一个模块来处理每个数据库请求。我不知道还缺少什么。 - Soraya Anvari
@SorayaAnvari,你解决这个问题了吗?我也遇到了类似的情况。谢谢! - ddsultan
显示剩余2条评论

15

如果您正在使用多个数据库连接,请在TypeOrmModule.forRoot({ name: 'db1Connection' })内部以相同的级别明确传递连接名称。

TypeOrmModule.forRootAsync({
  name: DB1_CONNECTION,
  imports: [ConfigModule],
  useClass: TypeormDb1ConfigService,
}),

TypeOrmModule.forRootAsync({
  name: DB2_CONNECTION,
  imports: [ConfigModule],
  useClass: TypeormDb2ConfigService,
})

2
您刚刚救了我的一天,先生。TypeOrmModule.forRootAsync拥有我曾经见过的最糟糕的API接口。 - Pubudu Dodangoda
@PubuduDodangoda,我认为问题不在于API,而是文档中只有一小句话提到了这一点,当我读Frozenex的回复时才恍然大悟。虽然他们这样做是有道理的,但文档中应该更清晰地说明。 - Allan Lima

9

为了更加清晰,让其他开发者也能够理解这篇文章:

根据 NestJS 文档

如果你没有为连接设置名称,则它的名称将设置为 default。请注意,您不应该有多个没有名称或具有相同名称的连接,否则它们将被覆盖。

你的连接之一必须具备以下之一:

  1. "name":"default"
  2. 没有任何名称。

我建议在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: [...]
})

在您的模块中(不必是根模块,仅包括您需要连接的模块)。

8
对于那些遇到这个问题的人,这是我的解决方案。
AppModule
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [
        database,
        databaseAllo #<= my second database
      ]
    }),
    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 {}

我的项目模块(包含第二个数据库中的表)


@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)
  }

}

3
这是我解决问题的方法。通过一个配置文件,我可以在应用程序启动时或使用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

0

@DamarOwen的回答对我有用,尽管我在另一个问题上绊了一下:如果你想将连接名称存储为变量,请不要从app.module.ts导出该常量。请将变量存储在另一个文件中,如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>,
  ) {}
}

出现了错误,找不到名为MyOrmEntity的存储库。看起来这个实体没有在当前的“默认”连接中注册。(注意:我的应用程序有另一个名为“default”的连接)。

我不得不将export const DB_CONNECTION_1 = 'conn1';app.module.ts移动到它自己的文件constants.ts。然后它就可以工作了。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接