NestJS+TypeORM本地正常生产环境报错:循环依赖问题求助
NestJS + Postgres生产环境循环依赖错误排查
问题描述
服务基于NestJS和Postgres构建,本地运行正常,但生产服务器连接生产Postgres时始终抛出以下错误:
[Nest] 20 - 09/05/2023, 8:43:09 PM ERROR [ExceptionsHandler] Cyclic dependency: "d" TypeORMError: Cyclic dependency: "d" at visit (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:147:23) at visit (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:164:21) at SubjectTopoligicalSorter.toposort (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:143:17) at SubjectTopoligicalSorter.sort (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:53:45) at SubjectExecutor.execute (/app/node_modules/typeorm/persistence/SubjectExecutor.js:91:108) at EntityPersistExecutor.execute (/app/node_modules/typeorm/persistence/EntityPersistExecutor.js:140:36) at processTicksAndRejections (node:internal/process/task_queues:96:5)
环境差异:
- 本地开发服务器连接远程生产Postgres:运行正常
- 生产服务器连接生产Postgres:触发上述错误
技术栈信息
Node: 16.19.1 Package Manager: npm 8.19.3 OS: darwin arm64
生产依赖
"dependencies": { "@angular/animations": "^13.0.0", "@angular/cdk": "^13.0.0", "@angular/common": "^13.0.0", "@angular/compiler": "^13.0.0", "@angular/core": "^13.0.0", "@angular/forms": "^13.0.0", "@angular/platform-browser": "^13.0.0", "@angular/platform-browser-dynamic": "^13.0.0", "@angular/router": "^13.0.0", "@nestjs/common": "^8.0.0", "@nestjs/config": "^3.0.0", "@nestjs/core": "^8.0.0", "@nestjs/jwt": "^10.0.3", "@nestjs/mapped-types": "^2.0.2", "@nestjs/platform-express": "^8.0.0", "@nestjs/serve-static": "^4.0.0", "@nestjs/typeorm": "^10.0.0", "@nrwl/angular": "13.3.1", "bootstrap": "^5.2.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cloudinary": "^1.37.3", "express": "^4.18.2", "http-status-codes": "^2.2.0", "multer": "^1.4.5-lts.1", "pg": "^8.11.1", "primeflex": "^3.3.0", "primeicons": "^4.1.0", "primeng": "^13.4.1", "reflect-metadata": "^0.1.13", "rxjs": "~7.4.0", "sqlite3": "^5.1.6", "tslib": "^2.0.0", "typeorm": "^0.3.17", "uuid": "^9.0.0", "zone.js": "~0.11.4" }
开发依赖
"devDependencies": { "@angular-devkit/build-angular": "~13.0.0", "@angular-eslint/eslint-plugin": "~13.0.1", "@angular-eslint/eslint-plugin-template": "~13.0.1", "@angular-eslint/template-parser": "~13.0.1", "@angular/cli": "^13.3.4", "@angular/compiler-cli": "^13.0.0", "@angular/language-service": "^13.0.0", "@nestjs/schematics": "^8.0.0", "@nestjs/testing": "^8.0.0", "@nrwl/cli": "13.3.1", "@nrwl/cypress": "13.3.1", "@nrwl/eslint-plugin-nx": "13.3.1", "@nrwl/jest": "13.3.1", "@nrwl/linter": "13.3.1", "@nrwl/nest": "13.3.1", "@nrwl/node": "13.3.1", "@nrwl/tao": "13.3.1", "@nrwl/workspace": "13.3.1", "@types/express": "^4.17.17", "@types/jest": "27.0.2", "@types/multer": "^1.4.7", "@types/node": "^15.14.0", "@typescript-eslint/eslint-plugin": "~5.3.0", "@typescript-eslint/parser": "~5.3.0", "cypress": "^8.3.0", "eslint": "8.2.0", "eslint-config-prettier": "8.1.0", "eslint-plugin-cypress": "^2.10.3", "jest": "27.2.3", "jest-preset-angular": "11.0.0", "prettier": "^2.3.1", "ts-jest": "27.0.5", "typescript": "~4.4.3" }
实体定义
PatientEntity
@Entity() export class PatientEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column() name: string; @Column({ type: 'date' }) dateOfBirth: Date; @Column({ unique: true }) email: string; @Column({ nullable: true }) phoneNumber: string; @Column({ nullable: true }) photo: string | null; @OneToMany( () => MedicalPlanEntity, plan => plan.patient, { cascade: true } ) plans: MedicalPlanEntity[]; }
MedicalPlanEntity
@Entity() export class MedicalPlanEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column({ nullable: true }) name: string; @Column({ nullable: true }) description: string; @ManyToOne( () => PatientEntity, patient => patient.plans, { nullable: true } ) patient: PatientEntity; }
ORM配置
import { registerAs } from '@nestjs/config'; import { TypeOrmModuleOptions } from '@nestjs/typeorm'; export default registerAs( 'orm.config', (): TypeOrmModuleOptions => ({ ssl: true, extra: { ssl: { rejectUnauthorized: false } }, type: 'postgres', autoLoadEntities: true, host: process.env.DB_HOST, port: Number(process.env.DB_PORT), username: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, }) );
原因分析与解决方案
核心原因
- 生产数据存在异常关联:生产库中可能存在
MedicalPlan的patient_id为null,或指向不存在/异常的Patient数据,导致TypeORM在处理关联时形成无效循环引用链。本地开发数据量小或无此类脏数据,因此未触发问题。 - TypeORM拓扑排序bug:当前使用的
typeorm@0.3.17在特定环境(如Linux生产服务器)下,拓扑排序逻辑存在缺陷,误判正常关联为循环依赖。 - Cascade配置过度:
PatientEntity的cascade: true会触发全量级联操作,生产环境处理批量数据时,递归加载关联实体容易触发循环检测。
解决方案
清理生产脏数据:执行SQL排查并修复无效关联:
-- 找出无关联或关联无效的MedicalPlan数据 SELECT * FROM medical_plan WHERE patient_id IS NULL OR patient_id NOT IN (SELECT id FROM patient); -- 删除或修正这些数据 DELETE FROM medical_plan WHERE patient_id IS NULL;调整Cascade策略:将全量级联改为指定操作,避免不必要的递归加载:
@OneToMany( () => MedicalPlanEntity, plan => plan.patient, { cascade: ['insert', 'update'] } // 仅允许插入和更新级联 ) plans: MedicalPlanEntity[];升级TypeORM版本:将
typeorm升级到0.3.x最新稳定版(如0.3.19+),修复已知的拓扑排序bug,同时确保@nestjs/typeorm版本兼容。手动指定实体列表:禁用
autoLoadEntities,改为手动导入实体,避免自动加载时的实体顺序问题:// orm.config.ts import { PatientEntity, MedicalPlanEntity } from './entities'; // 替换为你的实体路径 export default registerAs( 'orm.config', (): TypeOrmModuleOptions => ({ // ...其他配置 entities: [PatientEntity, MedicalPlanEntity], // autoLoadEntities: true, // 注释掉该行 }) );
内容的提问来源于stack exchange,提问作者Lorraine Ram-El
相关产品推荐
相关产品推荐

