OEA 框架提供了多种方式来优化分布式数据查询的性能,本篇将会说明如何以声明 OEA 冗余属性的方式,来实现轻量级的数据冗余,以减少关联查询次数及网络数据传输量,提高分布式应用程序性能。

冗余属性功能说明


OEA 冗余属性在框架层面提供了一种易用的机制,把指定冗余路径的关系对象中的属性值复制到本对象中,以解决关联查询、关联数据量等性能问题。应用开发人员只需要简单的定义一个冗余属性,而框架会自动完成对冗余属性的赋值、更新操作。

ORM 中的 N+1 问题示例


在进销存示例中,采购订单的列表界面中,每一行采购订单都要显示它对应的供应商名称:

在不作任何优化处理的情况下,每一行订单数据的这个供应商名称值都会从它的关系中加载,即:PurchaseOrder.Supplier.Name,这样造成 “N+1” 问题,即两行数据查询了三次,分别是:

这样,如果列表中有 10 条数据就会查询 11 次。

编写冗余属性优化


对于 N+1 问题,OEA 之前提供了《聚合 SQL》的方式,来实现生成 Left Outer Join 查询一个大表,并直接加载整个聚合对象及它对应的关系。而现在我将用冗余属性把供应商名称冗余到采购订单表 PurchaseOrder 上。

目前,PurchaseOrder 采购订单表已经有了到供应商表(客户表) ClientInfo 的引用属性:

数据库结构如下:

那么,如果把它对应的供应商的名称冗余到 PurchaseOrder 表中呢?

其实,只要使用 OEA CodeSnippet 提供的代码段模板在 PurchaseOrder 上定义一个冗余属性 SupplierName 就行了,模板如下:

代码段中,需要编写属性类型、属性名称以及冗余路径。冗余路径即是从当前对象到目的属性的托管属性集合。供应商名称属性编写完成后,代码如下:

SupplierName 属性为只读,不需要应用层进行任何设置。框架自动完成属性值的赋值、更新。

RedundantPath 中的两个属性表示冗余的路径:即把当前订单的 Supplier.Name 属性值冗余到这个属性中。

然后,把这个属性显示在列表中,而把之前显示在列表中的引用属性设置为只显示在表单中:

这样,表格中看到的这个字段就是我们的冗余属性:

同时,数据库结构中也多了 SupplierName 这个字段:

由于是刚添加的冗余属性,所以历史数据还是 Null。此时,我们可以使用自动更新的功能来更新这些数据:到供应商编辑界面,把该行供应商的名称变更并保存,这样,由于它已经被冗余到 SupplierName 属性,所以这个冗余属性会被自动更新:

改名并保存:

冗余属性已经被更新:

再来试一试添加一个新的订单:

这样,采购订单在查询时,因为只是显示本表的数据,就不会再有因为对象关系而造成的 N+1 性能问题。

多级路径冗余


其实,细心的朋友可能在上面代码段的那张图中已经看出,冗余属性支持在路径中多级引用。例如,我们把供应商的客户类别的名称也冗余到订单表中:

界面生成:

设计


需求其实很简单,就是应用开发人员可以通过简单地声明冗余属性路径,把引用实体中的属性值冗余到本对象中。这儿的引用实体可以是一级的直接引用,例如上面讲的 PurchaseOrder 引用 ClientInfo;也可以是多级的间接引用,例如 PurchaseOrder 引用了 ClientInfo 作为它的 Supplier,而 ClientInfo 引用了 ClientCategory,这时 PurchaseOrder 也可以把 ClientCategory 中的 Name 属性冗余过来。当然了,可能还会有更多级别的引用。

另一方面,当被引用的实体的值改变时,所有该值的冗余属性的值也应该会被更新。当引用的关系发生变化时,同样需要触发更新操作。

基于 OEA 的托管属性架构,要实现一级引用变化的同时,更新内存中运行时对象相关的冗余属性,是比较简单的,在属性变更回调中处理即可。

所以,重点是实现冗余在数据库中的更新。这里要根据变化的情况,动态生成 SQL 去更新数据库中所有的冗余数据。经过分析,变化主要分为三种。以这个引用链接为例:D –> C –> B –> A,A 中存在属性 Name,D 中冗余了 D.C.B.A.Name 属性为 D.AName。那么它的变化可能是:

  1. 第一级引用属性被变更,即:D.C 属性值从 C1 被设置为新的 C 类型的对象 C2;
    对于这种情况,直接在内存中更新当前对象的值即可。
  2. 中间级引用属性被变更,即:被 D 引用的 C1 对象的 C.B 属性变更、或者 B.A 属性变更。
    这种情况下,需要生成如下的 SQL 执行:
    update D set AName = @AName where CId = @CId,
    update D set AName = @AName where CId in (
        select id from C where BId = @BId
    )
  3. 最终值变更,即:被 D 间接引用的 A1 对象的 Name 属性被变化。
    对应 SQL:
    update D set AName = @AName where CId in (
        select id from C where BId in (
            select id from B where AId = @AId
        )
    )

