☰
MikroORM v2 升级到 v3 完整迁移指南:破坏性变更、新事务模型与实体定义重构
2026/9/29 10:33:57 网站建设 项目流程
  • 后端

【免费下载链接】mikro-orm

TypeScript 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 官方 v2 → v3 升级文档(upgrading-v2-to-v3.md)为骨架,逐一解析每个破坏性变更的成因、影响面与迁移做法,并结合当前仓库源码(packages/core)印证新版本的实际实现。读完本文,你将掌握:autoFlush默认值变化后的正确持久化方式、基于em.transactional()的新事务模型、去IEntity化的实体类型体系(AnyEntity/WrappedEntity)、m:n 复合主键与fixedOrder排序、以及日志与元数据提供者的新配置方法。

提示:v3 属于历史版本,本文源码佐证均取自当前仓库(v7.x 分支)中的实现,用于说明这些机制自 v3 引入后如何演进并被保留;实际升级到新版本时,请以当前版本的 upgrading-v6-to-v7.md 等文档为准。

一、autoFlush 默认值改为 false:显式 flush 成为默认

v2 中autoFlush默认开启,实体persist后会自动同步到数据库;v3 起默认值变为false。这意味着你必须显式调用em.flush()才能把更改持久化:

orm.em.persist(new Entity()); // 默认不再自动 flush await orm.em.flush(); // 显式刷新,将变更写入数据库 await orm.em.persist(new Entity(), true); // 仍可通过第二个参数强制 auto-flush

迁移要点:

  • 如果你之前显式配置过autoFlush: false,现在可以直接删除该配置行,行为与默认一致;
  • 若想平滑过渡,仍可在 ORM 配置中临时开启autoFlush,但官方不推荐长期使用——它会在每次persist周围产生不必要的小事务,损害批量写入性能。

从当前仓库源码看,这一设计延续至今:EntityManager的persist仅在autoFlush开启(或调用flush())时才会真正落库,而工作单元(Unit of Work)机制会把多次persist合并到同一次flush()中批量执行(相关实现见 packages/core/src/EntityManager.ts 与 packages/core/src/unit-of-work/UnitOfWork.ts)。

二、实体定义重构:告别 IEntity,拥抱 AnyEntity / WrappedEntity

v2 要求实体与IEntity接口合并,导致实体的公开接口被内部方法“污染”。v3 引入了一组纯标记型新接口,它们不添加任何属性或方法,因此可以完全省略:

接口适用场景要求
IdEntity<T>数字/字符串主键且属性名为idid: number等
UuidEntity<T>字符串主键且属性名为uuiduuid: string
MongoEntity<T>MongoDB 实体必须含id: string与_id: ObjectId
AnyEntity<T, PK>其他任意主键名用PK参数指明主键属性名,如AnyEntity<Book, 'myPrimaryProperty'>

IEntity被重命名为AnyEntity,且不再暴露toJSON()、toObject()、init()等公开方法。这些能力改由wrap()提供,并在需要时按属性类型增强:

await wrap(book.author).init(); // 通过 wrap() 使用 init()

若希望实体上直接保留全部方法(与 v2 一致的体验),可通过接口合并引入WrappedEntity<T, PK>——它同时继承了AnyEntity并声明了所有辅助方法:

@Entity() export class Book { /* ... */ } export interface Book extends WrappedEntity<Book, 'id'> { }

在 packages/core/src/typings.ts 中可以查看IWrappedEntity的完整方法面:isInitialized()、populated()、populate()、init()、toObject()、toJSON()、serialize()、assign()、getSchema()/setSchema()等,均为实体状态管理的核心 API。更多实体定义示例见 defining-entities.md。

三、QueryBuilder 底层接入 Knex:连接池与事务模型重建

v3 起QueryBuilder内部使用knex执行所有查询,连接池能力随之免费获得。此改动带来的关键连锁变化:

1. 事务 API 收敛为em.transactional()

旧的beginTransaction/commit/rollback辅助方法被移除,事务必须通过em.transactional()包裹:

