☰
MikroORM JSON 属性实战指南:定义、查询、$elemMatch 与索引
2026/9/27 8:42:37 网站建设 项目流程
  • 后端

【免费下载链接】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(基于 Data Mapper、Unit of Work 与 Identity Map 模式的 TypeScript ORM)中JSON 属性的完整使用链路展开:从实体中定义 JSON 字段、按 JSON 对象属性查询、对 JSON 数组使用$elemMatch,到为 JSON 属性创建索引,内容以 docs/versioned_docs/version-5.9/json-properties.md(v5.9 版本文档)为主体骨架,并结合当前仓库packages/core与packages/sql的源码实现进行纵深印证。读完本文,你将能直接在项目中使用type: 'json'字段并写出跨 PostgreSQL / MySQL / MariaDB / SQLite / MongoDB / MSSQL 等驱动统一语义的 JSON 查询。

定义 JSON 属性

不同数据库驱动对 JSON 列的处理方式差异很大:有些驱动(如 MongoDB、PostgreSQL 的jsonb)会自动把查询结果解析为 JavaScript 对象,另一些驱动则返回 JSON 字符串。MikroORM 通过统一的 JsonType(Type<unknown, string | null>)来抹平这些差异——只要在@Property中指定type: 'json',ORM 就会自动选用该类型。

@Entity() export class Book { @Property({ type: 'json', nullable: true }) meta?: { foo: string; bar: number }; }

从 JsonType 源码可以看到它的核心职责:

  • convertToDatabaseValue:写入数据库时把 JS 对象交给platform.convertJsonToDatabaseValue序列化;
  • convertToJSValue:读取时先判断当前驱动是否convertsJsonAutomatically()(见 Platform.ts,默认返回true),如果驱动本身已自动解析 JSON 列,就直接返回原值避免重复JSON.parse;
  • getColumnType:列类型统一交给platform.getJsonDeclarationSQL(),例如 PostgreSQL 平台返回jsonb(见 BasePostgreSqlPlatform.ts),其余驱动默认是json。

按 JSON 对象属性查询

该能力自 v4.4.2 起加入,v5.9 完全支持。

可以在findOne/find的查询条件中直接使用嵌套对象匹配 JSON 内部结构:

const b = await em.findOne(Book, { meta: { valid: true, nested: { foo: '123', bar: 321, deep: { baz: 59, qux: false, }, }, }, });

在 PostgreSQL 上会生成如下 SQL(路径逐层用->/->>展开,字符串取文本、数字与布尔值自动加类型转换):

select "e0".* from "book" as "e0" where ("meta"->>'valid')::bool = true and "meta"->'nested'->>'foo' = '123' and ("meta"->'nested'->>'bar')::float8 = 321 and ("meta"->'nested'->'deep'->>'baz')::float8 = 59 and ("meta"->'nested'->'deep'->>'qux')::bool = false limit 1

该能力目前覆盖所有驱动(包括 SQLite 与 MongoDB)。在 PostgreSQL 上,当右侧值为 number 或 boolean 时,ORM 会尝试对提取结果做类型转换。

这条查询链路的底层实现在 QueryHelper.ts:当属性是JsonType且条件值是普通对象(非$eq/$elemMatch开头)时,会调用processJsonCondition递归地把每个键展开成 JSON 路径;随后各平台通过getSearchJsonPropertyKey生成具体 SQL 片段。以 PostgreSQL 为例(BasePostgreSqlPlatform.ts):

  • 字符串类型的叶子用->>取文本;
  • 数字 / 布尔值通过#jsonTypeCasts映射表生成::float8、::bool等显式转换;
  • 路径中间层用->连接,键名一律安全引用,防止 SQL 注入。

使用$elemMatch查询 JSON 数组元素

当 JSON 属性存放的是对象数组时,可用$elemMatch操作符针对数组中的单个元素属性做查询。MikroORM 会生成EXISTS子查询,并针对各平台选择对应的 JSON 数组展开函数(PostgreSQL 用jsonb_array_elements、MySQL/MariaDB 用json_table、SQLite 用json_each)。查询值的类型会被自动推断,无需额外 schema 提示:

@Entity() export class Event { @Property({ type: 'json', nullable: true }) tags?: { name: string; priority: number }[]; } // 找出带 "typescript" 标签的事件 const events = await em.find(Event, { tags: { $elemMatch: { name: 'typescript' } }, }); // 数值条件自动完成类型转换(如 postgres 上的 ::float8) const events = await em.find(Event, { tags: { $elemMatch: { priority: { $gt: 5 } } }, }); // 多个条件必须命中同一个数组元素 const events = await em.find(Event, { tags: { $elemMatch: { name: 'typescript', priority: { $gte: 8 } } }, }); // $or/$and/$not 在 $elemMatch 内部同样可用 const events = await em.find(Event, { tags: { $elemMatch: { $or: [{ name: 'typescript' }, { name: 'rust' }] } }, });