以上的设计,是基于“冗余属性不会再被其它的冗余属性冗余”的前提下才能起作用。本来想为多重冗余进行设计,但是考虑到场景不多,并不是很有必要,而且复杂度提升太高,所以决定暂时不支持多重冗余:

以下,给出相关的单元测试(以下测试基于上述引用链条:E->D->C->B->A.AName):

[TestMethod]
public void MPT_Redundancy_SetB()
{var a = new A { Name = "AName" };var b = new B { A = a };Assert.AreEqual(a.Name, b.AName);
}[TestMethod]
public void MPT_Redundancy_SetC()
{var a = new A { Name = "AName" };var b = new B { A = a };var c = new C { B = b };Assert.AreEqual(a.Name, c.AName);
}[TestMethod]
public void MPT_Redundancy_SetBOfC()
{var a1 = new A { Name = "a1" };var a2 = new A { Name = "a2" };var b1 = new B { A = a1, Id = 1 };var b2 = new B { A = a2, Id = 2 };var c = new C { B = b1 };Assert.AreEqual("a1", c.AName);c.B = b2;Assert.AreEqual("a2", c.AName);
}[TestMethod]
public void MPT_Redundancy_UpdateB()
{using (RF.TransactionScope(UnitTestEntity.DbSetting)){var a = new A { Name = "AName" };Save(a);var b = new B { A = a };Save(b);a.Name = "New Name";Save(a);var b2 = RF.Concreate<BRepository>().GetById(b.Id) as B;Assert.AreEqual("New Name", b2.AName);}
}[TestMethod]
public void MPT_Redundancy_UpdateC()
{using (RF.TransactionScope(UnitTestEntity.DbSetting)){var a = new A { Name = "AName" };Save(a);var b = new B { A = a };Save(b);var c = new C { B = b };Save(c);a.Name = "New Name";Save(a);var b2 = RF.Concreate<BRepository>().GetById(b.Id) as B;Assert.AreEqual("New Name", b2.AName);var c2 = RF.Concreate<CRepository>().GetById(c.Id) as C;Assert.AreEqual("New Name", c2.AName);}
}[TestMethod]
public void MPT_Redundancy_UpdateCByRefChanged()
{using (RF.TransactionScope(UnitTestEntity.DbSetting)){var a1 = new A { Name = "A1" };var a2 = new A { Name = "A2" };Save(a1, a2);var b = new B { A = a1 };Save(b);var c = new C { B = b };Save(c);Assert.AreEqual(c.AName, "A1");b.A = a2;Save(b);var cInDb = RF.Concreate<CRepository>().GetById(c.Id) as C;Assert.AreEqual(cInDb.AName, "A2");}
}[TestMethod]
public void MPT_Redundancy_UpdateDEByRefChanged()
{using (RF.TransactionScope(UnitTestEntity.DbSetting)){var a1 = new A { Name = "A1" };var a2 = new A { Name = "A2" };Save(a1, a2);var b = new B { A = a1 };Save(b);var c = new C { B = b };Save(c);var d = new D { C = c };Save(d);var e = new E { D = d, C = c };Save(e);Assert.AreEqual(d.AName, "A1");Assert.AreEqual(e.ANameFromDCBA, "A1");Assert.AreEqual(e.ANameFromCBA, "A1");b.A = a2;Save(b);var dInDb = RF.Concreate<DRepository>().GetById(d.Id) as D;Assert.AreEqual(dInDb.AName, "A2");var eInDb = RF.Concreate<ERepository>().GetById(e.Id) as E;Assert.AreEqual(eInDb.ANameFromDCBA, "A2");Assert.AreEqual(eInDb.ANameFromCBA, "A2");}
}

在实现上,冗余属性被设计为 OEA 实体框架层中,作为实体框架在托管属性框架上的扩展,而并没有内置到托管属性框架中。选用一般的托管属性作为冗余属性的实现,在属性变更处理中扩展并调用相关处理方法。虽然作为一般属性,冗余属性也可以被设置值,但是在应用开发时,我们不要去提供 CLR 属性的设置器。这样,简单地表达了冗余属性只读、框架自动设置的思想。

小结


因为 N+1 问题最常见的场景就只是显示一个关联对象的名称、编码等一般属性。所以在解决自动更新冗余属性之后,冗余属性可以被用来解决许多常见的关联问题。应用开发人员在使用时,只需要简单地声明一个属性,并把它映射到数据库就行了。

PS:冗余属性的相关代码目前还没有提交到开源服务器上,待下次更新时大家才能获取到。

