深度分页问题优化方案

问题原因

Mysql使用select * from table limit offset, rows分页在深度分页的情况下, 性能急剧下降。

例如:select * 的情况下直接⽤limit 600000,10 扫描的是约60万条数据,并且是需要回表 60W次,也就是说⼤部分性能都耗在随机访问上,到头来只⽤到10条数据(总共取600010条数据只留10条记录)

优化方案

前端方案:业务层面限制跨度比较大的跳页

提供2种风格分页器供用户选择

  1. 标准分页器,展示最后一页和跳转指定页输入框
    image.png
  2. 简单分页器
    image.png

参考
百度方案: 不展示最后一页和直接跳转指定分页的输入框
image.png
Google方案:只展示查看下一页的按钮
image.png

界面设计器选表格/画廊的属性面板提供分页器风格的属性下拉选择

image.png

xml示例
<!-- 表格使用的标准分页器 --> <view type="TABLE" paginationStyle="SIMPLE"> <!-- fields --> </view> <!-- 画廊使用默认的标准分页器 --> <view type="GALLERY" paginationStyle="STANDARD"> <!-- fields --> </view>

后端方案

  1. 使用索引:确保数据库表中的相关字段上创建了适当的索引。索引可以加快查询速度,特别是在处理大数据量时。

  2. 分批查询:将大数据分成多个较小的批次进行查询,而不是一次性查询全部数据。可以通过限制每次查询的数据量和使用合适的偏移量来实现分批查询,例如使用LIMIT和OFFSET子句。

  3. 基于游标的分页:使用基于游标的分页技术,而不是传统的偏移分页。游标分页是通过记录上一次查询的游标位置,在下一次查询时从该位置开始获取新的数据,避免了大偏移量的影响。这可以通过数据库自身的功能(例如MySQL的CURSOR)或使用第三方库来实现。

  4. 缓存数据:如果数据变化较少,可以考虑将查询结果缓存到内存中,以避免频繁地查询数据库。这样可以提高页面相应速度,并减轻数据库负担。缓存的数据应该根据业务需要及时更新。

  5. 数据预处理:如果查询结果经常需要进行复杂的计算或处理,可以考虑提前对数据进行预处理并缓存结果,以减少每次查询的计算负担。

  6. 数据库优化:针对具体数据库系统,可以根据实际情况进行数据库调优。例如,合理设置数据库连接池大小、调整数据库参数等。

  7. 分布式存储和计算:对于非关系型数据库或分布式存储系统,可以考虑使用分布式存储和计算方案,将数据分散存储在多个节点上,并通过计算节点并行处理查询请求,以提高性能和可伸缩性。

参考链接

MySQL深分页场景下的性能优化

Oinone社区 作者:原创文章,如若转载,请注明出处:https://doc.oinone.top/other/75.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

Like (0)
's avatar
Previous 2023年6月20日 pm4:07
Next 2023年11月2日 pm1:58

