- 后端
【免费下载链接】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.
本篇技术指南围绕 MikroORM 官方兼容包@mikro-orm/knex-compat展开:它解决了在 MikroORM 查询(em.find、em.createQueryBuilder等)中直接复用既有 KnexQueryBuilder与knex.raw()表达式的问题,把这些对象统一转换为 MikroORM 的RawQueryFragment。读完本文,你将掌握该包的安装方式、raw助手的两种典型用法(作为对象键与作为子查询值)、它与@mikro-orm/core自带raw()的分工边界,以及其底层实现的转换原理与运行时安全机制。
为什么需要这个兼容包
MikroORM 的 SQL 驱动底层原本依赖 knex 构建查询,而在 v6.5 之后的演进中,项目逐步用 kysely 取代 knex 执行查询(相关计划与说明见 docs/blog/2025-08-27-mikro-orm-6-5-released.md 中 "knex being replaces with kysely for query execution" 一节)。因此,SQL 驱动重新导出的raw助手(见 packages/sql/src/query/raw.ts)在检测到对象带有compile方法时按 KyselyQueryBuilder处理,而无法直接识别 knex 的QueryBuilder与Raw实例。
对于仍然使用 knex 编写 SQL 表达式的存量代码,@mikro-orm/knex-compat正是为这一迁移场景提供的官方兼容层:它导出一个raw助手,接受 knexQueryBuilder和Raw实例,并将其转换成 MikroORM 可以理解的RawQueryFragment对象,使这些既有表达式可以无缝进入 MikroORM 的查询体系。包的功能定位在 packages/knex-compat/README.md 开头有明确说明。
安装与依赖约束
npm install @mikro-orm/knex-compat knex其中knex是 peer dependency,需要单独安装。查看 packages/knex-compat/package.json 可以看到完整的约束:
peerDependencies:@mikro-orm/sql(版本与当前包严格对齐,如7.2.1)以及knex: ^3.0.0;engines.node:要求 Node.js>= 22.17.0;- 包以 ESM 形式发布(
"type": "module"),入口指向 packages/knex-compat/src/index.ts。
这意味着使用该包的项目应同时装有对应版本的@mikro-orm/sql(通常由你使用的 SQL 驱动包间接提供)和 knex v3。
核心用法:把 knex 表达式传入 MikroORM 查询
从该包导入的raw助手可以直接用在em.find的过滤条件中,既能作为对象键(key),也能作为值(value)——这也是 MikroORM 中原始 SQL 片段的标准使用形态。
场景一:作为对象键,传入knex.raw()实例
import { raw } from '@mikro-orm/knex-compat'; import knex from 'knex'; const k = knex({ client: 'pg' }); // Pass a knex.raw() instance await em.find(User, { [raw(k.raw('lower(name)'))]: name.toLowerCase() });这里k.raw('lower(name)')是 knex 的原生原始表达式,经过兼容层转换后作为过滤条件的键,等价于在 SQL 中生成where lower(name) = ?形式的条件,并把name.toLowerCase()作为绑定参数。
场景二:作为值,传入 knex QueryBuilder 子查询
// Pass a knex QueryBuilder instance const subquery = k('book') .count('*') .where('author_id', k.raw('??', ['author.id'])); await em.find(Author, { [raw(subquery)]: { $gt: 5 } });这里把整个 knex 子查询对象作为条件键raw(subquery),配合$gt: 5操作符,最终生成类似where (select count(*) from book where author_id = author.id) > 5的查询。注意 knex 中??占位符用于标识符(这里是列名author.id),这正是 knex 写法本身的特点。
与@mikro-orm/core自带raw()的分工
原文档特别强调:对于不带 knex 的普通字符串 SQL 片段,应直接使用@mikro-orm/core的raw()助手;只有当你手里已经有现成的 knex 表达式需要集成时,才需要这个兼容包。例如:
import { raw } from '@mikro-orm/core'; // 普通字符串片段直接用 core 的 raw await em.find(User, { [raw('lower(name)')]: name.toLowerCase() });关于raw()助手的完整能力(命名参数、数组参数、过滤器、索引表达式、sql标签模板、sql.ref()/sql.now()/sql.lower()等),可进一步参考 docs/docs/raw-queries.md 的官方指南。
底层原理:rawKnex 是如何转换的
整个包的核心实现只有两个文件:
- packages/knex-compat/src/index.ts 把
rawKnex以raw的名字重新导出; - packages/knex-compat/src/raw.ts 定义了
rawKnex函数本体。
rawKnex的转换逻辑(packages/knex-compat/src/raw.ts)非常精炼,核心是“鸭子类型”检测:
export function rawKnex<R = RawQueryFragment & symbol, T extends object = any>( sql: | QueryBuilderLike | Knex.QueryBuilder | Knex.Raw | EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>, ): R { if (Utils.isObject<Knex.QueryBuilder | Knex.Raw>(sql) && 'toSQL' in sql) { const query = sql.toSQL(); return raw(query.sql, query.bindings); } return raw(sql, params); }可以拆解为三步:
- 类型判断:通过
Utils.isObject(...) && 'toSQL' in sql识别 knex 对象。无论是 knex 的QueryBuilder还是Raw实例,都实现了toSQL()方法,这是与 KyselyQueryBuilder(其标志方法是compile())区分的关键特征——后者由 packages/sql/src/query/raw.ts 中的 SQL 驱动版raw负责处理; - 编译取参:调用
sql.toSQL()得到{ sql, bindings },即 knex 最终生成的 SQL 字符串与按顺序排列的绑定参数; - 委托转换:把编译结果交给
@mikro-orm/sql的raw(query.sql, query.bindings),由其构造RawQueryFragment实例。
也就是说,兼容包本身不做 SQL 生成,它只是把 knex 的编译产物“翻译”成 MikroORM 认识的数据结构,后续的别名替换、参数绑定、方言引号处理全部交由 MikroORM 原有管线完成。
RawQueryFragment:既是值也是键的运行时安全机制
转换的最终产物RawQueryFragment定义在 packages/core/src/utils/RawQueryFragment.ts,它保证原始片段可以在两个位置使用:
- 作为值:直接赋值给属性或过滤条件值;
- 作为键:通过
Symbol.toPrimitive(RawQueryFragment.ts)在需要字符串化时返回一个唯一 Symbol 作为对象键,同时把该 Symbol 与片段实例登记到内部rawQueryReferences弱引用表(keygetter 见 RawQueryFragment.ts)。
序列化得到的键会被缓存,ORM 只识别“已登记的已知片段键”(isKnownFragmentSymbol/getKnownFragment静态方法),这带来一层运行时安全:普通字符串、JSON 载荷或其他对象无法伪造被 ORM 认可的原始 SQL 键,防止注入类误用。
测试验证:三种输入形态的等价输出
仓库测试直接验证了该兼容包的行为,见 tests/features/entity-manager/EntityManager.postgre.test.ts 的em.find with knex query用例(pglite 版本见 tests/features/entity-manager/EntityManager.pglite.test.ts)。测试覆盖三种输入:
// 1) MikroORM 自己的 QueryBuilder(同样可被兼容层接受) const qb1 = orm.em.createQueryBuilder(Book2, 'b').select('b.uuid').where({ author: 1 }); await orm.em.find(Author2, { books: { $in: rawKnex(qb1) } }); // 2) knex.raw() 实例 const pg = knex({ client: 'pg' }); const raw1 = pg.raw('select "b"."uuid_pk" from "book2" as "b" where "b"."author_id" = 2'); await orm.em.find(Author2, { books: { $in: rawKnex(raw1) } }); // 3) knex QueryBuilder(链式 select/from/where) const raw2 = pg.select('b.uuid_pk').from({ b: 'book2' }).where('b.author_id', 3); await orm.em.find(Author2, { books: { $in: rawKnex(raw2) } });测试对生成的 SQL 做了断言,三种形态分别生成(author_id取 1、2、3):
where "b2"."uuid_pk" in (select "b"."uuid_pk" from "book2" as "b" where "b"."author_id" = N)这说明:无论片段来自 MikroORM 自身 QueryBuilder、knex 原始表达式还是 knex 链式查询构建器,经过兼容层转换后输出结构完全一致,可以被$in等集合操作符直接使用。类似地,在 v6.6 的发布说明 docs/blog/2025-11-11-mikro-orm-6-6-released.md 中也展示了em.find(User, { id: raw(knexRaw) })这一官方示例。
适用边界与注意事项
- 只处理 knex 对象:
rawKnex对非 knex 对象(字符串、数组、回调、RawQueryFragment等)会原样透传给@mikro-orm/sql的raw,因此它同时保留了普通raw()的全部能力,但它的设计初衷仍是“knex 兼容”; - 版本对齐:该包与
@mikro-orm/sql的 peer 版本严格绑定(当前为 7.2.1),升级 MikroORM 时需同步升级兼容包; - Node 版本:要求 Node.js >= 22.17.0,部署环境需满足该前提;
- 优先使用原生
raw():纯字符串 SQL 片段建议直接使用@mikro-orm/core或 SQL 驱动导出的raw(),兼容包只在确实需要集成既有 knex 表达式时引入,避免不必要的依赖。
小结
@mikro-orm/knex-compat是一个小而精准的桥接层:它识别 knexQueryBuilder/Raw的toSQL()特征,将其编译为 SQL 字符串与绑定参数后委托给 MikroORM 的raw(),最终以带运行时安全校验的RawQueryFragment形式参与查询构建。对于正在从 knex 迁移到 kysely、或希望在新代码中复用历史 knex 表达式的 MikroORM 用户,这是官方提供的标准集成入口,相关实现与测试均可直接在本仓库中查阅。
- 后端
【免费下载链接】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.
相关推荐
big-AGI 仓库中的 /tape-opens 技能:基于转录本测量形状的会话堆栈展开回顾
big AGI 仓库中的 /tape opens 技能:基于转录本测量形状的会话堆栈展开回顾 导读 本文介绍 big AGI 仓库中沉淀的一套 Claude C
后端Drizzle ORM与其他ORM对比:Prisma、Kysely、Knex优缺点分析
Drizzle ORM与其他ORM对比:Prisma、Kysely、Knex优缺点分析 Drizzle ORM是一个现代化的TypeScript ORM库,专为
后端数据库ORMObjection.js 入门指南:基于Knex的Node.js ORM框架
Objection.js 入门指南:基于Knex的Node.js ORM框架 什么是Objection.js Objection.js 是一个基于 Knex 构
数据库后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考