后端【免费下载链接】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 官方升级文档docs/versioned_docs/version-6.6/upgrading-v5-to-v6.md整理完整覆盖 v5 到 v6 的所有破坏性变更运行时要求、加载策略调整、EntityRepositoryAPI 精简、type选项移除与驱动包导出的引入、raw/sql原生 SQL 片段、Date/BigInt类型映射、ReferenceAPI 变化等。读完并对照本文逐项自查后你可以安全地把一个基于 MikroORM v5 的项目迁移到 v6并且理解每项变更背后的设计动机与源码级依据。需要说明并非所有变更都会影响你——例如如果你不使用自定义NamingStrategy命名策略相关的变更就与你无关。建议结合自己的代码逐项核对。运行时与工具链要求Node 18.12 强制要求v6 放弃了对更老 Node 版本的支持。TypeScript 5.0 强制要求v6 放弃了对更老 TypeScript 版本的支持因为 v6 大量使用了新版 TS 的类型特性下文多处类型系统收紧都依赖这一点。迁移前请先确认engines与tsconfig满足以上两条这是后续所有类型级变更能正确生效的前提。类型系统收紧更严格的静态检查Strict partial loading严格的部分加载类型Loaded类型现在支持部分加载提示fields选项。当使用fields时返回类型只允许访问被选中的属性主键会被自动选中// book is typed to SelectedBook, author, title | author.email const book await em.findOneOrFail(Book, 1, { fields: [title, author.email], populate: [author], }); const id book.id; // ok, PK is selected automatically const title book.title; // ok, title is selected const publisher book.publisher; // fail, not selected const author book.author.id; // ok, PK is selected automatically const email book.author.email; // ok, selected const name book.author.name; // fail, not selected这意味着 v5 中运行时才能发现字段不存在的问题在 v6 会直接变成编译错误。从源码结构看查询选项类型定义在 packages/core/src/drivers/IDatabaseDriver.ts 中find/findOne系列的fields选项配合Loaded工具类型共同实现该能力。主键推断与PrimaryKeyPropv5 中一些方法允许通过第二个泛型参数传入主键属性v6 移除了这一用法改为自动推断需要显式声明主键类型时使用PrimaryKeyProp符号。同时PrimaryKeyType符号被移除。此外复合主键的值现在必须是元组tuple而非联合union以保证主键顺序Entity() export class Foo { ManyToOne(() Bar, { primary: true }) bar!: Bar; ManyToOne(() Baz, { primary: true }) baz!: Baz; - [PrimaryKeyType]?: [number, number]; - [PrimaryKeyProp]?: bar | baz; [PrimaryKeyProp]?: [bar, baz]; }移除BaseEntity泛型参数与BaseEntity.toJSONBaseEntity不再带泛型类型参数改用this类型-class User extends BaseEntityUser { ... } class User extends BaseEntity { ... }BaseEntity.toJSON方法被移除它的类型签名变得过于复杂导致难以重写而它本来就是唯一被设计为可重写的序列化钩子。该方法只是转发到BaseEntity.toObject所以请在代码中直接使用toObject。注意该方法在原型上仍然以普通方法的形式存在任何实体都有无论是否继承BaseEntity。wrap不再接受undefined类型层面wrap()不再接受undefined运行时实现仍会检查这种情况并原样返回参数。因为返回类型中从未允许可空值传入可空值从来都不是正确用法。如果你之前用wrap把实体实例转成引用包装新的ref()助手函数在这方面做得更好且接受可空值。em.insert()强制必填属性em.insert()现在和em.create()一样要求你传入所有非可选属性。对于TS 上必填、但有运行时或数据库默认值的属性可以用OptionalProps符号或新的Opt类型声明它们应视为可选从而跳过严格检查。加载策略与查询行为变化Joined 策略行为对齐与默认策略切换joined 策略现在支持populateWhere: all并且这是默认行为含义是无视 where 条件填充完整关系。此前 joined 策略不能做到这一点因为它复用了 where 子句相同的 join 条件v6 中 joined 策略会为被填充关系使用独立的 join 分支。这使各加载策略的行为保持一致。order by子句对两个 join 分支共享新增populateOrderBy选项允许单独控制被填充关系的排序。这两个选项在 packages/core/src/drivers/IDatabaseDriver.ts 中有对应定义populateWhere约 L301、populateOrderBy约 L328。SQL 驱动的默认加载策略改为 joined 策略。若要保持 v5 的旧行为可以在 ORM 配置中覆盖默认的loadStrategy。从当前主干仓库源码结构看packages/core/src/utils/Configuration.ts 中默认loadStrategy已进一步演进为LoadStrategy.BALANCED——也就是说 v6 时期的joined 默认在更新的版本中又被调整为 balanced如果你目标是升级到最新版本这一点要特别注意而单纯 v5→v6 升级时默认策略切换为 joined 是本文所述的行为。Join 条件别名解析变化附加 join 条件额外 where 条件过去隐式别名为根实体现在改为别名到被 join 的实体。如果你已经在 join 条件中使用了显式别名则没有变化// the name used to resolve to b.name, now it will resolve to a.name instead qb.join(b.author, a, { name: foo });遍历未初始化 Collection 直接抛错对未初始化的Collection实例执行for...of迭代时v6 会检查其初始化状态并在未初始化时抛错v5 会静默得到空结果极具迷惑性const author await em.findOne(User, 1); // this will throw as the books collection is not initialized for (const book of author.books) { // ... }这是刻意的行为收紧把忘记 populate从静默 bug 变成显式异常。em.populate()单实体返回单实体em.populate()现在喂什么还什么传单个实体返回单个实体传数组返回数组。这其实就是最初的行为只是 v5 时期类型上难以严格表达被改成了总是返回数组v6 借助类型能力恢复了原始行为且类型安全-const [author] await em.populate(author, [books]); const author await em.populate(author, [books]);populate: [*]与移除populate: true填充所有关系时Loaded类型现在能识别*通配符旧的布尔写法populate: true被移除改用populate: [*]。该规则同样适用于serialize()助手函数及其populate参数const users await em.find(User, {}, { - populate: true, populate: [*], });populate: false仍然允许用于禁用 eager load 属性。EntityRepository移除持久化相关方法以下方法不再存在于EntityRepository实例上persistpersistAndFlushremoveremoveAndFlushflush移除的动机是这些方法给人一种作用域化上下文例如只操作User类型的错觉实际上它们只是底层EntityManager同名方法的快捷方式。官方建议实体持久化直接操作EntityManager而把 repository 当作自定义逻辑如包装 Query Builder 用法的扩展点。-userRepository.persist(user); -await userRepository.flush(); em.persist(user); await em.flush();替代方案可以用repository.getEntityManager()直接拿到EntityManager调用这些方法。从当前仓库源码看packages/core/src/entity/EntityRepository.ts 确实已不含persist/flush等方法只保留查询、create、assign、merge、upsert、count、getReference等并暴露getEntityManager()。如果你希望保留 repository 层面的这些方法可以定义一个自定义基础 repository 并全局使用import { EntityManager, EntityRepository, AnyEntity } from mikro-orm/mysql; export class ExtendedEntityRepositoryT extends object extends EntityRepositoryT { persist(entity: AnyEntity | AnyEntity[]): EntityManager { return this.em.persist(entity); } async persistAndFlush(entity: AnyEntity | AnyEntity[]): Promisevoid { await this.em.persistAndFlush(entity); } remove(entity: AnyEntity): EntityManager { return this.em.remove(entity); } async removeAndFlush(entity: AnyEntity): Promisevoid { await this.em.removeAndFlush(entity); } async flush(): Promisevoid { return this.em.flush(); } }然后在 ORM 配置中指定它MikroORM.init({ entityRepository: ExtendedEntityRepository, })必要时还可以配合EntityRepositoryType符号例如放在自定义基础实体上一起使用。移除静态require()type选项与驱动包导出v5 中存在一些静态require()调用例如根据type选项加载驱动实现。这些代码对 webpack 等打包器以及 vite 等新一代构建系统很不友好v6 全部移除。type选项被移除改用驱动导出不再指定type改为三种方式之一使用从驱动包导入的defineConfig()助手创建 ORM 配置import { defineConfig } from mikro-orm/mysql; export default defineConfig({ ... });使用从驱动包导入的MikroORM.init()import { MikroORM } from mikro-orm/mysql; const orm await MikroORM.init({ ... });显式指定driver选项import { MySqlDriver } from mikro-orm/mysql; export default { driver: MySqlDriver, ... };环境变量MIKRO_ORM_TYPE仍然受支持但不再对驱动类做静态require。官方不推荐使用它且未来版本可能移除。从源码结构看MIKRO_ORM_前缀环境变量的统一读取集中在 packages/core/src/utils/env-vars.ts。defineConfig本身只是一个带类型辅助的恒等函数其通用定义位于 packages/core/src/utils/Configuration.tsdefineMySqlConfig等由驱动包再导出见 packages/mysql/src/index.ts。ORM 扩展需显式注册同理Migrator、EntityGenerator、Seeder这类扩展不再通过require()自动加载需要在 ORM 配置中注册为扩展SchemaGenerator扩展会自动注册。这只是为了使用MikroORM对象上的快捷方式如orm.migrator.up()你也可以自行实例化Migrator而不注册。import { defineConfig } from mikro-orm/mysql; import { Migrator } from mikro-orm/migrations; import { EntityGenerator } from mikro-orm/entity-generator; import { SeedManager } from mikro-orm/seeder; export default defineConfig({ dbName: test, extensions: [Migrator, EntityGenerator, SeedManager], // those would have a static register method });MikroORM.init()签名收紧不再接受Configuration实例options 必须始终是普通 JS 对象。传入Configuration实例从来只是内部用法部分场景在测试中有用并不是面向用户的 API。不再接受第二个connect参数请改用connect配置选项。驱动包统一再导出mikro-orm/core所有驱动包现在都再导出mikro-orm/core的内容你不再需要纠结 import 该来自哪个包——始终优先从驱动包导入-import { Entity, PrimaryKey } from mikro-orm/core; -import { MikroORM, EntityManager } from mikro-orm/mysql; import { Entity, PrimaryKey, MikroORM, EntityManager } from mikro-orm/mysql;以 MySQL 为例packages/mysql/src/index.ts 首行即export * from mikro-orm/sql并导出驱动类与defineConfig别名这一再导出结构正是该约定在源码中的体现。其他被移除的 API 与迁移对照移除项替代方案MongoDriver.createCollectionsorm.schema.createSchema()MongoDriver.dropCollectionsorm.schema.dropSchema()MongoDriver.refreshCollectionsorm.schema.refreshDatabase()MongoDriver.ensureIndexesorm.schema.ensureIndexes()JavaScriptMetadataProvider使用EntitySchema可用EntitySchema.fromMetadata()工厂辅助迁移其接口本身也很相似PropertyOptions.customType直接用typeem.nativeInsert()em.insert()em.persistLater()em.persist()em.removeLater()em.remove()IdentifiedReferenceRefuow.getOriginalEntityData()无参形式需带参数调用orm.schema.generate()移除无直接替代schema 生成走orm.schema系列方法qb.ref()sql.ref()RequestContext.createAsyncRequestContext.create现在可以 await其中customType的迁移写法-Property({ customType: new ArrayType() }) Property({ type: new ArrayType() }) foo: string[];RequestContext.createAsync的迁移写法-const ret await RequestContext.createAsync(em, async () { ... }); const ret await RequestContext.create(em, async () { ... });选项与类型重命名清单以下是 v6 的一批重命名迁移时可直接按表全局替换v5v6PropertyOptions.onUpdateIntegrityPropertyOptions.updateRulePropertyOptions.onDeletePropertyOptions.deleteRuleEntityProperty.referenceEntityProperty.kindReferenceTypeReferenceKindPropertyOptions.wrappedReferencePropertyOptions.refAssignOptions.mergeObjectsAssignOptions.mergeObjectPropertiesEntityOptions.customRepositoryEntityOptions.repositoryOptions.cacheOptions.metadataCacheUnitOfWork.registerManagedUnitOfWork.registerbaseDirEntityGenerator.generate()参数path环境变量MIKRO_ORM_CLIMIKRO_ORM_CLI_CONFIGInitOptionsInitCollectionOptions此外UseRequestContext()装饰器更名为CreateRequestContext()——名字更明确地表达总是创建新上下文同时新增EnsureRequestContext()装饰器当已有可用上下文时会复用它。原生 SQL 片段raw助手与sql标签模板v5 中原生 SQL 片段靠自动探测并不精确v6 引入新的raw静态助手来处理这件事const users await em.find(User, { - [expr(lower(email))]: foobar.baz, [raw(lower(email))]: foobar.baz, });原先的em.raw()和qb.raw()助手被移除expr助手同样被raw取代。任何 SQL 片段都必须显式用raw或sql标记这一点同时适用于查询的 key 和参数位置。从源码看raw()的实现位于 packages/core/src/utils/RawQueryFragment.ts支持字符串、??标识符占位、复合外键数组、对象参数映射等形态。raw还可以用于通过flush做原子更新const ref em.getReference(User, 1); ref.age raw(age * 2); await em.flush(); console.log(ref.age); // real value is available after flush或者使用新的sql标签模板函数更简洁的写法ref.age sqlage * 2;更完整的用法过滤器中的回调签名、索引表达式中的??占位、quote模板等可参考 使用原生 SQL 查询片段 一节。数据映射Date与BigIntPostgreSQLDate映射精度变化此前所有驱动默认把Date映射为 0 精度秒级的 timestamp。PostgreSQL 官方不推荐timestamp(0)/timestamptz(0)v6 中未显式设置length时的默认映射改为timestamptz微秒精度等价于timestamptz(6)。要恢复 v5 行为可以设置columnType: timestamptz(0)或使用length: 0Property({ length: 0 })各驱动Date属性映射统一此前 datetime 列到 JSDate对象的映射行为因驱动而异SQLite 没有开箱即用的支持需要在各处手动转换。v6 中所有驱动都禁用了隐式Date转换改为显式、且跨驱动一致地处理。同时date类型过去被当作datetime看待现在只有Date大写D才视为datetime小写date就是纯粹的date。最后DateType用于映射date列而非datetime不再映射为Date对象而是映射为string。原生 BigInt 支持bigint列的默认映射改为原生 JavaScriptBigInt并且可配置为 number 或 stringPrimaryKey() id0: bigint; // type is inferred PrimaryKey({ type: new BigIntType(bigint) }) id1: bigint; // same as id0 PrimaryKey({ type: new BigIntType(string) }) id2: string; PrimaryKey({ type: new BigIntType(number) }) id3: number;ReferenceAPI 的三处变化Reference.load(prop: keyof T)签名移除Reference.load()曾有重载一个用于确保实体已加载另一个用于一步取属性值。后者被移除改用新方法Reference.loadProperty(prop)-const email book.author.load(email); const email book.author.loadProperty(email);Reference.set()变为私有Reference包装器持有身份identity——其实例与底层实体绑定。当你试图更换被包装的实体时同一个Reference实例可能还存在于实体图的其他位置这就产生了问题。因此set方法不再公开应优先直接替换引用实例-book.author.set(other); book.author ref(other);Reference.load()可能返回null当目标实体未找到时可能已被删除也可能与当前启用的过滤器不兼容Reference.load()及所有基于WrappedEntity.init()的方法现在返回null而不是解析到一个未加载的实体。新增loadOrFail()方法行为与em.findOneOrFail()一致——有值就返回否则抛错-const publisher await book.publisher.load(); const publisher await book.publisher.loadOrFail();隐式序列化行为变化隐式序列化——即在实体上调用toObject()/toJSON()相对于显式使用serialize()助手——现在完全基于populate提示工作除非你通过wrap(entity).populated()显式标记某个关系已填充否则该关系只有在属于populate提示时才会出现在序列化结果中// lets say both Author and Book entity has a m:1 relation to Publisher entity // we only populate the publisher relation of the Book entity const user await em.findOneOrFail(Author, 1, { populate: [books.publisher], }); const dto wrap(user).toObject(); console.log(dto.publisher); // only the FK, e.g. 123 console.log(dto.books[0].publisher); // populated, e.g. { id: 123, name: ... }此外隐式序列化现在还尊重部分加载提示。此前所有已加载属性都会被序列化fields只在数据库查询层面起作用v6 起运行时也会裁剪数据。也就是说除非属性在fields提示中否则不会进入 DTO——唯一例外是主键可通过属性选项hidden: true选择隐藏。最直观的差异体现在外键上外键经常会被自动选中构建实体图所需但不再出现在 DTO 中。const user await em.findOneOrFail(Author, 1, { fields: [books.publisher.name], }); const dto wrap(user).toObject(); // only the publishers name will be available, previously there would be also book.author // { id: 1, books: [{ id: 2, publisher: { id: 3, name: ... } }] }这同样适用于 embeddable包括嵌套与 object 模式。其他行为收紧与配置变化重复fieldName现在会被校验同一实体内两个属性使用相同fieldName会抛错Entity() class User { PrimaryKey() id!: number; Property({ name: custom_name }) name!: number; Property({ name: custom_name }) age!: number; }虚拟属性不受该限制Entity() class User { PrimaryKey() id!: number; ManyToOne(() User, { name: parent_id }) parent!: User; Property({ name: parent_id, persist: false }) parentId!: number; }该校验可以通过discovery.checkDuplicateFieldNames配置项关闭。从源码看该选项默认值为truepackages/core/src/utils/Configuration.ts实际校验逻辑在 packages/core/src/metadata/MetadataValidator.ts 中执行。Embedded 属性遵循NamingStrategy这对 SQL 驱动主要是破坏性的SQL 驱动的默认命名策略是下划线命名现在也会应用到 embedded 属性上。若要恢复旧行为可实现自定义命名策略重写propertyToColumnName方法——它现在有一个第二个布尔参数表示属性是否定义在 JSON 对象上下文中import { UnderscoreNamingStrategy } from mikro-orm/core; class CustomNamingStrategy extends UnderscoreNamingStrategy { propertyToColumnName(propertyName: string, object?: boolean): string { if (object) { return propertyName; } return super.propertyToColumnName(propertyName, object); } }Seeder 包移除对faker的依赖faker是一个体积较大的库仅仅是 import 就可能带来性能损耗而 seeder 包本身并不直接使用它因此被移除。需要 faker 的用户自行安装并在工厂中使用import 时机由你自己控制-import { Factory, Faker } from mikro-orm/seeder; import { Factory } from mikro-orm/seeder; import { faker } from faker-js/faker/locale/en; export class ProjectFactory extends FactoryProject { model Project; - definition(faker: Faker): PartialProject { definition(): PartialProject { return { name: faker.company.name(), }; } }.env文件不再被自动加载v5 中如果根目录存在.env文件会被自动加载。v6 中它只被检查 ORM 环境变量MIKRO_ORM_前缀其余变量一律忽略。如果你需要访问.env中定义的全部环境变量请在应用或 ORM 配置文件中自行调用import dotenv/config。元数据缓存要求同步 APIMetadata CacheAdapter必须提供同步 API这是为了能在MikroORM.initSync中正常工作。元数据缓存现在强制同步接口通常你应依赖文件型缓存它现在使用同步方法操作文件系统。metadataCache.adapter同理需要同步实现。注意缓存只对TsMorphMetadataProvider有意义其他 provider 通常自身足够快不需要缓存。移除Subscriber()装饰器装饰器只有在对应文件被你的代码 import 时才生效而订阅者subscriber往往在应用中无处被 import导致装饰器经常不生效人们于是把它留着、又同时在 ORM 配置里添加订阅者——一旦文件真的被 import、装饰器开始生效就会发生重复注册。因此Subscriber()装饰器被移除统一使用subscribers配置项它现在既接受实例也接受类引用MikroORM.init({ subscribers: [MySubscriber], // or new MySubscriber() })迁移检查清单速查升级 Node 到 18.12、TypeScript 到 5.0把type选项改为驱动包导入defineConfig/ 驱动MikroORM/driver类并注册Migrator、EntityGenerator、SeedManager等扩展populate: true改为populate: [*]em.populate解包写法调整repository 上的persist/flush/remove系列迁到EntityManager或自定义基础 repositoryexpr/em.raw/qb.raw/qb.ref统一替换为raw/sql/sql.ref检查Date映射Postgres 精度、DateType改 string与bigint列默认改为原生BigInt按重命名清单全局替换选项与类型名Reference相关loadProperty、loadOrFail、用ref()替换set()调用BaseEntityUser去掉泛型JavaScriptMetadataProvider换EntitySchema检查隐式序列化输出字段是否符合新规则populate/fields 提示驱动确认.env中非 ORM 变量是否仍被读取必要时自行import dotenv/config。逐项完成后运行测试套件尤其是涉及联合查询、join 条件别名与序列化的用例即可确认迁移完整。赞分享后端【免费下载链接】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点击查看免费下载相关推荐手把手实战 LaMa 图像修复从大掩码到批量服务手把手实战 LaMa 图像修复从大掩码到批量服务 图像修复就是把图片里被遮挡、缺失的部分补回来。LaMaLarge Mask InpaintingWAC人工智能计算机视觉深度学习图像处理Plotly.py 版本迁移指南从 v2 到 v6 的破坏性变更与升级实战Plotly.py 版本迁移指南从 v2 到 v6 的破坏性变更与升级实战 本文以开源仓库 MIGRATION_GUIDE.md https://link.g数据可视化数据分析Shaka Player 升级指南从 v1 到 v6 的破坏性变更全解析与迁移实战Shaka Player 升级指南从 v1 到 v6 的破坏性变更全解析与迁移实战 本文是 Shaka Player 官方 docs/tutorials/up前端音视频上一篇Flower自动扩缩容弹性资源管理下一篇索尼耳机电脑控制终极指南免费开源工具快速上手教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?