await orm.em.transactional(async (em) => { const author = new Author('Jon'); em.persist(author); // 回调结束时自动 flush });

2. 事务上下文从 Driver 上移到 EntityManager

所有事务管理逻辑从IDatabaseDriver接口中移除,改由 EM 统一持有事务上下文(由Connection创建),并透传给各 driver 方法。EM 为此新增了两个方法:

  • isInTransaction():判断当前 EM 是否运行在数据库事务内;
  • getTransactionContext():获取驱动相关的事务上下文对象(用于确保查询在同一连接上执行)。

当前源码实现可见 packages/core/src/EntityManager.ts:isInTransaction()检查内部#transactionContext,getTransactionContext()则原样返回该上下文。transactional()方法(EntityManager.ts)委托TransactionManager处理嵌套、传播与回滚(见 packages/core/src/utils/TransactionManager.ts)。新版还支持嵌套事务(默认创建 savepoint)与propagation选项控制传播行为。

3. 参数占位符统一为?

此前 Postgres 驱动要求按索引的美元符号占位符($1、$2…),knex 接管后统一为简单问号?,与其他方言保持一致。

四、ManyToMany 改用复合主键,fixedOrder 保留稳定排序

v2 中 m:n 关联表必须有自增主键;v3 起默认只要求两列外键,并以两者构成的复合主键作为表主键:

@ManyToMany({ entity: () => BookTag, pivotTable: 'book_tags' }) tags = new Collection<BookTag>(this);

若你依赖集合的稳定顺序,可通过fixedOrder: true恢复旧行为——此时按id列排序;排序列名可用fixedOrderColumn覆盖:

@ManyToMany({ entity: () => BookTag, fixedOrder: true, fixedOrderColumn: 'order' }) tags = new Collection<BookTag>(this);

也可直接用orderBy: { ... }指定默认排序。

从 packages/core/src/metadata/MetadataDiscovery.ts 可以看到这些选项的归一化逻辑:fixedOrder在未显式设置时会由fixedOrderColumn推导,默认fixedOrderColumn取第一个主键列名;属性级配置定义见 packages/core/src/typings.ts。

五、实体引用不再持有实例化的集合

v2 中所有实体实例(包括只知主键的实体引用)都带有实例化的集合类;v3 起只有已初始化的实体才拥有集合:

const book = em.getReference(Book, 1); console.log(book.tags); // undefined —— 引用阶段不实例化集合 await book.init(); // 初始化后 console.log(book.tags); // Collection 实例(尚未加载)

好处是大幅降低轻量引用(如getReference返回的对象)的内存占用与初始化开销,只有真正访问关系时才会创建Collection。

六、EntityAssigner.assign():新实体必须显式传入 EM

v2 中所有实体内部都持有根 EM 引用;v3 起只有被管理实体(已 merge 到 EM,如从数据库加载的实体)才保留该内部引用。因此,对新建(未管理)实体调用assign()时必须显式提供em参数:

const book = new Book(); wrap(book).assign(data, { em: orm.em });

相关实现见 packages/core/src/entity/EntityAssigner.ts 与 packages/core/src/entity/wrap.ts,IWrappedEntity.assign()的类型签名位于 packages/core/src/typings.ts。

七、严格化的 FilterQuery 与智能查询条件

FilterQuery不再允许使用 v2 的“智能操作符”字符串语法。旧写法{ 'age:gte': 18 }必须改为对象语法,或显式把条件断言为any:

// 旧:{ 'age:gte': 18 } ❌ // 新: em.find(User, { age: { $gte: 18 } });

类型层面更严格有助于在编译期捕获拼写错误的字段名与操作符,条件对象的类型定义(FilterQuery、FilterValue、FilterObjectProp等)位于 packages/core/src/typings.ts。

八、日志配置:内置 console.log 与 debug 命名空间

v2 要求自定义 logger 才能开启日志;v3 起 logger 默认为console.log(),只需通过debug选项订阅感兴趣的命名空间:

// MikroORM.init 配置 { debug: true, // 开启全部命名空间 // debug: ['query', 'query-params'], // 仅订阅部分命名空间 }
  • true/false分别启用 / 禁用所有命名空间;
  • 可用命名空间:'query'、'query-params'、'discovery'、'info'。

当前仓库 packages/core/src/utils/Configuration.ts 仍保留这四个命名空间的定义,说明该日志体系自 v3 起延续至今。

九、其他破坏性变更速查

1. 1:m / m:1 装饰器中的fk选项被移除

必须改用mappedBy(1:m 侧)与inversedBy(m:1 侧)。

2.SchemaGenerator.generate()改为异步

不再直接调用,推荐改用内置 CLI 工具;CLI 配置见 installation section,SchemaGenerator 用法见 schema-generator.md。

3. NamingStrategy 接口新增getClassName()

该方法根据文件名推断实体类名,自定义命名策略时可覆写。若你实现了自定义策略,必须补上该方法,或改为继承AbstractNamingStrategy。

4.TypescriptMetadataProvider更名为TsMorphMetadataProvider

同时新增基于reflect-metadata的ReflectMetadataProvider。由于旧名曾是默认提供者,多数用户无需改动。当前仓库中内置提供者为ReflectMetadataProvider(默认)与TsMorphMetadataProvider(见 packages/core/src/utils/Configuration.ts)。

5. MongoDB 驱动最低版本提升到 3.3.4

使用 MongoDB 需升级驱动版本。

6.EntityManager.find()的where参数变为必填

find方法签名与EntityRepository对齐,where不再可省略;查询全部实体请显式传空对象:

const all = await em.find(Book, {}); // 显式空条件

十、升级自查清单

按本文顺序逐项检查你的代码库:

  • 移除冗余的autoFlush: false配置,检查所有依赖隐式自动 flush 的代码路径,改为显式await em.flush()
  • 将实体与IEntity的合并替换为可选的AnyEntity<T, PK>/IdEntity<T>/UuidEntity<T>/MongoEntity<T>,需要完整方法时改用WrappedEntity<T, PK>
  • 把beginTransaction/commit/rollback调用重写为em.transactional(cb);检查 Postgres 参数占位符是否仍为$1格式
  • 确认 m:n 关联表迁移方案兼容复合主键;依赖稳定顺序时配置fixedOrder: true
  • 检查对getReference()返回对象的集合访问,必要时先await ref.init()
  • 对所有新建实体的wrap(entity).assign(data)补充{ em }参数
  • 将{ 'age:gte': 18 }类智能操作符改为对象语法{ age: { $gte: 18 } }
  • 用debug选项替换自定义 logger 的日志初始化
  • 清理 1:m/m:1 装饰器中的fk,改用mappedBy/inversedBy
  • SchemaGenerator 改为 CLI 调用;自定义 NamingStrategy 补充getClassName()
  • 如有em.find(Entity)无参调用,补{}作为where
  • 后端

【免费下载链接】mikro-orm

TypeScript 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
点击查看免费下载
上一篇:解决Windows设备管理难题:HidHide内核级过滤技术深度解析
下一篇:3分钟掌握GB/T 7714—2015引用规范:终极CSL样式库指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询