ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MikroORM 实体关系建模详解:四种关系类型、双向自动接线与外键约束定制(v6.6)

MikroORM 实体关系建模详解:四种关系类型、双向自动接线与外键约束定制(v6.6) 后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载本文基于 MikroORM v6.6 官方文档 Modeling Entity Relationships系统讲解 MikroORM 中 ManyToOne、OneToMany、OneToOne、ManyToMany 四种实体关系的定义方式、单向/双向关系的所有权规则以及外键约束的名称定制与创建控制并结合源码说明装饰器元数据写入、mappedBy自动接线与元数据校验的底层机制帮助你在建模阶段一次性写对关系映射。一、关系类型总览与所有权规则MikroORM 支持四种实体关系源码中以ReferenceKind枚举区分见 MetadataDiscovery.tsManyToOne多对一当前实体的多个实例指向被引用实体的一个实例OneToMany一对多当前实体的一个实例关联被引用实体的多个实例OneToOne一对一两侧都只有一个实例外键列带唯一约束ManyToMany多对多两侧均为多个实例通常经由 pivot中间表实现。关系可以是单向的也可以是双向的单向关系只在**拥有方owning side**一侧定义外键或 pivot 表就落在这一侧双向关系在两侧都定义拥有方使用inversedBy指向对方的反向属性反向方使用mappedBy指回拥有方。文档原文明确指出建模双向关系时inversedBy属性可以省略——只要在反向方定义了mappedByMikroORM 会自动接线auto-wire。这一点在源码中有直接对应。元数据发现阶段会遍历所有非拥有方且带mappedBy的关系若对方属性还没有inversedBy则自动补上// packages/core/src/metadata/MetadataDiscovery.tsL1377-L1388 private autoWireBidirectionalProperties(meta: EntityMetadata): void { Object.values(meta.properties) .filter(prop prop.kind ! ReferenceKind.SCALAR !prop.owner prop.mappedBy) .forEach(prop { const meta2 prop.targetMeta!; const prop2 meta2.properties[prop.mappedBy]; if (prop2 !prop2.inversedBy) { prop2.inversedBy prop.name; } }); }同时启动时的 MetadataValidator 会对双向关系做强制校验L639-L724拥有方必须带inversedBy或owner反向方必须带mappedBy且指向一个真实存在的拥有方属性并且两侧关系类型必须匹配ManyToOne↔OneToMany、ManyToMany↔ManyToMany 等否则抛出MetadataError。例如OneToMany缺少mappedBy会直接报fromMissingOption反向方不能再次声明为拥有方fromWrongOwnership。二、ManyToOne拥有方的多种等价写法语义当前实体的多个实例指向被引用实体的一个实例。文档给出四种完全等价的定义方式Entity() export class Book { ManyToOne() // 纯装饰器即可目标类型通过反射自动嗅探 author1!: Author; ManyToOne(() Author) // 以回调形式手动指定类型 author2!: Author; ManyToOne(Author) // 或以实体名字符串指定 author3!: Author; ManyToOne({ entity: () Author }) // 或使用选项对象 author4!: Author; }从 legacy 版 ManyToOne 装饰器 的源码可以看到这些写法最终都汇入同一实现entity函数、字符串或选项对象与options会经processDecoratorParameters归一化为ManyToOneOptions然后与属性名、关系类型一起写入实体元数据const property { name: propertyName, kind: ReferenceKind.MANY_TO_ONE } as EntityProperty; meta.properties[propertyName as EntityKeyOwner] Object.assign( meta.properties[propertyName as EntityKeyOwner] ?? {}, property, options, );选项类型定义在 types.tsManyToOneOptions它继承自ReferenceOptions因此在关系上可直接使用以下通用选项见 types.ts L445-L460选项说明cascade操作如何级联到被引用实体默认值为[Cascade.PERSIST, Cascade.MERGE]详见 级联文档eager是否总是加载该关系to-many 关系出于性能原因不建议开启strategy覆盖该属性的默认加载策略优先级高于全局loadStrategy但可被FindOptions.strategy覆盖filters控制该关系上的查询过滤器参数可作为默认值inversedBy指向反向方属性名双向关系时此外ManyToOneOptions还支持through/where/orderBy通过 pivot 实体解析的只读关系、deleteRule/updateRule/deferMode外键级联规则与约束模式、foreignKeyName与createForeignKeyConstraint见第六节。TC39 标准装饰器版本的 es/ManyToOne.ts 参数签名与 legacy 版一致供启用实验性装饰器的项目使用。三、OneToMany反向方与孤儿移除语义当前实体的一个实例关联被引用实体的多个实例。OneToMany是ManyToOne的反向方后者才是拥有方。四种等价写法Entity() export class Author { OneToMany(() Book, book book.author) books1 new CollectionBook(this); OneToMany(Book, author) books2 new CollectionBook(this); OneToMany({ mappedBy: book book.author }) // 被引用实体类型也可自动嗅探 books3 new CollectionBook(this); OneToMany({ entity: () Book, mappedBy: author, orphanRemoval: true }) books4 new CollectionBook(this); }注意反向方必须持有Collection实例集合的工作机制详见 collections 文档且必须提供mappedBy字符串或属性访问器函数均可——OneToManyOptions中mappedBy是唯一必填项见 types.ts L624-L625这与MetadataValidator.validateBidirectional中1:m 属性缺少mappedBy即报错的规则完全一致。OneToManyOptionstypes.ts L596-L626在ReferenceOptions之上还增加了orphanRemoval: boolean——比级联删除更激进的移除模式当目标实体从关系中脱离变为孤儿时直接将其删除即文档中books4示例的能力详见 级联文档的 Orphan Removal 章节orderBy/where——集合的默认排序与声明式部分加载条件joinColumn(s)/inverseJoinColumn(s)/referenceColumnName(s)——在需要时覆盖拥有方列名、目标列名适合复合键场景。legacy 版 OneToMany 装饰器 的第二参数即mappedBy字符串或(e: Target) any访问器实现上同样归一化后写入元数据kind记为ReferenceKind.ONE_TO_MANY。四、OneToOne拥有方、反向方与 owner 标记语义当前实体的一个实例指向被引用实体的一个实例。OneToOne 是 ManyToOne 的特例两侧都只有一个实体因此外键列同时带唯一约束。关系也可以是自引用的。拥有方Entity() export class User { // 当 owner/inversedBy/mappedBy 都未提供时默认视为拥有方 OneToOne() bestFriend1!: User; // 带 inversedBy 的一侧即拥有方反向方用 mappedBy 定义 OneToOne({ inversedBy: bestFriend1 }) bestFriend2!: User; // 这样定义时必须用 owner: true 明确标记拥有方 OneToOne(() User, user user.bestFriend2, { owner: true }) bestFriend3!: User; }反向方Entity() export class User { OneToOne({ mappedBy: bestFriend1, orphanRemoval: true }) bestFriend1!: User; OneToOne(() User, user user.bestFriend2, { orphanRemoval: true }) bestFriend2!: User; }源码证实了OneToOne 也支持孤儿移除这一点OneToOneOptions继承自PartialOmitOneToManyOptions, orderBy | wheretypes.ts L628-L629因此天然带有orphanRemoval选项而owner选项的文档注释明确写道当你使用inversedBy或mappedBy区分两侧时本选项非必需。在 EntitySchema.addOneToOne 中可以看到默认值推导逻辑未显式提供owner时owner默认等于!!prop.inversedBy || !prop.mappedBy即有inversedBy或无反向标记的一侧就是拥有方且unique默认等于owner——这正是外键列唯一约束的来源拥有方还会默认createForeignKeyConstraint true。五、ManyToMany拥有方、反向方与 pivot 表语义当前实体的多个实例关联被引用实体的多个实例。拥有方Entity() export class Book { // owner/inversedBy/mappedBy 都未提供时视为拥有方 ManyToMany() tags1 new CollectionBookTag(this); ManyToMany(() BookTag, books, { owner: true }) tags2 new CollectionBookTag(this); ManyToMany(() BookTag, books, { owner: true }) tags3 new CollectionBookTag(this); ManyToMany(() BookTag, books, { owner: true }) tags4 new CollectionBookTag(this); // 定义单向多对多只需单独提供目标实体 ManyToMany(() Author) friends: CollectionAuthor new CollectionAuthor(this); }反向方Entity() export class BookTag { // 反向方必须通过 mappedBy 属性/参数指向拥有方 ManyToMany(() Book, book book.tags) books new CollectionBook(this); }关于底层机制源码提供了两个可验证的细节见 MetadataDiscovery.tspivot 表名自动生成当关系带owner且平台使用 pivot 表时pivotTable会由 NamingStrategy.joinTableName 依据双方表名与属性名生成与文档命名策略自动生成、可用foreignKeyName等选项覆盖的说明一致反向方元数据自动派生元数据发现阶段会遍历所有mappedBy prop.name的反向ManyToMany属性把拥有方的pivotTable/pivotEntity/fixedOrder/joinColumns等字段同步过去L838-L857因此反向方不需要重复声明任何中间表配置。此外 EntitySchema.addManyToMany 中同样有默认值推导无owner且无mappedBy时自动置owner true拥有方会把误写的mappedBy重命名为inversedBy并默认创建外键约束。ManyToManyOptionstypes.ts L676 起还包含fixedOrder/fixedOrderColumn固定排序等选项集合操作细节参见 collections 文档。六、定制外键约束名称foreignKeyName如果需要对底层 SQL schema 做更精细的控制可以在拥有方关系上提供自定义外键约束名Entity() export class Book { ManyToOne(() Author, { foreignKeyName: my_custom_name }) author1: Author; }该名称会覆盖当前 NamingStrategy 自动生成的名字。在 types.ts 中ManyToOneOptions与OneToOneOptions的注释都写明foreignKeyName用于覆盖NamingStrategy.indexName()生成的约束名。七、禁用外键约束创建createForeignKeyConstraint关系级禁用若某个关系不需要生成底层 SQL 外键约束在拥有方上设置createForeignKeyConstraint: falseEntity() export class Book { ManyToOne(() Author, { createForeignKeyConstraint: false }) author1: Author; }注意默认行为ManyToOneOptions/OneToOneOptions中createForeignKeyConstraint的默认值是truetypes.ts L589-L590EntitySchema在为ManyToMany/OneToOne拥有方补默认值时也统一取trueEntitySchema.ts L284、L317。全局禁用如果通过全局配置schemaGenerator.createForeignKeyConstraints false关闭所有外键约束的创建则任何关系都不会创建外键约束即使个别关系显式将其设为trueconst orm await MikroORM.init({ ... schemaGenerator: { createForeignKeyConstraints: false, }, });仓库中的测试套件 tests/features/createForeignKeyConstraint/ 专门覆盖了这两种场景createForeignKeyConstraint.postgres.test.ts与createForeignKeyConstraint.mysql.test.ts分别验证在个别 OneToOne / ManyToOne / ManyToMany 拥有方禁用约束与全局禁用后即使关系级为 true 也不创建约束两类 DDL 输出对比orm.schema.getCreateSchemaSQL()的结果。八、ESM 项目中的循环依赖Rel 映射类型在 TypeScript ESM 项目中若启用了reflect-metadata可能会遇到循环依赖问题典型报错为ReferenceError: Cannot access Author before initialization解决方法是使用Rel映射类型。它是一个恒等类型identity type作用是禁用由reflect-metadata触发的类型推断——正是这种推断会导致 ESM 项目失败import { Rel } from mikro-orm/core; Entity() export class Book { ManyToOne(() Author) author!: RelAuthor; }源码中Rel的定义只有两行与其恒等定位完全一致typings.ts L826-L827/** Identity type that can be used to get around issues with cycles in bidirectional relations. It will disable reflect-metadata inference. */ export type RelT T;由于RelT在类型层面完全等价于T运行时实体类型不受任何影响仅在编译期切断了design:type元数据对目标类的直接引用。九、小结与延伸阅读主题文档/源码位置关系建模完整文档docs/versioned_docs/version-6.6/relationships.md级联与孤儿移除docs/versioned_docs/version-6.6/cascading.mdCollection 工作机制docs/versioned_docs/version-6.6/collections.md命名策略约束/列名生成docs/versioned_docs/version-6.6/naming-strategy.md关系选项类型定义packages/core/src/metadata/types.ts装饰器实现legacy / TC39packages/decorators/src/legacy/ManyToOne.ts、packages/decorators/src/es/OneToOne.ts元数据发现与双向自动接线packages/core/src/metadata/MetadataDiscovery.ts双向关系校验packages/core/src/metadata/MetadataValidator.ts外键约束测试tests/features/createForeignKeyConstraint/掌握本文后你应能完成为四类关系选择合适的定义语法纯装饰器 / 回调 / 字符串 / 选项对象正确区分并声明拥有方与反向方inversedBy/mappedBy/owner并依赖自动接线与启动期校验避免建模错误按需定制外键约束名或按关系/全局粒度关闭外键约束创建以及在 ESM 项目中用RelT规避reflect-metadata循环引用问题。赞分享后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载相关推荐MikroORM 双向关系传播Propagation机制详解让关系两侧始终保持同步MikroORM 双向关系传播Propagation机制详解让关系两侧始终保持同步 MikroORM 的 Propagation传播机制负责将双向关系后端单视图3D重建新突破AtlasNet如何通过2D图像生成高精度3D模型单视图3D重建新突破AtlasNet如何通过2D图像生成高精度3D模型 AtlasNet是一个基于深度学习的3D表面生成项目能够从低分辨率点云或单张2D图像LikeC4 DSL 关系建模双向关系-与单向关系-的完整指南LikeC4 DSL 关系建模双向关系 与单向关系 的完整指南 LikeC4 的 DSL 提供了一组精炼的关系运算符用于表达架构元素之间的交互开发工具CLI数据可视化MCP 服务UI组件上一篇游戏鼠标宏精准射击辅助工具配置指南下一篇罗技PUBG压枪宏配置从入门到精通提升射击精准度的完整方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表