后端【免费下载链接】mikro-ormTypeScript 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 内置的一套轻量级查询结果缓存机制它允许EntityManager的查询方法与QueryBuilder的查询结果在指定时间内被复用从而避免重复命中数据库。本文以官方文档 docs/docs/caching.md 为骨架结合packages/core与packages/sql中的源码实现和 result-cache 测试套件 的验证行为系统讲解缓存如何开启、缓存键如何生成、命中后如何还原实体以及如何通过实现CacheAdapter接口接入 Redis 等外部存储。结果缓存是什么适用范围与默认行为MikroORM 的结果缓存result cache是一套简单但实用的机制适用于以下EntityManager方法find()findOne()findAndCount()findOneOrFail()count()以及QueryBuilder的所有结果获取方法包括execute()。当这些查询开启缓存后相同条件的查询在缓存有效期内会直接复用上一次的结果不再向数据库发起新的 SQL。关于默认行为官方文档明确指出两点默认使用内存缓存MemoryCacheAdapter该缓存对整个MikroORM实例共享默认过期时间为 1 秒1000ms。需要特别说明的是结果缓存默认是关闭的必须显式开启逐查询或全局配置这与 Identity Map身份映射是两回事——后者始终存在用于保证同一个实体在单位工作Unit of Work内是同一个实例。驱动层接口的注释也明确区分了这一点见 IDatabaseDriver.ts 中cache选项的说明“Result cache is by default disabled, not to be confused with the identity map.”。按查询启用缓存cache选项的三种形态在find()等方法的 options 中传入cache字段即可为该次查询单独开启缓存。cache字段支持三种形态类型定义为boolean | number | [string, number]见 IDatabaseDriver.ts形态含义cache: true使用默认缓存键自动生成和默认过期时间cache: 50使用自动缓存键但将过期时间覆盖为 50mscache: [cache-key, 50]同时指定自定义缓存键和过期时间60s 可写作60_000官方文档给出的find()示例const res await em.find(Book, { author: { name: Jon Snow } }, { populate: [author, tags], cache: 50, // 设置过期时间为 50ms // cache: [cache-key, 50], // 自定义缓存键 过期时间 // cache: true, // 使用默认缓存键和过期时间 });同样的选项也适用于findOne()、findOneOrFail()、findAndCount()与count()例如const res await em.findOneOrFail(Book, { author: { name: Jon Snow } }, { populate: [author, tags], cache: [abc, 100], }); const total await em.count(Book, { author: { name: Jon Snow } }, { cache: 100 });QueryBuilder 的.cache()方法QueryBuilder提供链式方法.cache()默认参数为true同样支持数字与[key, expiration]元组const res await em.createQueryBuilder(Book) .where({ author: { name: Jon Snow } }) .cache() // 使用默认缓存键和过期时间 .getResultList();.cache()的实现位于 packages/sql/src/query/QueryBuilder.ts它只是把配置存入查询状态cache(config: boolean | number | [string, number] true): this { this.ensureNotFinalized(); this.#state.cache config; return this; }getResultList()、getSingleResult()、getCount()、execute()等结果方法在真正执行 SQL 前都会先经过tryCache检查、执行后经storeCache写回见 QueryBuilder.ts因此只要链上调用过.cache()所有结果方法都会生效。全局启用与默认配置resultCache配置详解如果希望对所有查询统一启用缓存可以在MikroORM.init()的配置中设置resultCache字段const orm await MikroORM.init({ resultCache: { // 以下均为默认值 adapter: MemoryCacheAdapter, // 缓存适配器类 expiration: 1000, // 默认过期时间1s options: {}, // 传给适配器构造函数的额外选项 // 也可以全局启用缓存对全部查询生效 // global: 50, // 全局缓存过期时间 50ms }, // ... });resultCache配置项的完整类型定义位于 packages/core/src/utils/Configuration.ts各字段说明如下expiration?: number默认缓存过期时间毫秒默认1000。当某个查询只写了cache: true而未显式指定数字时使用该值。adapter?: { new (...params: any[]): CacheAdapter }缓存适配器类默认MemoryCacheAdapter。需要实现CacheAdapter接口。options?: Dictionary透传给适配器构造函数的选项对象默认{}。例如自定义适配器所需的连接配置、缓存目录等。global?: boolean | number | [string, number]是否对所有查询全局启用结果缓存。可以是true用默认过期时间、一个数字全局过期时间或[key, expiration]元组。开启后即使单个查询没有传cache选项也会生效。全局配置的实际取值逻辑在tryCache/storeCache中体现EntityManager.ts 与 EntityManager.tsconfig ?? this.config.get(resultCache).global;也就是说查询级cache选项优先未指定时回退到全局global配置。全局开关也可以在运行时动态修改测试套件中就有orm.config.get(resultCache).global 100;之后再次置为undefined的用法见 result-cache.postgre.test.ts。适配器实例的创建由Configuration.getResultCacheAdapter()完成Configuration.ts它通过getCachedService按配置惰性创建并缓存单例并将expiration与options合并后传给适配器构造函数getResultCacheAdapter(): CacheAdapter { return this.getCachedService(this.#options.resultCache.adapter!, { expiration: this.#options.resultCache.expiration, ...this.#options.resultCache.options, }); }缓存键是如何生成的理解缓存键的生成规则有助于判断哪些查询会共享缓存、哪些不会。MikroORM 的缓存键生成分两条路径EntityManager系列方法cacheKey()find()等方法的自动缓存键由内部方法cacheKey()生成EntityManager.ts其核心逻辑为剔除与结果无关的选项从 options 中删除ctx、strategy、flushMode、logging、loggerContext、signal、inflightQueryAbortStrategy等字段注释说明 logger context 等可能包含同一查询的动态数据不应参与键计算实体标识优先使用数据库侧信息如果元数据存在使用[schema, tableName, discriminatorValue]表名跨构建、跨进程稳定且为单表继承STI加入判别值元数据未知时才回退到实体类名组合键[entityKey, method, opts, where]其中method是em.find、em.findOne、em.count等RLS 会话上下文作用域如果存在行级安全Row Level Security的会话上下文会将其追加到键尾部避免不同租户/角色之间的缓存串扰。QueryBuilder基于 SQL 与参数QueryBuilder的缓存键由执行路径构造QueryBuilder.tsconst cacheKey: unknown[] [qb.execute, query.sql, query.params, method];即“SQL 语句 绑定参数 结果方法名”的组合同样地存在会话上下文时也会追加到键中。由于键包含完整 SQL 与参数相同查询条件但不同排序、分页的查询不会互相污染。自定义键与会话作用域当使用cache: [cache-key, 50]显式指定键时如果当前存在会话上下文实际存储的键会追加|${JSON.stringify(sessionContext)}后缀见 EntityManager.ts 的注释“a named cache key discards the computed key ... scope it here too”防止 fork 出来的 EntityManager 在相同命名键下串读其他会话的数据。缓存命中后发生了什么命中缓存并不是简单地把 JSON 数据原样返回而是经历了一个实体还原过程。核心逻辑在tryCache()EntityManager.tsconst cached await em.#resultCache.get(cacheKey); if (!cached) { return { key: cacheKey, data: cached }; } // ... if (Array.isArray(cached) merge) { data cached.map(item em.#entityFactory.createT(entityName, item, createOptions)) as unknown as R; } else if (Utils.isObjectEntityDataT(cached) merge) { data em.#entityFactory.createT(entityName, cached, createOptions) as unknown as R; } else { data cached; } await em.#unitOfWork.dispatchOnLoadEvent();关键点对缓存中的实体数据通过实体工厂entityFactory.create()重新创建实体实例并支持merge合并进当前上下文、refresh、schema等选项recomputeSnapshot: true会重算快照还原后还会派发onLoad生命周期事件在find()的调用路径中EntityManager.ts命中缓存后返回实体列表并继续执行entityLoader.populate()完成 populate 关联加载——也就是说缓存命中并不跳过 populate 流程关联数据依然会被正确加载当使用 JOINED 策略时关联数据通常已随查询一并缓存测试中命中后不会再产生新的 SQL。未命中时查询正常执行结果通过storeCache()写回缓存EntityManager.ts过期时间的计算规则为元组取第二个元素、数字取自身、true则为undefined交由适配器使用默认过期时间。清除缓存clearCache()与显式键结果缓存没有自动失效机制除非达到过期时间因此如果需要主动让缓存失效必须为缓存指定显式键之后调用em.clearCache(cacheKey)// 以 book-cache-key 作为缓存键过期时间 60s const res await em.find(Book, { ... }, { cache: [book-cache-key, 60_000] }); // 按名称清除该缓存项 await em.clearCache(book-cache-key);clearCache()的实现EntityManager.ts会先移除指定键如果存在会话上下文还会一并移除${cacheKey}|${JSON.stringify(sessionContext)}变体确保命名键在会话作用域下也能被彻底清理。测试套件对清除行为做了明确验证result-cache.postgre.test.ts在缓存有效期内clearCache(abc)之后再次查询SQL 调用数从 2 增加到 3证明缓存已被清除、查询重新执行。CacheAdapter接口与内置适配器所有缓存后端都必须实现CacheAdapter接口。官方文档给出的接口定义与仓库 packages/core/src/cache/CacheAdapter.ts 中的实现一致export interface CacheAdapter { /** * 获取 name 键下的缓存项。 */ get(name: string): Promiseany; /** * 写入缓存项。origin 用于缓存失效判断应反映数据来源的变化。 */ set(name: string, data: any, origin: string, expiration?: number): Promisevoid; /** * 移除指定缓存项。 */ remove(name: string): Promisevoid; /** * 清空所有缓存项。 */ clear(): Promisevoid; /** * 在 MikroORM.close() 中被调用用于优雅关闭例如 Redis 连接。 */ close?(): Promisevoid; }值得补充的是仓库中实际接口比文档示例更进一步get/set/remove/clear均支持同步与异步两种返回形态并额外定义了用于元数据缓存的同步变体SyncCacheAdapter带可选的combine()方法见 CacheAdapter.ts。适配器同时服务于结果缓存与元数据缓存两条链路。内置适配器一览仓库packages/core/src/cache/目录下提供了四个内置适配器适配器文件用途MemoryCacheAdapterMemoryCacheAdapter.ts结果缓存默认适配器进程内 Map 存储基于时间过期NullCacheAdapterNullCacheAdapter.ts空操作适配器所有读写均为 no-op用于关闭缓存FileCacheAdapterFileCacheAdapter.ts文件落盘缓存JSON 文件主要用于元数据缓存支持combined合并模式GeneratedCacheAdapterGeneratedCacheAdapter.ts基于预生成静态数据的适配器由 CLIcache:generate命令产出MemoryCacheAdapter源码级解读默认的MemoryCacheAdapterMemoryCacheAdapter.ts实现非常简洁值得逐行理解其过期策略export class MemoryCacheAdapter implements CacheAdapter { readonly #data new Mapstring, { data: any; expiration: number }(); readonly #options: { expiration: number }; constructor(options: { expiration: number }) { this.#options options; } getT any(name: string): T | undefined { const data this.#data.get(name); if (data) { if (data.expiration Date.now()) { this.#data.delete(name); // 惰性过期读取时才发现过期并删除 } else { return data.data; } } return undefined; } set(name: string, data: any, origin: string, expiration?: number): void { this.#data.set(name, { data, expiration: Date.now() (expiration ?? this.#options.expiration) }); } }要点底层是Mapstring, { data, expiration }没有后台清理线程采用惰性过期——条目在get()时被检查若已过期则删除并返回undefined视为未命中set()时若无显式expiration回退到构造函数收到的默认过期时间即配置中的resultCache.expiration因为存的是普通对象引用缓存的实体数据与返回结果共享内存这也是为什么tryCache中要重新通过实体工厂创建实例并重算快照。编写自定义适配器如 Redis官方文档特别在close?()的注释中点名了 Redis 场景。要接入外部存储只需实现CacheAdapter接口并在配置中替换adapter即可例如一个基于 Redis 的示意实现import type { CacheAdapter } from mikro-orm/core; class RedisCacheAdapter implements CacheAdapter { constructor(private client: RedisClient, private defaultExpiration: number) {} async get(name: string) { const raw await this.client.get(name); return raw ? JSON.parse(raw) : undefined; } async set(name: string, data: any, origin: string, expiration?: number) { const ttl expiration ?? this.defaultExpiration; await this.client.set(name, JSON.stringify(data), EX, ttl / 1000); } async remove(name: string) { await this.client.del(name); } async clear() { // 按业务规则清空相关键例如 scan del } async close() { await this.client.quit(); // 在 orm.close() 时优雅关闭连接 } } const orm await MikroORM.init({ resultCache: { adapter: RedisCacheAdapter, expiration: 1000, options: { /* 传给构造函数的额外参数 */ }, }, });注意expiration与options会被合并后传入构造函数见前文getResultCacheAdapter()源码因此自定义适配器可以从构造参数中读取默认过期时间。关闭时的清理钩子MikroORM.close()会依次调用元数据缓存适配器与结果缓存适配器的close()MikroORM.tsasync close(force false): Promisevoid { await this.driver.close(force); await this.config.getMetadataCacheAdapter()?.close?.(); await this.config.getResultCacheAdapter()?.close?.(); }close?()是可选的未实现则跳过——这正是为 Redis 这类需要主动断开连接的外部存储准备的优雅关闭入口。测试验证缓存命中、过期与清除的完整证据仓库在 tests/features/result-cache/result-cache.postgre.test.ts 中用 PostgreSQL 环境系统验证了上述全部行为测试手法是通过 mock 查询日志计数来判断是否真的执行了 SQLfind命中与过期L38-L85cache: 100的查询执行 1 次 SQL50ms 后未过期再次查询仍为 1 次命中再推进 1ms100ms 过期后查询 SQL 计数变为 2未命中重新执行。全局缓存L87-L137设置orm.config.get(resultCache).global 100后不带cache选项的查询同样被缓存清除global后恢复原状。findOneOrFail与显式键清除L139-L197使用cache: [abc, 100]命中期间无新 SQL调用em.clearCache(abc)后下一次查询 SQL 计数增加证明显式键被成功清除。count与QueryBuilderL199-L279count支持cache: 100QueryBuilder 链式.cache(100)同样表现为“命中无新 SQL、过期后重新执行”并且.cache()无参数与.cache(100)在键层面是等价的测试最后用.cache()验证过期后重查。元数据未知时的键回退L281-L286未发现元数据的实体缓存键回退为[NotDiscovered, em.find, {}, {}]形式。此外还有 MongoDB 平台的结果缓存测试 result-cache.mongo.test.ts说明该机制对非 SQL 平台同样适用。使用建议与边界结果缓存 ≠ 数据实时性缓存只按过期时间失效写入数据后需要主动clearCache()才能立即可见。因此它更适合低频变化、读多写少的查询如分类列表、统计数据。过期时间以毫秒为单位cache: 50即 50mscache: [book-cache-key, 60_000]即 60s未显式指定时使用resultCache.expiration默认 1000ms。自定义键要能对应到清除动作只有使用显式键的缓存才能通过em.clearCache(key)精准清除自动键由内部规则生成不建议手动猜测。多租户/RLS 场景存在会话上下文时缓存键会自动附加上下文作用域显式键也会被追加后缀避免跨租户串读——清除时clearCache()同样处理了对应变体。与元数据缓存的区别本文介绍的是查询结果缓存实体元数据的磁盘/内存缓存是另一套机制metadataCache配置与FileCacheAdapter等适配器详见 docs/docs/metadata-cache.md。与 Identity Map 的区别结果缓存默认关闭且服务于跨请求复用Identity Map 始终开启且服务于单次单位工作内的实体唯一性二者不要混淆。赞分享后端【免费下载链接】mikro-ormTypeScript 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点击查看免费下载相关推荐macOS 下 TPM 报 returned 127 怎么解决macOS 下 TPM 报 returned 127 怎么解决 在 macOS 上通过 Homebrew 安装 tmux 并使用 TPMTmux Plu后端MikroORM 结果缓存Result Cache完全指南从默认内存缓存到自定义 CacheAdapterMikroORM 结果缓存Result Cache完全指南从默认内存缓存到自定义 CacheAdapter MikroORM 内置了一套轻量级的结果缓存后端MikroORM 结果缓存实战从 find() 的 cache 选项、全局配置到自定义 CacheAdapter 实现MikroORM 结果缓存实战从 find 的 cache 选项、全局配置到自定义 CacheAdapter 实现 本篇指南基于 MikroORM 官方文档后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
基于Django与深度学习的购物可视化与行为预测系统 又到一年毕设季,后台私信里问选题的同学越来越多了。说句实在话,大多数毕设题目不是难在“做不出来”,而是难在“一眼看起来就没技术含量”。今天把这个基于django深度学习的淘宝用户购物可视化与行为预测系统拿出来聊一聊,正好是… · 2026/9/26 6:21:11
2机5节点系统Simulink暂态稳定仿真建模与功角分析实战 2机5节点系统这个题目,做电力系统课程设计和研究入门的人应该都不陌生。它既不复杂到让你无从下手,又比单机无穷大系统更能看到真实的机电振荡行为。我当年第一次在MATLAB/Simulink里把这个模型跑通并复现出教材里的功角摇摆曲线时,那种“原来… · 2026/9/26 6:21:11
AI写代码能信吗?16万行代码背后的AI Engineering实践 16万行代码,不是一次性“敲”出来的,是“跑”出来的。这里的跑,有两种含义:一是项目不断迭代、持续演进,代码总量像雪球一样滚起来;二是AI Coding工具在背后不停生成、修改、再生成,把写代码这件… · 2026/9/26 6:59:18
音乐网站毕业设计实战:Spring Boot+Vue前后端分离项目全解析 做毕业设计的时候,一听到“音乐网站”就觉得太普通,但恰恰是这类题目最容易拿高分。“乐之境音乐网站”是一个典型的计算机毕业设计原创项目,前后端分离,覆盖用户注册登录、歌曲搜索播放、歌单管理、评论互动和后台管理࿰… · 2026/9/26 6:59:18
C++多重继承实战:菱形继承、虚继承与使用纪律 多重继承大概是C里争议最大的特性之一,没有“之一”。我最早接触它是在刚工作那年的代码评审上,一位老同事指着一棵五层继承树问我“这里走的是哪个Base?”,我当时答不上来。后来被菱形继承坑过、被虚函数表搞懵过、也被二义性编译… · 2026/9/26 6:59:18
C语言strcat陷阱全解析:从缓冲区溢出到安全替代方案 如果你在C语言项目里搜索“段错误”出现次数最多的函数,strcat一定排得进前三。我见过不少人一边骂strcpy不安全,一边却对strcat毫无防备:没有检查剩余空间、没有确认源字符串以\0结尾、甚至让源字符串和目标字符串指向同一块内存。直到日志模… · 2026/9/26 6:59:18
从笔记仓库到知识系统:五年实践沉淀的高效管理方案 我正式开始搭建自己的知识管理系统,大概是五年前的事了。这五年里换过三个笔记软件、迁移过四次数据、攒下过上千条笔记,但真正让我决心重构整个系统的,是一次特别尴尬的经历:某天开会前,我需要找出半年前写的一份关于… · 2026/9/26 6:59:18
美赛各题型代码包实战指南:从熵权TOPSIS到蒙特卡洛的快速上手 简介:这份资源面向参加数学建模竞赛(尤其是美赛)的学生与研究者,系统整理了各常见题型的参考代码,覆盖从线性回归等基础方法到遗传算法改进神经网络等进阶模型,适合需要快速搭建求解框架、对照复现算法的中… · 2026/9/26 6:59:12
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46