冗余属性的设计,说到底还是为了解决 N+1 查询问题,而这个问题是 ORM 框架都必须面对的。我发现从一开始写数据库应用程序到现在,几年来,一直战斗在 ORM 第一线,累啊~

OEA ORM 框架中的冗余属性设计相关推荐

  1. ORM 框架中SQLALCHEMY一点点个人总结

    声明:工作原因涉及一点 What is ORM 答: object relation mapping 一种实现对象与数据库中的关系表映射的中间件.ORM 框架中最有名的是SQLALCHEMY 具体使用 ...

  2. python orm框架sqlalchemy_python orm 框架中sqlalchemy用法实例详解

    本文实例讲述了python orm 框架中sqlalchemy用法.分享给大家供大家参考,具体如下: 一.ORM简介 1. ORM(Object-Relational Mapping,对象关系映射): ...

  3. Hadoop精华问答 | Hadoop框架中最核心的设计是什么?

    Hadoop能够进行大批量数据的离线处理,但是在实时计算上的表现实在是不尽如人意;而Storm就可以担当这部分的角色,今天,就让我们看看关于Storm的精华问答吧. 1 Q:hadoop发展史 A: ...

  4. Hadoop框架中最核心的设计是什么?

    A.为海量数据提供存储的HDFS和对数据进行计算的MapReduce B.提供整个HDFS文件系统的NameSpace(命名空间)管理.块管理等所有服务 C.Hadoop不仅可以运行在企业内部的集群中 ...

  5. bootstrap框架中data-xxx 的属性

    这个是自定义属性.自己写的,通过js代码执行对应的操作.也就可以实现相应的插件效果.

  6. OEA ORM中的分页支持

    本篇博客主要描述分页的常见技术方案,以及在 OEA 框架中的分页的应用及实现原理. 分页的几种方案 分页是解决大数据量显示的有效方法.根据分页技术应用的位置不同,大致可以把分页分为以下几种: 界面层分 ...

  7. Django框架(3.django设计模型类、模型类生成表、ORM框架简介)

    ORM框架简介 O是object,也就类对象的意思, R是relation,翻译成中文是关系,也就是关系数据库中数据表的意思, M是mapping,是映射的意思.在ORM框架中,它帮我们把类和数据表进 ...

  8. 开源XDesigner ORM 框架设计

    XDesigner ORM 框架设计 袁永福 2011-01-20 最新版本源代码下载地址 http://files.cnblogs.com/xdesigner/XDesignerORM.zip . ...

  9. java orm设计_大搜车orm框架设计思路

    orm基本概念 ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和 ...

  10. Spring框架中级联赋值(外部属性注入)以及内部属性注入

    Spring框架中级联赋值(外部属性注入)以及内部属性注入 前言 级联赋值 1.对上述外部`Bean`配置文件进行修改: 2.级联赋值第二种写法 内部`bean`属性注入 前言 Spring框架中存在 ...

最新文章

  1. mysql filter_MySQL 过滤复制+复制映射 配置方法
  2. 图解Transformer(完整版)!
  3. linux 线程操作问题undefined reference to ‘pthread_create‘的解决办法(cmake)
  4. linux centos7修改默认启动的内核(升级及切换内核)
  5. file_get_contents()采集不到原因
  6. 吃货阶段01 类的定义 方法的布局 0925
  7. mysql5.6+master+date_MySQL5.6的4个自带库详解
  8. 你们心心念念的 GitHub 客户端终于来了!
  9. 齿轮计算机在线,齿轮参数计算器(萝卜花齿轮计算工具)9.5 中
  10. 右脑记忆法的个人理解
  11. 学python-当当发布2020程序员新态:左手Python,右手机器学习
  12. 点是否在三角形内——C++实现
  13. python中创建requirement.txt
  14. 【数据竞赛】风控实操案例 | 基于Xgboost与Catboost实现非法集资企业识别
  15. 计算机耍人,抖音中的连环套路耍人问题
  16. 开源资产管理软件—OCS Inventory NG+ GLPI 系统安装配置UTF-8版 支持中文
  17. 最全的项目部署+持续集成解决方案:Jenkins + git + docker
  18. windows server 2012 RD服务器
  19. 跨时钟域(CDC)优秀文章汇总-持续收集
  20. QQ游戏对对碰辅助程序

热门文章

  1. 数据挖掘十大算法-决策树的实现
  2. 消息中间件的研究 (一)
  3. 【linux基础】cuDNN版本查询
  4. RF工具ride使用
  5. 自动化运维工具SaltStack
  6. 提取已有的内核配置文件
  7. R语言data manipulation学习笔记之创建变量、重命名、数据融合
  8. Ubuntu18.04 安装 Idea 2018.2
  9. Web Clip 图片变淡变浅变灰解决方案
  10. WebApi 基于token的多平台身份认证架构设计