$elemMatch还可以通过$and与数组级操作符组合使用:

const events = await em.find(Event, { $and: [ { tags: { $elemMatch: { priority: { $gt: 5 } } } }, { tags: { $contains: [{ name: 'typescript' }] } }, ], });

对于嵌入数组属性,由于 ORM 已从 embeddable 元数据中获知元素 schema,元素级查询可以隐式进行,无需显式$elemMatch。

仓库中的端到端测试 tests/features/embeddables/json-elem-match.test.ts 在 sqlite / mysql / mariadb / postgresql / mssql / oracledb 六种驱动上统一验证了该行为,关键结论包括:

  • EXISTS语义:tags为null或空数组的事件不会命中(由EXISTS子查询天然处理);
  • 同元素约束:{ name: 'typescript', priority: { $lt: 3 } }这类跨条件组合要求同一元素同时满足——测试中typescript的 priority 为 10,因此返回 0 条;
  • $not否定:$elemMatch: { $not: { name: 'typescript' } }匹配至少含一个非 typescript 标签的事件;
  • 安全防护:在非 JSON 属性上使用$elemMatch会抛错;包含x'; DROP TABLE event --这类非法属性名的查询会抛出Invalid JSON property name,键名均被安全引用(safely quoted),不存在注入风险。

源码层面,QueryHelper.ts 在判定 JSON 条件时把$eq/$elemMatch排除在"普通对象"之外,从而让它们走常规操作符处理路径;DatabaseDriver.ts 则显式允许$exists、$ne、$eq、$elemMatch、$all作为 JSON 属性内部的操作符。

为 JSON 属性创建索引

借助实体级@Index()装饰器 + 点路径(dot path),可以为 JSON 属性内部的字段建立索引:

@Entity() @Index({ properties: 'metaData.foo' }) @Index({ properties: ['metaData.foo', 'metaData.bar'] }) // 复合索引 export class Book { @Property({ type: 'json', nullable: true }) metaData?: { foo: string; bar: number }; }

在 PostgreSQL 上生成的索引 DDL 大致如下:

create index "book_meta_data_foo_index" on "book" (("meta_data"->>'foo'));

唯一索引使用@Unique()装饰器,写法一致:

@Entity() @Unique({ properties: 'metaData.foo' }) @Unique({ properties: ['metaData.foo', 'metaData.bar'] }) // 复合唯一索引 export class Book { @Property({ type: 'json', nullable: true }) metaData?: { foo: string; bar: number }; }

MySQL 上还可以通过options显式指定生成表达式(returning 'char(200)'用于控制表达式索引的返回类型):

@Entity() @Index({ properties: 'metaData.foo', options: { returning: 'char(200)' } }) export class Book { @Property({ type: 'json', nullable: true }) metaData?: { foo: string; bar: number }; }

生成的 DDL 如下:

alter table `book` add index `book_meta_data_foo_index`((json_value(`meta_data`, '$.foo' returning char(200))));

注意:MariaDB 驱动不支持该特性。

索引生成同样由平台层实现支撑:Platform.getJsonIndexDefinition(Platform.ts)的默认实现原样返回列名;PostgreSQL 平台覆写该方法(BasePostgreSqlPlatform.ts),把metaData.foo这样的点路径转换为("meta_data"->>'foo')表达式索引,路径中间层用->、叶子用->>,与查询条件的生成规则保持一致。

总结与适用边界

  • 定义 JSON 字段统一使用@Property({ type: 'json' }),由JsonType+ 各平台getJsonDeclarationSQL决定实际列类型(PostgreSQL 为jsonb);
  • 按 JSON 对象属性查询时嵌套对象会被自动展平为路径表达式,PostgreSQL 会对 number / boolean 做类型转换;
  • JSON 数组查询使用$elemMatch,多条件命中同一元素,支持$or/$and/$not/$in,可与$contains等数组级操作符通过$and组合;
  • 索引与唯一索引通过实体级@Index/@Unique+ 点路径声明,MariaDB 驱动除外;
  • 本文示例以 v5.9 文档为准,$elemMatch与 JSON 索引相关能力在后续版本(v6/v7)中持续演进,可在 docs/docs/json-properties.md 查阅最新版本文档。
  • 后端

【免费下载链接】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
点击查看免费下载
上一篇:CANN/ge RT2运行时约束规范
下一篇:TorchMetrics完全指南:5分钟掌握PyTorch机器学习评估利器

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

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

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

立即咨询