ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MikroORM 视图实体(View Entities)完全指南:从虚拟实体到物化视图的实战详解

MikroORM 视图实体(View Entities)完全指南:从虚拟实体到物化视图的实战详解 后端【免费下载链接】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点击查看免费下载导读视图实体View Entity是 MikroORM 中用于映射真实数据库视图的核心机制它允许你把一段 SQL 查询定义成实体由 ORM 的 Schema Generator 自动生成CREATE VIEW/DROP VIEW语句并纳入迁移Migration的追踪与 diff 之中。本文将围绕官方文档 docs/versioned_docs/version-7.1/view-entities.md 展开结合仓库源码与测试用例完整讲解视图实体的定义方式字符串表达式与 QueryBuilder 表达式、查询与只读行为、主键使用、物化视图Materialized Views以及跨数据库支持情况。读完本文你将能够用 MikroORM 以类型安全的方式把报表查询、遗留视图、复杂 JOIN 等场景沉淀为可复用、可迁移、可被 Identity Map 追踪的数据库视图。一、什么是视图实体与虚拟实体的本质区别在 MikroORM 中用 SQL 表达式定义实体有两种截然不同的实现理解二者的差异是使用视图实体的前提虚拟实体Virtual Entities只在查询时动态求值表达式数据库中不创建任何对象不允许定义主键视图实体View Entities在数据库中创建持久的视图对象Schema Generator 会生成CREATE VIEW/DROP VIEW迁移Migration会对其进行追踪和 diff允许定义主键。这一点在源码中有直接印证在 packages/core/src/typings.ts 中元数据初始化时会把对象形式的view选项归一化为扁平字段并据此判定实体类型// Normalize object-form view option: view: { materialized: true, withData: false } // into flat metadata fields (view: true, materialized: true, withData: false). if (typeof this.view object) { this.materialized this.view.materialized; this.withData this.view.withData; this.view true; } // If view is set, this is a database view entity (not a virtual entity). // Virtual entities evaluate expressions at query time, view entities create actual database views. this.virtual !!this.expression !this.view;也就是说只要有expression而没有view: true实体就是虚拟实体只有同时设置view: true才会变成视图实体。官方文档用一张对比表总结了全部差异FeatureVirtual EntitiesView EntitiesDatabase objectNone表达式在查询时求值Actual database view真实数据库视图Primary key不允许允许Schema generation忽略生成CREATE VIEW/DROP VIEWMigrations不追踪追踪并 diffRead-only是是Use case动态查询、聚合可复用视图、复杂查询、遗留视图在 packages/core/src/metadata/types.ts 中virtual与view选项的注释也明确说明使用expression选项时除非设置了view自动标记为 virtual视图实体默认只读view: true表示普通视图view: { materialized: true }表示物化视图仅 PostgreSQL。二、定义视图实体view: trueexpression定义一个视图实体需要同时设置两个选项view: true或对象形式view: { materialized: true, withData: false }标记为视图实体expression视图背后的 SQL 查询定义可以是字符串或返回 QueryBuilder 的回调。2.1 使用字符串表达式字符串表达式直接内嵌原生 SQL适合复用已有、较复杂的查询。下面以作者及其书籍数量统计视图为例展示四种官方支持的实体定义方式defineEntity class、defineEntity、reflect-metadata 装饰器、ts-morph 装饰器。方式一defineEntity class 组合文件./entities/AuthorStats.tsimport { defineEntity, p } from mikro-orm/core; const AuthorStatsSchema defineEntity({ name: AuthorStats, tableName: author_stats_view, view: true, expression: select a.name, count(b.id) as book_count from author a left join book b on b.author_id a.id group by a.id, a.name , properties: { name: p.string().primary(), bookCount: p.integer(), }, }); export class AuthorStats extends AuthorStatsSchema.class {} AuthorStatsSchema.setClass(AuthorStats);方式二纯defineEntity文件./entities/AuthorStats.tsimport { defineEntity, p } from mikro-orm/core; export const AuthorStats defineEntity({ name: AuthorStats, tableName: author_stats_view, view: true, expression: select a.name, count(b.id) as book_count from author a left join book b on b.author_id a.id group by a.id, a.name , properties: { name: p.string().primary(), bookCount: p.integer(), }, });方式三reflect-metadata 装饰器文件./entities/AuthorStats.tsEntity({ tableName: author_stats_view, view: true, expression: select a.name, count(b.id) as book_count from author a left join book b on b.author_id a.id group by a.id, a.name , }) export class AuthorStats { PrimaryKey() name!: string; Property() bookCount!: number; }方式四ts-morph 装饰器文件./entities/AuthorStats.ts与方式三代码完全一致装饰器写法相同区别在于元数据由 ts-morph 编译期分析而非运行时反射提供此处不再重复列出。注意book_count是 SQL 中的列别名实体属性名则使用驼峰bookCount——属性到列名的映射由命名策略处理视图实体与普通实体在此行为一致。2.2 使用 QueryBuilder 表达式类型安全除了字符串expression还可以是一个接收EntityManager并返回QueryBuilder的回调。这样做的好处是视图定义可以复用实体间的关联关系如join(b.author, a)中的b.author路径享受类型检查和重构时的安全性。以书籍摘要视图为例文件./entities/BookSummary.tsimport { defineEntity, p } from mikro-orm/core; const BookSummarySchema defineEntity({ name: BookSummary, tableName: book_summary_view, view: true, expression: (em: EntityManager) { return em.createQueryBuilder(Book, b) .select([b.title, a.name as author_name]) .join(b.author, a); }, properties: { title: p.string().primary(), authorName: p.string(), }, }); export class BookSummary extends BookSummarySchema.class {} BookSummarySchema.setClass(BookSummary);装饰器写法与之等价expression同样接收em参数Entity({ tableName: book_summary_view, view: true, expression: (em: EntityManager) { return em.createQueryBuilder(Book, b) .select([b.title, a.name as author_name]) .join(b.author, a); }, }) export class BookSummary { PrimaryKey() title!: string; Property() authorName!: string; }在 packages/core/src/metadata/types.ts 中expression的类型定义支持字符串与回调两种形态回调还能拿到where、options与stream参数说明 MikroORM 在元数据层即为此设计了完整的灵活性。2.3 校验规则没有 expression 的视图实体无法初始化视图实体必须携带expression否则 ORM 初始化会直接报错。仓库中的校验测试 tests/features/view-entities/view-entities.validation.test.ts 明确验证了这一点test(view entity without expression should throw, async () { Entity({ tableName: invalid_view, view: true }) class InvalidView { PrimaryKey() id!: number; } await expect( MikroORM.init({ entities: [InvalidView], dbName: :memory:, metadataProvider: ReflectMetadataProvider, }), ).rejects.toThrow(/view.*expression/i); });同时该测试也验证了合法定义初始化后orm.getMetadata(ValidView).view为trueexpression被原样保存。三、查询视图实体与普通实体完全一致的 API视图实体在查询层面与普通实体无差别可以使用EntityManager与QueryBuilder的全部查询能力// 查询全部 const stats await em.find(AuthorStats, {}); // 带条件查询书籍数量 5 的作者 const prolificAuthors await em.find(AuthorStats, { bookCount: { $gte: 5 }, }); // 使用 QueryBuilderTop 10 作者 const topAuthors await em.createQueryBuilder(AuthorStats, a) .where({ bookCount: { $gt: 0 } }) .orderBy({ bookCount: desc }) .limit(10) .getResult();由于视图实体允许主键查询结果会进入 Identity Map 进行身份追踪同一请求内重复查询返回同一实例这为后续的findOne按主键查找和关系引用Relation提供了基础。四、只读行为对视图实体的写入不会落库视图实体在定义时即被自动标记为只读readonly: true。对实例的修改会被 Unit of Work 忽略flush()不会为视图实体生成任何INSERT/UPDATE语句const stat await em.findOne(AuthorStats, { name: John }); stat.bookCount 100; // 该修改不会被持久化 await em.flush(); // 不会为视图实体生成 INSERT/UPDATE这也符合 SQL 的语义视图本质上是查询的投影多数数据库尤其是复杂 JOIN 与聚合视图不支持直接写入即使某些简单视图可更新MikroORM 也刻意保持只读避免产生不可预期的副作用。官方文档在Limitations一节再次强调视图实体是只读的无法被持久化部分数据库对可更新视图存在限制。五、主键视图实体与虚拟实体的关键分水岭与虚拟实体不同视图实体可以且应该定义主键这带来三个实际收益Identity Map 正确追踪请求内同一主键的实体实例唯一支持findOne按主键查找如em.findOne(AuthorStats, { name: John })可被其他实体在关系Relation中引用如有需要。Entity({ tableName: my_view, view: true, expression: ... }) export class MyView { PrimaryKey() id!: number; // 主键在视图实体中是允许的 Property() value!: string; }选择主键列时建议选取结果集中天然唯一的列如聚合分组键、业务唯一键这能让 Identity Map 与findOne发挥最大价值。六、典型使用场景官方文档给出了五类最适合视图实体的场景报表查询Reporting queries为仪表盘预聚合数据把count、sum等聚合逻辑下沉到数据库遗留数据库视图Legacy database views映射数据库里已经存在的视图无需改写既有 SQL复杂 JOINComplex joins把频繁联表的查询封装成单一视图简化应用层代码反规范化数据Denormalized data以扁平结构暴露规范化表的数据便于消费方直接读取访问控制Access control通过视图只暴露受限字段从数据库层控制数据可见范围。配合遗留视图场景Schema Generator 的 introspection 能力还能把已有视图反向读回元数据见下文Schema 生成。七、支持的数据库视图实体在所有 SQL 数据库中均受支持覆盖PostgreSQLMySQL / MariaDBSQLiteMicrosoft SQL ServerMongoDB 不支持视图实体——它没有数据库视图这一概念。官方文档明确建议MongoDB 场景请改用虚拟实体Virtual Entities。仓库中也按数据库分列了完整的测试验证见 tests/features/view-entities/ 下的view-entities.postgres.test.ts、view-entities.mysql.test.ts、view-entities.sqlite.test.ts、view-entities.mssql.test.ts每个方言都有对应的定义、建视图与查询测试。八、物化视图Materialized Views物化视图是把查询结果物理预计算并存储的数据库特性读取性能更高但数据存在时效性必须显式刷新才能反映底层数据的变化。8.1 定义物化视图实体在 PostgreSQL 中只需把view写成对象形式view: { materialized: true }Entity({ tableName: author_stats_mat_view, view: { materialized: true }, expression: select a.name, count(b.id) as book_count from author a left join book b on b.author_id a.id group by a.id, a.name , }) export class AuthorStatsMatView { PrimaryKey() name!: string; Property() bookCount!: number; }官方文档也提供了defineEntity的写法见 docs/docs/materialized-views.mdimport { defineEntity, p } from mikro-orm/postgresql; const AuthorStatsSchema defineEntity({ name: AuthorStats, tableName: author_stats_matview, view: { materialized: true }, expression: select a.id, a.name, count(b.id)::int as book_count from author a left join book b on b.author_id a.id group by a.id , properties: { id: p.integer().primary(), name: p.string(), bookCount: p.integer(), }, });8.2withData: false创建空物化视图默认情况下物化视图创建时会立即填充数据对应 SQL 的WITH DATA。如果底层表在建库时尚为空或希望控制首次数据填充时机可以设置withData: false生成WITH NO DATAconst AuthorStats defineEntity({ name: AuthorStats, tableName: author_stats_matview, view: { materialized: true, withData: false }, // 生成 WITH NO DATA expression: select ..., properties: { ... }, });8.3 刷新物化视图物化视图缓存了查询结果必须刷新才能看到新数据。MikroORM 在 PostgreSQL 方言的 EntityManager 上提供了refreshMaterializedView方法。其实现位于 packages/sql/src/dialects/postgresql/BasePostgreSqlEntityManager.ts关键逻辑是校验目标实体确实是物化视图然后委托 Schema Helper 生成并执行 SQLasync refreshMaterializedViewEntity extends object( entityName: EntityNameEntity, options?: { concurrently?: boolean }, ): Promisevoid { const meta this.getMetadata(entityName); if (!meta.view || !meta.materialized) { throw new Error(Entity ${meta.className} is not a materialized view); } const helper this.getDriver().getPlatform().getSchemaHelper()!; const schema meta.schema ?? this.config.get(schema); const sql helper.refreshMaterializedView(meta.tableName, schema, options?.concurrently); await this.execute(sql); }使用方式import { MikroORM, EntityManager } from mikro-orm/postgresql; const orm await MikroORM.init({ ... }); const em orm.em; // 刷新物化视图 await em.refreshMaterializedView(AuthorStats); // 刷新后查询返回最新数据 const stats await em.find(AuthorStats, {});并发刷新Concurrent RefreshPostgreSQL 支持REFRESH MATERIALIZED VIEW CONCURRENTLY刷新期间读操作不被阻塞但要求物化视图上至少存在一个唯一索引否则 PostgreSQL 会直接报错// 并发刷新需要视图上有唯一索引 await em.refreshMaterializedView(AuthorStats, { concurrently: true });底层 SQL 生成见 packages/sql/src/dialects/postgresql/PostgreSqlSchemaHelper.tsoverride refreshMaterializedView(name: string, schema?: string, concurrently false): string { const concurrent concurrently ? concurrently : ; return refresh materialized view${concurrent} ${this.quote(this.getTableName(name, schema))}; }8.4 物化视图的限制与最佳实践仅限 PostgreSQL其他数据库使用view: { materialized: true }会直接报错无自动刷新MikroORM 不会自动刷新物化视图必须手动调用refreshMaterializedView()或依赖数据库侧的刷新机制触发器、定时任务等建议为常用查询列建立索引物化视图支持索引例如CREATE UNIQUE INDEX author_stats_id_idx ON author_stats_matview (id); CREATE INDEX author_stats_book_count_idx ON author_stats_matview (book_count);刷新策略取舍数据变化频繁的场景优先使用普通视图物化视图适合数据变化不频繁、可容忍短暂陈旧数据的场景生产环境优先并发刷新如果刷新期间仍需读取视图务必使用{ concurrently: true }前提是存在唯一索引监控视图体积物化视图占用磁盘空间大数据集需关注体积并考虑分区等策略。关于物化视图的完整细节含 Schema 生成 SQL 示例与最佳实践可进一步阅读 docs/docs/materialized-views.md。九、Schema 生成与迁移视图被完整追踪视图实体的一个重要价值是被 Schema Generator 与迁移机制完整管理。创建orm.schema.create()会生成CREATE VIEW普通视图或CREATE MATERIALIZED VIEW ... WITH DATA / WITH NO DATA物化视图更新orm.schema.update()会 diff 视图定义检测变化并重建视图——仓库中view-expression-diffing.postgres.test.ts与materialized-view-diffing.postgres.test.ts两个测试专门覆盖了视图表达式与物化视图的 diff 行为见 tests/features/schema-generator/删除orm.schema.drop()会生成DROP VIEW/DROP MATERIALIZED VIEW IF EXISTS ... CASCADE。在 packages/sql/src/schema/SqlSchemaGenerator.ts 中可以看到drop 时视图会优先于表被删除且按依赖关系倒序处理因为视图可能依赖表// Drop views first (views may depend on tables)。普通视图的CREATE VIEW语句在 Schema Helper 层生成例如 packages/sql/src/schema/SchemaHelper.ts 的create view ${viewName} as ${definition}物化视图的语句则在 PostgreSqlSchemaHelper.ts 中按withData拼出with data/with no data子句。此外MySQL 与 SQLite 方言还实现了视图定义的 introspectionSHOW CREATE VIEW等支持把数据库既有视图反向读取为元数据。十、常见问题与限制视图实体是只读的无法通过flush()持久化只能查询必须提供expressionview: true但没有expression时 ORM 初始化直接抛错见 view-entities.validation.test.ts部分数据库对可更新视图存在限制即使底层视图在 SQL 层面可更新ORM 层面依然保持只读语义物化视图仅 PostgreSQL 支持且需要手动刷新MongoDB 无视图概念请改用虚拟实体。结语视图实体把数据库视图这一传统能力无缝接入了 MikroORM 的实体体系view: true expression两个选项即可完成定义Schema Generator 负责建/删/diff迁移机制负责版本追踪QueryBuilder 表达式保证类型安全而物化视图则让 PostgreSQL 用户在报表与性能之间拥有了更灵活的选择。无论你是要复用遗留视图、封装复杂 JOIN还是为仪表盘预聚合数据视图实体都是比虚拟实体更重量级、也更适合长期沉淀查询逻辑的正确方案。赞分享后端【免费下载链接】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 View Entities 完全指南从 CREATE VIEW 到物化视图的实战详解MikroORM View Entities 完全指南从 CREATE VIEW 到物化视图的实战详解 导读 视图实体View Entity是 Mikro后端MikroORM View Entities 完全指南用 Schema Generator 管理真实数据库视图MikroORM View Entities 完全指南用 Schema Generator 管理真实数据库视图 视图实体View Entity是 Mikr后端MikroORM 物化视图Materialized Views实战指南从实体定义到并发刷新与 Schema 管理MikroORM 物化视图Materialized Views实战指南从实体定义到并发刷新与 Schema 管理 物化视图Materialized Vi后端上一篇鸣潮自动化工具ok-ww终极指南解放双手的智能游戏助手下一篇Buddy-MLIR与PyTorch集成教程轻松实现AI模型的硬件加速创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表