- 后端
【免费下载链接】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 属性功能允许你在关系型数据库中直接存储、查询和索引任意 JSON 结构,同时保持 TypeScript 的类型安全。本文将围绕官方文档 json-properties.md 展开,深入讲解如何定义 JSON 属性、如何按对象属性嵌套查询、如何使用$elemMatch匹配数组元素,以及如何在 JSON 路径上创建普通/唯一/复合索引,并结合仓库源码揭示其底层实现原理。读完本文,你将能够在 PostgreSQL、MySQL、MariaDB、SQLite 与 MongoDB 等支持的数据库中熟练使用 MikroORM 的 JSON 查询能力,并理解查询 SQL 是如何生成的。
定义 JSON 属性
不同数据库驱动对 JSON 的处理方式差异很大:有些驱动会自动解析 JSON 值并返回 JS 对象,另一些则原样返回 JSON 字符串。为了统一这种体验,MikroORM 通过JsonType类型封装了序列化与反序列化逻辑,当你指定type: 'json'时,ORM 也会自动使用该类型。
在实体中定义 JSON 属性非常简单:
@Entity() export class Book { @Property({ type: 'json', nullable: true }) meta?: { foo: string; bar: number }; }这里meta的 TypeScript 类型直接描述了 JSON 结构,MikroORM 无需额外的 schema 声明即可在读写时完成对象与数据库字符串之间的转换。
JsonType 的底层实现
从源码看,JsonType继承自Type基类,核心逻辑位于 packages/core/src/types/JsonType.ts:
convertToDatabaseValue:写入时将 JS 对象交给platform.convertJsonToDatabaseValue()序列化;convertToJSValue:读取时判断列类型是否为json/jsonb且平台支持自动转换,若支持则直接返回原始对象,否则调用platform.convertJsonToJSValue()解析字符串;convertToJSValueSQL/convertToDatabaseValueSQL:为 SQL 层提供列级转换(如 PostgreSQL 的::jsonb或::text转换);getColumnType:返回platform.getJsonDeclarationSQL(),即各平台对应的 JSON 列声明(如 PostgreSQL 的jsonb)。
这种设计使得同一个实体定义在不同数据库上都能正确工作,无需针对驱动单独写序列化代码。官方 custom-types.md 文档也确认了这一点:JsonType以驱动无关的方式处理序列化,仅在必要时才调用parse和stringify。
注意:
JsonType也常用于嵌入对象(embeddable)的存储,其ensureComparable方法决定了该属性在查询时是否需要走 JSON 路径比较逻辑。
按 JSON 对象属性查询
你可以像查询普通字段一样,直接按 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可以看到,MikroORM 将嵌套对象逐层展开为->/->>路径访问,并且:
- 字符串比较直接使用
->>取值; - 数字会附加
::float8类型转换; - 布尔值会附加
::bool类型转换。
所有受支持的驱动(包括 SQLite 和 MongoDB)都支持这种按 JSON 对象属性查询的方式。在 PostgreSQL 上,当检测到右侧值是数字或布尔值时,MikroORM 会自动尝试进行类型转换,以保证查询结果准确。
底层处理路径
当查询条件中某个属性的自定义类型是JsonType,且值不是原始 SQL(Raw)、不是$eq/$elemMatch操作符包装时,查询处理器会将其识别为 JSON 条件,并交由QueryHelper.processJsonCondition()处理(见 packages/core/src/utils/QueryHelper.ts)。该方法最终委托给对应平台的platform.processJsonCondition(),由各平台生成->/->>路径表达式、类型转换与别名处理,这正是不同数据库 JSON 语法差异得以统一的根源。
查询 JSON 数组元素:$elemMatch
当 JSON 属性存储的是对象数组时,你可以使用$elemMatch操作符查询满足条件的数组元素。MikroORM 会为不同平台生成相应的EXISTS子查询: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' } }, }); // 数值条件会被自动转换(如 PostgreSQL 上的 ::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中的多个条件必须命中同一个数组元素,而不是分散在不同元素上。这与 MongoDB 的$elemMatch语义一致,也与普通$contains(只要数组包含某个元素即满足)形成鲜明对比。
$elemMatch还可以通过$and与数组级别的操作符组合使用:
const events = await em.find(Event, { $and: [ { tags: { $elemMatch: { priority: { $gt: 5 } } } }, { tags: { $contains: [{ name: 'typescript' }] } }, ], });对于嵌入数组属性(embeddable array),由于 ORM 可以从 embeddable 元数据中获知元素结构,元素级查询是隐式生效的,无需显式书写
$elemMatch,详见 embeddables.md。
$elemMatch 的源码实现
$elemMatch的处理位于 packages/sql/src/query/QueryBuilderHelper.ts:当条件键的$elemMatch存在且是该键的唯一操作符时,会调用processJsonElemMatch()生成EXISTS子查询。该实现有两个值得注意的细节:
- 类型校验:
$elemMatch只能用于JsonType的数组属性。如果用在非 JSON 属性上,会抛出ValidationError,提示$elemMatch can only be used on JSON array properties; - 类型推断:
inferJsonValueType()(QueryBuilderHelper.ts)根据查询值的 JS 类型推断 JSON 元素类型——number推断为number、boolean推断为boolean、bigint推断为bigint、对象内部的值也会被递归探测,默认回退为string。这正是文档所述"类型自动推断"的实现基础,也是 PostgreSQL 上生成::float8等转换的依据。
$elemMatch与$contains等数组操作符组合时,processObjectSubCondition会先将多操作符条件拆分,使$elemMatch单独进入上述处理分支,从而正确生成多个EXISTS子查询并用AND连接(见 QueryBuilderHelper.ts)。
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 上,这会生成如下 SQL:
create index "book_meta_data_foo_index" on "book" (("meta_data"->>'foo'));创建唯一索引则使用@Unique()装饰器,语法与@Index()完全一致:
@Entity() @Unique({ properties: 'metaData.foo' }) @Unique({ properties: ['metaData.foo', 'metaData.bar'] }) // 复合唯一索引 export class Book { // ... }MySQL 上的 JSON 索引与函数索引
MySQL 不允许直接对 JSON 列建立普通索引,必须借助函数索引(functional index)。MikroORM 支持通过options显式指定returning类型,从而生成合法的 MySQL 函数索引:
@Entity() @Index({ properties: 'metaData.foo', options: { returning: 'char(200)' } }) export class Book { // ... }生成的 SQL 如下:
alter table `book` add index `book_meta_data_foo_index`((json_value(`meta_data`, '$.foo' returning char(200))));其中json_value()是 MySQL 的 JSON 提取函数,returning char(200)指定了索引列的返回类型。需要注意:MariaDB 驱动目前不支持此特性(json_value函数索引语法无法在 MariaDB 上生成)。
实践建议
- 在 PostgreSQL 上,优先使用
jsonb列类型,配合点路径索引可以在按 JSON 字段过滤时获得索引加速; - 对于高频查询的 JSON 字段,复合索引(多个点路径)能进一步提升组合过滤性能,但索引大小会随之增加,需权衡;
- MySQL 上必须为索引提供
returning类型,否则无法生成合法的函数索引;该选项在不同 MySQL 版本上的兼容性需结合目标实例验证。
小结
MikroORM 的 JSON 支持是一条完整的链路:JsonType统一了各驱动的序列化差异,type: 'json'即可声明 JSON 属性;查询层通过platform.processJsonCondition()生成平台相关的->/->>路径表达式并自动做类型转换;$elemMatch通过EXISTS子查询实现数组元素级过滤,类型自动推断免去了 schema 声明;索引层则支持点路径的普通/唯一/复合索引,并在 MySQL 上通过returning生成函数索引。掌握这些能力,你就可以在保持类型安全的前提下,把灵活的 JSON 结构无缝融入关系型数据库的工作流中。
- 后端
【免费下载链接】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 属性完全指南:定义、对象查询、$elemMatch 数组查询与 JSON 索引
MikroORM JSON 属性完全指南:定义、对象查询、$elemMatch 数组查询与 JSON 索引 MikroORM 为 Node.js 提供了一套跨数
后端MikroORM JSON 属性实战指南:定义、查询、$elemMatch 与索引
MikroORM JSON 属性实战指南:定义、查询、$elemMatch 与索引 本篇技术指南围绕 MikroORM(基于 Data Mapper、Unit
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考