TypeORM执行种子数据迁移时报错:relation "account" does not exist
数据库迁移执行种子数据时提示表不存在的问题排查
基本信息
- 报错内容:执行种子数据迁移脚本时抛出
QueryFailedError: relation "account" does not exist - 已知前置条件:
account、source表的创建逻辑均写在其他独立迁移文件中 - 运行环境版本:
- PostgreSQL 12.6
- Node 14.0.0
- TypeORM 0.2.32
- @nestjs/typeorm 7.1.5
关联代码
账户实体 account.entity.ts
import { BeforeInsert, Column, Entity, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../common/abstract.entity'; import { SourceEntity } from '../source/source.entity'; import { UtilsService } from '../../shared/services/utils.service'; @Entity({ name: 'account' }) export class AccountEntity extends AbstractEntity { @Column({ unique: true }) username: string; @Column({ nullable: true }) password: string; @OneToMany(() => SourceEntity, (source) => source.account, { cascade: true, }) sources: SourceEntity[]; @BeforeInsert() async setPassword() { this.password = UtilsService.generateHash(this.password); } }
资源实体 source.entity.ts
import { Column, Entity, ManyToOne } from 'typeorm'; import { AbstractEntity } from '../../common/abstract.entity'; import { AccountEntity } from '../account/account.entity'; @Entity({ name: 'source' }) export class SourceEntity extends AbstractEntity { @Column({ unique: true }) username: string; @Column({ default: true }) overrideCaption: boolean; @ManyToOne(() => AccountEntity, (account) => account.sources) account: AccountEntity; }
种子数据迁移文件 seed-data.migration.ts
import { getCustomRepository, MigrationInterface, QueryRunner } from 'typeorm'; import { AccountRepository } from '../modules/account/account.repository'; import { SourceRepository } from '../modules/source/source.repository'; type DataType = { username: string; password: string; sources: { username: string }[]; }; export class SeedData1617243952346 implements MigrationInterface { private data: DataType[] = [ { username: 'test', password: 'password', sources: [ { username: 'some_test' }, { username: 'okey_test' }, { username: 'super_test' }, ], }, { username: 'account', password: 'password', sources: [ { username: 'some_account' }, { username: 'okey_account' }, { username: 'super_account' }, ], }, ]; public async up(): Promise<void> { await Promise.all( this.data.map(async (item) => { const accountRepository = getCustomRepository(AccountRepository); const accountEntity = accountRepository.create(); accountEntity.username = item.username; accountEntity.password = item.password; const sourceRepository = getCustomRepository(SourceRepository); const sources = []; await Promise.all( item.sources.map(async (sourceItem) => { const sourceEntity = sourceRepository.create(); sourceEntity.username = sourceItem.username; sources.push(sourceEntity); }), ); accountEntity.sources = sources; const account = await accountRepository.save(accountEntity); console.log('Account created:', account.id); }), ); } public async down(): Promise<void> { await Promise.all( this.data.map(async (item) => { const sourceRepository = getCustomRepository(SourceRepository); const accountRepository = getCustomRepository(AccountRepository); const account = await accountRepository.findOne({ where: { username: item.username }, }); if (account) { await Promise.all( item.sources.map(async (src) => { const source = await sourceRepository.findOne({ where: { username: src.username }, }); if (source) { await sourceRepository.delete(source); } }), ); await accountRepository.delete(account); } }), ); } }
完整报错日志
Error during migration run: QueryFailedError: relation "account" does not exist at new QueryFailedError (/home/wiha/dev/own/instahub/src/error/QueryFailedError.ts:9:9) at PostgresQueryRunner.<anonymous> (/home/wiha/dev/own/instahub/src/driver/postgres/PostgresQueryRunner.ts:228:19) at step (/home/wiha/dev/own/instahub/node_modules/tslib/tslib.js:143:27) at Object.throw (/home/wiha/dev/own/instahub/node_modules/tslib/tslib.js:124:57) at rejected (/home/wiha/dev/own/instahub/node_modules/tslib/tslib.js:115:69) at processTicksAndRejections (internal/process/task_queues.js:97:5) { length: 106, severity: 'ERROR', code: '42P01', detail: undefined, hint: undefined, position: '13', internalPosition: undefined, internalQuery: undefined, where: undefined, schema: undefined, table: undefined, column: undefined, dataType: undefined, constraint: undefined, file: 'parse_relation.c', line: '1191', routine: 'parserOpenTable', query: 'INSERT INTO "account"("id", "created_at", "updated_at", "username", "password") VALUES (DEFAULT, DEFAULT, DEFAULT, $1, $2) RETURNING "id", "created_at", "updated_at"', parameters: [ 'test', '$2b$10$iB6yb3D8e6iGmKoVAJ7eYeYfoItclw5lcVXqauPf9VH94DlDrbuSa' ] }
问题定位与修复方案
问题根因:
- 迁移执行顺序错位:TypeORM 严格按照迁移类名中的时间戳从小到大执行脚本。如果种子迁移文件的时间戳早于建表迁移文件的时间戳,会出现尚未建表就尝试写入数据的逻辑错误。
- 迁移上下文不匹配:当前迁移代码中通过
getCustomRepository()获取的仓库实例来自全局数据库连接,不会绑定到当前迁移专用的QueryRunner会话,即便表结构已经创建,也可能因会话隔离、事务可见性问题出现表无法识别的报错。
修复步骤:
- 核对所有迁移文件的时间戳,保证所有建表迁移的执行时机早于种子数据迁移。
- 改写迁移方法,通过注入的
QueryRunner获取实体仓库,替换全局getCustomRepository调用,示例改写参考:
// 注意:up/down 方法需要接收 queryRunner 参数,不能使用无参写法 public async up(queryRunner: QueryRunner): Promise<void> { const accountRepository = queryRunner.manager.getRepository(AccountEntity); const sourceRepository = queryRunner.manager.getRepository(SourceEntity); // 原有业务逻辑代码不变,仅替换仓库实例的获取方式即可 }
内容的提问来源于stack exchange,提问作者Vania Kycher
相关产品推荐
相关产品推荐

