- 后端
【免费下载链接】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(基于 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.
相关推荐
MikroORM JSON 属性实战指南:定义、查询、$elemMatch 与索引
MikroORM JSON 属性实战指南:定义、查询、$elemMatch 与索引 导读 本文是 MikroORM 官方文档 JSON Properties h
后端MikroORM 中的 JSON 属性:定义、按对象属性查询与索引实战指南
MikroORM 中的 JSON 属性:定义、按对象属性查询与索引实战指南 本文基于 MikroORM 6.6 版本文档( docs/versioned_doc
后端如何永久保存微信聊天记录?WeChatMsg开源工具完整指南
如何永久保存微信聊天记录?WeChatMsg开源工具完整指南 你是否曾因为换手机而丢失珍贵的聊天记录?那些与家人的温馨对话、重要的商务沟通、朋友的深夜畅谈,是否
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考