相关推荐

  • 【界面设计器】模型增删改查基础

    阅读之前 你应该: 已搭建包含界面设计器的基础环境。 对平台元数据有基本了解和认识。(如果不是特别清楚也没关系) 常用元数据简介 平台中包含了许多元数据,以这些元数据为基础,可以较为完整的描述了一个【应用】或【模块】的存储结构以及用户交互。 图解 “` mermaidgraph TDModule —> DataDictionaryModule —> Model Model —> FieldModel —> Function1(Function)Model —> Action Action —> ViewAction —> ViewAction —> ServerAction —> Function2(Function)Action —> UrlActionAction —> ClientAction“` 概念解释 模块(Module):用于定义元数据的作用域。 数据字典(DataDictionary):固定数据可选项。 模型(Model):用于描述一个具体对象的集合。 字段(Field):模型中字段,用于描述对象属性。 函数(Function):模型中的函数,用于定义对象的服务端行为。 动作(Action):模型中的动作,用于定义对象的交互行为。具体表现为以下四个行为。 跳转动作(ViewAction):用于视图与视图之间的跳转。 视图(View):用于定义主内容分发区的页面内容。 服务端动作(ServerAction):用于客户端调用服务端行为。特殊的,当函数被作为API级别开放时,客户端也可以直接调用。 链接动作(UrlAction):用于执行浏览器超链接行为。 客户端动作(ClientAction):用于自定义客户端动作。 其他概念解释 应用(Application):特殊的模块,可以被用户访问的模块。 模块依赖(ModuleDependencies):模块与模块之间具备的一种关系,使得模块可以使用被依赖模块的相关元数据。 场景描述 在以下演示过程中,我们将通过【演示模块】这一应用来引导大家完成【商品】模型的增删改查操作。并根据实际应用场景,为大家提供模型增删改查的最佳实践。 创建第一个无代码应用 (基于低代码开发的应用,可以跳过这一步骤。) 登录进入系统后,将可以按照如下步骤进行操作。(为了方便演示和体验,可直接使用admin用户进入系统) 切换应用到应用中心 创建第一个应用 至此,你已经创建了一个【演示应用】,用于接下来的操作。 创建第一个无代码模型 (基于低代码开发的模型,可以跳过这一步骤。) 切换应用到模型设计器 在模型设计器切换视图为表模式 模型设计器具有表模式和图模式两种模型展示形式,表模式主要用于单个模型的管理,图模式主要用于查看多个模型之间的关联关系。 在这里我们使用表模式。 在模型设计器切换模块到【演示模块】 创建一个存储模型【商品】 在无代码模式中,只能选择【存储模型】和【传输模型】,其代表的含义为: 存储模型:创建模型时同步创建数据库表,具备数据存储能力。 传输模型:仅创建模型,不创建数据库表,不具备数据存储能力,仅作用户交互使用。 创建模型时,一定会继承一个父模型,在模型设计器创建模型都将默认继承【基础存储模型】,该模型中默认包含【编码】和【名称】两个业务字段,以及【ID】、【创建人ID】、【更新人ID】、【创建时间】、【更新时间】五个系统字段。 在专家模式中,可以查看模型的父模型进行查看,也可以在创建模型时修改父模型。这一功能在这里不进行演示。 在下面的操作中,我们仅需使用【编码】和【名称】这两个业务字段来创建相关视图。 创建第一个表格视图 切换应用到界面设计器 选择应用和模型 这里为了方便查看,我们可以使用【全部视图类型】。 创建表格视图 输入【页面标题】,修改【视图类型】为【表格】,直接点击确定即可。会自动进入【页面设计】。 页面设计 设计表格视图 从【组件库】-【模型】中将【编码】和【名称】两个【低代码字段】拖入【表格区】 小贴士: 在左侧组件库可以切换【组件】和【模型】。【组件】用于创建和选择字段,【模型】用于直接使用现有字段。 在模型面板可以查看当前选中字段所在模型。如未选择时,为当前视图模型。 对于存储模型而言,创建字段应该是一个谨慎的操作,在学习阶段尽可能从【模型】中使用现有字段。除非有创建新字段的需求。 大家可以自由发挥,将【编码】和【名称】拖入【列表搜索区】即可使用这两个字段进行搜索。单行文本默认使用模糊搜索。 为表格提供【创建商品】功能 从【组件库】-【组件】中将【跳转动作】组件拖入【动作区】,并填写相关的元数据信息,按图示输入完成后,点击保存即可。(具体解释在文章下方) 这里会创建一个【表单】视图,并绑定至当前动作。设计完表格视图后,我们需要设计表单视图。 为表格提供【编辑商品】功能 从【组件库】-【组件】中将【跳转动作】组件拖入【行内动作区】,并填写相关的元数据信息,按图示输入完成后,点击保存即可。 为表格提供【删除商品】功能 从【组件库】-【模型】中将【删除】动作分别拖入【动作区】和【行内动作区】即可。 将表格绑定到菜单,可以让用户访问 使用【显示母版】可以查看当前页面在母版中的预览效果 添加菜单项【商品管理】并【绑定当前视图】 发布表格视图,并返回列表页 至此,我们已完成了【表格】视图的基本功能设计。 接下来,我们需要设计刚刚在【创建商品】步骤时创建的【表单】视图。点击【商品表单】卡片任意区域进入【页面设计】。 设计表单视图 从【组件库】-【模型】中将【编码】和【名称】两个【低代码字段】拖入【表单区】 为表单提供【创建/更新】功能,使得表单可以提交数据到服务端 从【组件库】-【模型】中将【创建】动作拖入【动作区】 将【创建】动作的【是否隐藏】属性改为【条件隐藏】并设置表达式 从【组件库】-【模型】中将【更新】动作拖入【动作区】 将【更新】动作的【是否隐藏】属性改为【条件隐藏】并设置表达式 为表单提供【返回】功能 将【组件】-【客户端动作】拖入【动作区】添加【返回】功能 发布表单视图,并返回列表页 (同表格视图发布) 切换应用到应用中心,为应用设置首页 使用绑定菜单,将【商品管理】菜单设置为【演示模块】的应用首页。 切换应用到演示模块,查看刚刚设计的视图以及体验增删改查基本功能 至此,一个模型的全部增删改查功能已进行完整演示。下面将对上述步骤中可能产生的疑问点进行解释说明。 跳转动作元数据配置 动作名称:跳转动作的默认显示名称 数据控制类型:用于动作点击后的数据行为控制。 处理单条数据:携带当前可用数据的一行到目标页面中,仅一行,如果勾选了多行也仅有首行(首行按勾选顺序决定)。 处理多条数据:携带当前可用数据的多行到目标页面中,至少是两行。 处理单条或多条数据:携带当前可用数据的多行到目标页面中,至少是一行。 不进行数据处理:不携带任何数据到目标页面。 打开方式:用于动作点击后的跳转行为控制。 当前窗口打开:基本的页面跳转,不打开新的浏览器窗口或标签页。 新窗口打开:打开新的浏览器窗口或标签页。特殊的,使用了平台内置的多标签页时,将不再打开新的浏览器窗口或标签页,仅会在多标签页中添加一个新的标签页。 弹窗打开:使用弹窗打开,并渲染目标页面。 抽屉打开:使用抽屉打开,并渲染目标页面。 页面内容:界面设计器交互属性。 绑定已有视图:从平台中已存在的视图中选择视图作为目标页面。 创建新页面:输入视图相关信息,创建新视图并绑定到当前动作作为目标页面。 绑定页面:【绑定已有视图】时用于选择视图。 页面标题:【创建新页面】时使用的页面信息,与【界面设计器】中【创建视图】的【页面标题】一致。 页面模型:【创建新页面】时使用的页面信息,与【界面设计器】中【创建视图】的【模型】一致。 业务类型:【创建新页面】时使用的页面信息,与【界面设计器】中【创建视图】的【业务类型】一致。 视图类型:【创建新页面】时使用的页面信息,与【界面设计器】中【创建视图】的【视图类型】一致。 操作栏位置:【创建新页面】时使用的页面信息,与【界面设计器】中【创建视图】的【操作栏位置】一致。 禁用数据加载:打开目标页面时不发起服务端查询。 加载函数:打开目标页面时使用指定的加载函数发起服务端查询。 客户端动作元数据配置 动作名称:客户端动作的默认显示名称 客户端行为:平台内置的客户端行为 数据校验:同提交动作的数据校验功能 返回上一页/关闭弹窗/关闭抽屉 刷新数据:同提交动作的刷新当前视图功能 批量更新:在批量动作打开的弹窗中使用的特殊客户端动作 删除数据:在一对多(O2M)和多对多(M2M)使用表格组件时使用的删除功能 Q/A 跳转动作的数据控制类型该如何选择? 通常情况下,我们可以通过目标页面是否需要携带数据以及携带的数据行数,来决定数据控制类型。 在演示内容中,从【表格】视图通过【创建】按钮跳转到表单时,通常我们不需要任何数据,因此我们选择【不进行数据处理】来实现这一目的。从【表格】视图通过【编辑】按钮跳转到表单时,通常我们在行内进行操作,并且我们一次只能编辑一行数据,因此我们选择【处理单条数据】。 【组件库】-【组件】中的动作和【组件库】-【模型】中的动作有什么区别? 【组件库】-【组件】中的动作拖入页面时,会创建一个对应的动作,因此需要输入元数据相关信息。【组件库】-【模型】中的动作为当前模型中的【服务端动作】,因此拖入页面后无需输入元数据相关信息。 【删除】动作为什么可以显示在两个区域? 在模型中内置的【删除】动作,其数据类型为【处理单条或多条数据】,因此可以在表格的【动作区】和【行内动作区】各放置一个。不仅如此,所有的数据类型为【处理单条或多条数据】的【服务端动作】都可以这样做。 表格的【创建】和【编辑】为什么一个使用【创建新页面】,另一个使用【绑定已有视图】? 在新增【创建】这个【跳转动作】时,由于我们在当前模型下没有一个【表单】视图可以直接使用,因此在这里我们使用【创建新页面】的方式直接创建【表单】视图,并绑定至当前动作。 在新增【编辑】这个【跳转动作】时,由于我们在上一步骤已经创建了一个可以直接使用的【表单】视图,因此在这里我们使用【绑定已有视图】。 在演示内容中,我们假设【创建商品】和【编辑商品】使用的【表单】视图可以通过【是否隐藏】属性进行简单区分,因此采取了这样的演示操作。根据我们实践经验,在大多数场景中也的确如此。 表单的【创建】和【更新】配置的【条件隐藏】具有怎样的含义? 在平台任何存储模型/代理模型(不包含中间模型)中,都具备ID这一系统字段。通过【不进行数据处理】的跳转动作跳转至【表单】视图时,ID不会在跳转时进行传递,因此在【表单】中的ID字段为空。通过【处理单条数据】的跳转动作跳转至【表单】视图时,ID会在跳转时进行传递(如URL上可以看到id属性),因此在【表单】中的ID字段不空。 根据这一特性,我们就可以通过【条件隐藏】区分当前这个视图的业务功能。 表单的【创建】可以通过当前视图数据.ID非空进行隐藏,即表示当前ID非空时隐藏(当前ID为空时显示)。 表单的【更新】可以通过当前视图数据.ID为空进行隐藏,即表示当前ID为空时隐藏(当前ID不空时显示)。

    2023年11月1日
    8.7K00
  • 4.1.3 模块之生命周期

    了解oinone的启动生命周期过程,对于理解oinone或者开发高级功能都有非常大的帮助 一、生命周期大图 图4-1-3-1 生命周期大图 二、平台扩展说明 平台节点通过SPI机制进行扩展,本书籍暂不展开,更多详情请见可关注数式Oinone公众号中的Oinone内核揭秘系列文章。 三、业务扩展说明 接口 说明 使用场景 LifecycleBeginAllInit 系统进入生命周期前置逻辑注:不能有任何数据库操作 系统级别的信息收集上报 LifecycleCompletedAllInit 系统生命周期完结后置逻辑 系统级别的信息收集上报、生命周期过程中的数据或上下文清理 LifecycleBeginInit 模块进入生命周期前置逻辑注:不能有任何数据库操作 预留,能做的事情比较少 LifecycleCompletedInit 模块生命周期完结后置逻辑 本模块需等待其他模块初始化完毕以后进行初始化的逻辑。比如:1.集成模块的初始化2.权限缓存的初始化…… MetaDataEditor 元数据编辑注:不能有任何数据库操作 这个在第3章Oinone的基础入门中已经多次提及,核心场景是向系统主动注册如Action、Menu、View等元数据 ExtendBuildInit 系统构建前置处理逻辑 预留,能做的事情比较少,做一些跟模块无关的事情 ExtendAfterBuilderInit 系统构建后置处理逻辑 预留,能做的事情比较少,做一些跟模块无关的事情 InstallDataInit 模块在初次安装时的初始化逻辑 根据模块启动指令来进行选择执行逻辑,一般用于初始化业务数据。应用启动参数与指令转化逻辑详见4.1.2【模块之启动指令】一文 UpgradeDataInit 模块在升级时的初始化逻辑注:根据启动指令来执行,是否执行一次业务自己控制 ReloadDataInit 模块在重启时的初始化逻辑注:根据启动指令来执行,是否执行一次业务自己控制 表4-1-3-1 业务拓展说明 四、常用生命周期举例 Install\Upgrade\Reload的业务初始化(举例) Step1 新建DemoModuleBizInit DemoModuleBizInit实现InstallDataInit, UpgradeDataInit, ReloadDataInit a. InstallDataInit 对应 init b. UpgradeDataInit 对应 upgrade c. ReloadDataInit 对应 reload modules方法代表改初始化类与哪些模块匹配,以模块编码为准 priority 执行优先级 package pro.shushi.pamirs.demo.core.init; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCommand; import pro.shushi.pamirs.boot.common.api.init.InstallDataInit; import pro.shushi.pamirs.boot.common.api.init.ReloadDataInit; import pro.shushi.pamirs.boot.common.api.init.UpgradeDataInit; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.api.enumeration.DemoExpEnumerate; import pro.shushi.pamirs.meta.common.exception.PamirsException; import java.util.Collections; import java.util.List; @Component public class DemoModuleBizInit implements InstallDataInit, UpgradeDataInit, ReloadDataInit { @Override public boolean init(AppLifecycleCommand command, String version) { throw PamirsException.construct(DemoExpEnumerate.SYSTEM_ERROR).appendMsg("DemoModuleBizInit: install").errThrow(); //安装指令执行逻辑 // return Boolean.TRUE; } @Override public boolean reload(AppLifecycleCommand command, String version) { throw PamirsException.construct(DemoExpEnumerate.SYSTEM_ERROR).appendMsg("DemoModuleBizInit: reload").errThrow(); //重启指令执行逻辑 // return Boolean.TRUE; } @Override public boolean upgrade(AppLifecycleCommand command, String version, String existVersion) { throw PamirsException.construct(DemoExpEnumerate.SYSTEM_ERROR).appendMsg("DemoModuleBizInit: upgrade").errThrow(); //升级指令执行逻辑 // return Boolean.TRUE; } @Override public List<String> modules() { return Collections.singletonList(DemoModule.MODULE_MODULE); } @Override public int priority() { return 0; } } 图4-1-3-2 新建DemoModuleBizInit Step2 重启看效果 启动指令为-Plifecycle=INSTALL,转化指令为 install为AUTO;upgrade为FORCE 因为DemoModule我们已经执行过好多次了,所以会进入upgrade逻辑。系统重启的效果跟我们预期的结果一致,确实执行了DemoModuleBizInit的upgrade方法 图4-1-3-3 系统重启执行DemoModuleBizInit的upgrade方法 MetaDataEditor 回顾使用情况 最早在3.3.2【模型的类型】一文中介绍“传输模型”时,初始化ViewAction窗口动作时使用到,这里不过多介绍。下面主要介绍下InitializationUtil的工具类包含方法。 注:模块上报元数据只能通过注解或者实现MetaDataEditor接口并使用InitializationUtil工具来进行,更建议用注解方式

    2024年5月23日
    1.8K00
  • 3.3.4 模型的继承

    在我们的很多项目中,客户都是有个性化需求的,就像我们不能找到两件一模一样的东西,何况是企业的经营与管理思路,多少都会有差异。常规的方式只能去修改标准产品的逻辑来适配客户的需求。导致后续标品维护非常困难。而在介绍完这节以后是不是让你更加清晰认知到我们2.4.2【oinone独特性之每一个需求都可以是一个模块】一文中所表达的特性带来的好处呢? 一、继承方式 继承方式可以分为五种: 抽象基类ABSTRACT,只保存不希望为每个子模型重复键入的信息的模型,抽象基类模型不生成数据表存储数据,只供其他模型继承模型可继承域使用,抽象基类可以继承抽象基类。 扩展继承EXTENDS,子模型与父模型的数据表相同,子模型继承父模型的字段与函数。存储模型之间的继承默认为扩展继承。 多表继承MULTI_TABLE,父模型不变,子模型获得父模型的可继承域生成新的模型;父子模型不同表,子模型会建立与父模型的一对一关联关系字段(而不是交叉表),使用主键关联,同时子模型会通过一对一关联关系引用父模型的所有字段。多表继承父模型需要使用@Model.MultiTable来标识,子模型需要使用@Model.MultiTableInherited来标识。 代理继承PROXY,为原始模型创建代理,可以增删改查代理模型的实体数据,就像使用原始(非代理)模型一样。不同之处在于代理继承并不关注更改字段,可以更改代理中的元信息、函数和动作,而无需更改原始内容。一个代理模型必须仅能继承一个非抽象模型类。一个代理模型可以继承任意数量的没有定义任何模型字段的抽象模型类。一个代理模型也可以继承任意数量继承相同父类的代理模型。 临时继承TRANSIENT,将父模型作为传输模型使用,并可以添加传输字段。 二、继承约束 通用约束 对于扩展继承,查询的时候,父模型只能查询到父模型字段的数据,子模型可以查询出父模型及子模型的字段数据(因为派生关系所以子模型复刻了一份父模型的字段到子模型中)。 系统不会为抽象基类创建实际的数据库表,它们也没有默认的数据管理器,不能被实例化也无法直接保存,它们就是用来被继承的。抽象基类完全就是用来保存子模型们共有的内容部分,达到重用的目的。当它们被继承时,它们的字段会全部复制到子模型中。 系统不支持非jar包依赖模型的继承。 多表继承具有阻断效应,子模型无法继承多表继承父模型的存储父模型的字段,需要使用@Model.Advanced注解的inherited属性显示声明继承父模型的父模型。但是可以继承多表继承父模型的抽象父模型的字段。 可以使用@Model.Advanced的unInheritedFields和unInheritedFunctions属性设置不从父类继承的字段和函数。 跨模块继承约束 如果模型间的继承是跨模块继承,应该与模型所属模块建立依赖关系;如果模块间有互斥关系,则不允许建立模块依赖关系,同理模型间也不允许存在继承关系。 跨模块代理继承,对代理模型的非inJvm函数调用将使用远程调用方式;跨模块扩展(同表)继承将使用本地调用方式,如果是数据管理器函数,将直连数据源。 模型类型与继承约束 抽象模型可继承:抽象模型(Abstract) 临时模型可继承:抽象模型(Abstract)、传输模型(Transient) 存储模型可继承:抽象模型(Abstract)、存储模型(Store)、存储模型(多表,Multi-table Store),不可继承多个Store或Multi-table Store 多表存储模型(父)可继承:同扩展继承 多表存储模型(子)在继承单个Multi-table Store后可继承:抽象模型(Abstract)、存储模型(Store),不可继承多个Store 代理模型可继承: 抽象模型(Abstract),须搭配继承Store、Multi-table Store或Proxy 存储模型(Store),不可继承多个Store或Multi-table Store 存储模型(多表,Multi-table Store),不可继承多个Store或Multi-table Store 代理模型(Proxy),可继承多个Proxy,但多个父Proxy须继承自同一个Store或Multi-table Store,且不能再继承其他Store或Multi-table Store 同名字段以模型自身字段为有效配置,若模型自身不存在该字段,继承字段以第一个加载的字段为有效配置,所以在多重继承的情况下,未避免继承同名父模型字段的不确定性,在自身模型配置同名字段来确定生效配置。 三、继承的使用场景 模型的继承可以继承父模型的元信息、字段、数据管理器和函数 抽象基类 解决公用字段问题 扩展继承 解决开放封闭原则、跨模块扩展等问题 多表继承 解决多型派生类字段差异问题和前端多存储模型组合外观问题 代理继承 解决同一模型在不同场景下的多态问题(一表多态) 临时继承 解决使用现有模型进行数据传输问题 举例,前端多存储模型组合外观问题可通过多表继承的子模型,并一对一关联到关联模型,同时使用排除继承字段去掉不需要继承的字段。子模型通过默认模型管理器提供查询功能给前端,默认查询会查询子模型数据列表并在列表行内根据一对一关系查出关联模型数据合并,关联模型数据展现形态在行内是平铺还是折叠,在详情是分组还是选项卡可以自定义view进行配置 扩展继承 父子同表,模型在所有场景都有一致化的表现,意味着原模型被扩展成了新模型,父子模型的表名一致,模型编码不同,可覆盖父模型的模型管理器、数据排序规则、函数 多表继承 父子多表,父子间有隐式一对一关系,即父子模型都增加了一对一关联关系字段,同时父模型的字段被引用到子模型,且引用字段为只读字段,意味着子模型不可以直接更改父模型的字段值,子模型不继承父模型的模型管理器、数据排序规则、函数,子模型拥有自己的默认模型管理器、数据排序规则、函数。多表继承具有阻断效应,子模型无法自动多表继承父模型的存储父模型,需要显式声明多表继承父模型的存储父模型。 代理继承 代理模型继承并可覆盖父模型的模型管理器、数据排序规则、函数,同时可以使用排除继承字段和函数来达到不同场景不同视觉交互的效果。 图3-3-4-1 继承的使用场景 四、抽象基类(举例) 参考前文中3.3.2【模型的类型】一文中关于抽象模型的介绍 五、多表继承(举例) 场景设计如下 图3-3-4-2 多表继承设计场景 Step1 新建宠物品种、宠狗品种和萌猫品种模型 新建宠物品种模型,用@Model.MultiTable(typeField = "kind"),申明为可多表继承父类,typeField指定为kind字段 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.IdModel; @Model.MultiTable(typeField = "kind") @Model.model(PetType.MODEL_MODEL) @Model(displayName="品种",labelFields = {"name"}) public class PetType extends IdModel { public static final String MODEL_MODEL="demo.PetType"; @Field(displayName = "品种名") private String name; @Field(displayName = "宠物分类") private String kind; } 图3-3-4-3 多表继承示例代码 新建宠狗品种模型,用@Model.MultiTableInherited(type = PetDogType.KIND_DOG),申明以多表继承模式继承PetType,覆盖kind字段(用defaultValue设置默认值,用invisible = true设置为前端不展示),更多模块元数据以及模型字段元数据配置详见4.1.6【模型之元数据详解】一文 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; @Model.MultiTableInherited(type = PetDogType.KIND_DOG) @Model.model(PetDogType.MODEL_MODEL) @Model(displayName="宠狗品种",labelFields = {"name"}) public class PetDogType extends PetType { public static final String MODEL_MODEL="demo.PetDogType"; public static final String KIND_DOG="DOG"; @Field(displayName = "宠物分类",defaultValue = PetDogType.KIND_DOG,invisible = true) private String kind; } 图3-3-4-4 多表继承示例代码 新建萌猫品种模型,用@Model.MultiTableInherited(type = PetCatType.KIND_CAT),申明以多表继承模式继承PetType,覆盖kind字段(用defaultValue设置默认值,用invisible = true设置为前端不展示),并新增一个CatShapeEnum枚举类型的字段shape package pro.shushi.pamirs.demo.api.enumeration; import pro.shushi.pamirs.meta.annotation.Dict; import pro.shushi.pamirs.meta.common.enmu.BaseEnum; @Dict(dictionary = CatShapeEnum.DICTIONARY,displayName = "萌猫体型") public class CatShapeEnum extends BaseEnum<CatShapeEnum,Integer>…

    2024年5月23日
    1.9K00
  • 2.4.2 Oinone独特性之每一个需求都可以是一个模块

    我们的Oinone平台采用模型驱动的方式,并符合面向对象设计原则,每个需求都可以是一个独立模块,可以独立安装、升级和卸载。这让系统真正像乐高积木一样搭建,具有高度的灵活性和可维护性。 与大部分低代码或无代码平台不同的是,它们的应用市场上的应用往往是模板式的,也就是说,这是一个拷贝,个性化只能在应用上直接修改,而且一旦修改就不能升级。这对于软件公司和客户来说都非常痛苦。客户无法享受到软件公司产品的升级功能,而软件公司在服务大量客户时,也会面临不同版本的维护问题,成本也非常高。而我们的Oinone平台完全避免了这些问题,让客户和软件公司都可以从中受益(如下图2-9、2-10所示)。 图2-9软件公司与客户项目的关系-让标准与个性化共存 图2-10 软件公司与客户项目的关系-让升级无忧 实现原理 在满足客户个性化定制需求时,传统的方法通常是直接修改标准产品源码,但这样做会带来一个问题:标准产品无法持续升级。相反,无论是在OP模式还是SaaS模式下,Oinone都采用全新的模块为客户进行个性化开发,保持标准产品和个性化模块的独立维护和升级。这是因为在元数据设计时,Oinone采用了面向对象的设计原则,实现了元数据设计与面向对象设计思想的完美融合。 面向对象设计的核心特征包括封装、继承、多态,而Oinone的元数据设计完全融入了这些思想。下面是几个例子,说明Oinone的元数据设计如何体现面向对象设计的核心特征,并带来了什么好处: 继承:在继承原有模型的字段、逻辑、展示的情况下,增加一段代码来扩展模型的字段、逻辑、展示。 多态:在继承原有模型的字段、逻辑、展示的情况下,增加一段代码来覆盖模型的原有字段、逻辑、展示。 封装:外部无需关心模型内部如何实现,只需按照不同场景调用模型对应开放级别的字段、逻辑、展示。 这些特征和优势使得Oinone在满足客户个性化需求时更加灵活和可持续,同时使得标准产品的维护和升级变得更加容易和高效。 在Java语言设计中,万物皆对象,一切都以对象为基础。而Oinone的元数据设计则是以模型为出发点,作为数据和行为的承载体。如下图2-11清晰地描述了Java面向对象编程中封装、继承、多态在Oinone元数据中的对应关系。Oinone元数据描述了B对象继承A对象并拥有其所有属性和方法,并覆盖了A对象的属性1和方法1,同时新增了属性3和方法3。 此外,Oinone的面向对象特性是用元数据来描述的。一方面,我们基于Java编码规范收集相关元数据,以保持不改变Java编程习惯。另一方面,方法和对象的挂载是松耦合的,只要按照元数据规范进行挂载,就能轻松地将其附加到模型上。在不改变原有A对象的情况下,我们可以直接增加方法和属性(如下图2-12所示)。 图2-11 java面向对象在Oinone元数据中对应 图2-12 java对象的修改 VS Oinone元数据模型的修改 Oinone函数不仅支持面向对象的继承和多态特性,还提供了面向切面的拦截器和SPI机制的扩展点,以应对方法逻辑的覆盖和扩展,以及系统层面的逻辑扩展(如下图2-13所示)。这些扩展功能可以独立地在模块中维护。 其中,拦截器可以在不侵入函数逻辑的情况下,根据优先级为满足条件的函数添加执行前和执行后的逻辑。 扩展点是一种类似于SPI机制的逻辑扩展机制,用于扩展函数的逻辑。通过这一机制,可以对函数逻辑进行灵活的扩展,以满足不同的业务需求。 图2-13 Oinone函数拦截与扩展机制 不管是对象、属性还是方法,都可以以独立的模块方式来扩展,这就使得每一个需求都可以成为一个独立的模块,方便我们在研发标准产品时进行模块化的划分,同时也让我们在以低代码模式为客户进行二次开发时,能够更好地支持“标准产品迭代与个性化保持独立”的需求。在2.4.3【oinone独特性之低无一体】一文中,我们也提到了这个特性,但那是在低无一体的情况下,通过元数据融合来实现的。让我们看看基于低代码开发模式下,典型的Oinone二次开发工程结构(如下图2-14所示),就可以更好地理解这个特性啦! 图2-14 Oinone典型的二开工程结构

    2024年5月23日
    1.7K00

Leave a Reply

Please Login to Comment