在我们的很多项目中,客户都是有个性化需求的,就像我们不能找到两件一模一样的东西,何况是企业的经营与管理思路,多少都会有差异。常规的方式只能去修改标准产品的逻辑来适配客户的需求。导致后续标品维护非常困难。而在介绍完这节以后是不是让你更加清晰认知到我们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.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;
}
- 新建宠狗品种模型,用@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;
}
- 新建萌猫品种模型,用@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> {
public static final String DICTIONARY ="demo.CatShapeEnum";
public final static CatShapeEnum BIG =create("BIG",1,"大","大");
public final static CatShapeEnum SMALL =create("SMALL",2,"小","小");
}
package pro.shushi.pamirs.demo.api.model;
import pro.shushi.pamirs.demo.api.enumeration.CatShapeEnum;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
@Model.MultiTableInherited(type = PetCatType.KIND_CAT)
@Model.model(PetCatType.MODEL_MODEL)
@Model(displayName="萌猫品种",labelFields = {"name"})
public class PetCatType extends PetType {
public static final String MODEL_MODEL="demo.PetCatType";
public static final String KIND_CAT="CAT";
@Field(displayName = "宠物分类",defaultValue = PetCatType.KIND_CAT,invisible = true)
private String kind;
@Field.Enum
@Field(displayName = "宠物体型")
private CatShapeEnum shape;
}
Step2 配置菜单后,重启查看效果
- 为了更好帮助大家更好地理解,这里先把对应的菜单配置上,从页面来看看效果。下面以注解方式进行菜单配置,更多详见3.5.1【构建第一个Menu】一文。
package pro.shushi.pamirs.demo.core.init;
import pro.shushi.pamirs.boot.base.constants.ViewActionConstants;
import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute;
import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxMenu;
import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxMenus;
import pro.shushi.pamirs.demo.api.model.*;
@UxMenus
public class DemoMenus implements ViewActionConstants {
@UxMenu("宠物品种")@UxRoute(PetType.MODEL_MODEL) class PetTypeMenu{}
@UxMenu("萌猫品种")@UxRoute(PetCatType.MODEL_MODEL) class CatTypeMenu{}
@UxMenu("宠狗品种")@UxRoute(PetDogType.MODEL_MODEL) class DogTypeMenu{}
}
- 在宠狗品种中新增、修改、列表都隐藏了品种类型字段,并附上了默认值DOG
- 在萌猫品种中新增、修改、列表都增加宠物体型枚举字段,并且隐藏了品种类型字段,并附上了默认值CAT
Step3 为宠狗和萌猫品种模型实现默认读写扩展点,保证父子表数据同步
多表继承默认父子表是不同步的,不过oinone为每一个模型的默认数据管理器都提供的默认读写扩展点,我们可以主动利用这个特性来进行手动同步,更多扩展点知识详见3.4.3【函数相关特性】部分中的扩展点一文,会介绍除了利用系统默认提供的扩展点,还有如何自定义扩展。
- PetCatType和PetDogType都需要实现扩展点来完成与父模型的同步,所以这里把相同逻辑做了提取AbstractPetTypeExtPoint
package pro.shushi.pamirs.demo.core.extpoint;
import pro.shushi.pamirs.core.common.CopyHelper;
import pro.shushi.pamirs.demo.api.model.PetType;
import pro.shushi.pamirs.framework.connectors.data.sql.Pops;
import pro.shushi.pamirs.meta.api.Models;
import pro.shushi.pamirs.meta.base.extpoint.DefaultReadWriteExtPoint;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractPetTypeExtPoint extends DefaultReadWriteExtPoint {
public T createBefore(T data) {
data.construct();
return data;
}
public T updateBefore(T data) {
return data;
}
public T createAfter(T data) {
CopyHelper.simpleReplace(data, new PetType()).create();
return data;
}
public T updateAfter(T data) {
CopyHelper.simpleReplace(data, new PetType()).updateById();
return data;
}
public List deleteBefore(List data) {
List petTypeIdList = new ArrayList();
for(T item:data){
petTypeIdList.add(item.getId());
}
Models.data().deleteByWrapper(Pops.lambdaQuery().from(PetType.MODEL_MODEL).in(PetType::getId,petTypeIdList));
return data;
}
}
- PetCatTypeExtPoint继承AbstractPetTypeExtPoint类,并通过@Ext(PetCatType.class)申明扩展点所扩展函数所在类为PetCatType,并@ExtPoint.Implement申明对应扩展点的实现方法实例
package pro.shushi.pamirs.demo.core.extpoint;
import pro.shushi.pamirs.demo.api.model.PetCatType;
import pro.shushi.pamirs.meta.annotation.Ext;
import pro.shushi.pamirs.meta.annotation.ExtPoint;
import java.util.List;
@Ext(PetCatType.class)
public class PetCatTypeExtPoint extends AbstractPetTypeExtPoint{
@Override
@ExtPoint.Implement
public PetCatType createBefore(PetCatType data) {
return super.createBefore(data);
}
@Override
@ExtPoint.Implement
public PetCatType updateBefore(PetCatType data) {
return super.updateBefore(data);
}
@Override
@ExtPoint.Implement
public PetCatType createAfter(PetCatType data) {
return super.createAfter(data);
}
@Override
@ExtPoint.Implement
public PetCatType updateAfter(PetCatType data) {
return super.updateAfter(data);
}
@Override
@ExtPoint.Implement
public List deleteBefore(List data) {
return super.deleteBefore(data);
}
}
- PetDogTypeExtPoint继承AbstractPetTypeExtPoint类,并通过@Ext(PetDogType.class)申明扩展点所扩展函数所在类为PetDogType,并@ExtPoint.Implement申明对应扩展点的实现方法实例
package pro.shushi.pamirs.demo.core.extpoint;
import pro.shushi.pamirs.demo.api.model.PetDogType;
import pro.shushi.pamirs.meta.annotation.Ext;
import pro.shushi.pamirs.meta.annotation.ExtPoint;
import java.util.List;
@Ext(PetDogType.class)
public class PetDogTypeExtPoint extends AbstractPetTypeExtPoint{
@Override
@ExtPoint.Implement
public PetDogType createBefore(PetDogType data) {
return super.createBefore(data);
}
@Override
@ExtPoint.Implement
public PetDogType updateBefore(PetDogType data) {
return super.updateBefore(data);
}
@Override
@ExtPoint.Implement
public PetDogType createAfter(PetDogType data) {
return super.createAfter(data);
}
@Override
@ExtPoint.Implement
public PetDogType updateAfter(PetDogType data) {
return super.updateAfter(data);
}
@Override
@ExtPoint.Implement
public List deleteBefore(List data) {
return super.deleteBefore(data);
}
}
Step4 重启查看效果:在萌猫品种和宠狗品种下新增、修改、删除,则会在宠物品种下看到对应记录变化
六、扩展继承(举例)
场景设计(如下图3-3-55所示)
Step1 新建宠物商品、宠狗商品和萌猫商品模型,并配置菜单
- 新建宠物商品模型为普通存储模型,用扩展继承父类设置公共字段如:店铺,品种等,注意type字段类型为PetType
package pro.shushi.pamirs.demo.api.model;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import java.math.BigDecimal;
@Model.model(PetItem.MODEL_MODEL)
@Model(displayName = "宠物商品",summary="宠物商品")
public class PetItem extends AbstractDemoCodeModel{
public static final String MODEL_MODEL="demo.PetItem";
@Field(displayName = "商品名称",required = true)
private String itemName;
@Field(displayName = "商品价格",required = true)
private BigDecimal price;
@Field(displayName = "店铺",required = true)
private PetShop shop;
@Field(displayName = "品种")
@Field.many2one
private PetType type;
}
- 新建宠狗商品模型继承宠物商品模型,注意这里把type字段类型覆盖为PetType的子类PetDogType,并新增原产地字段provenance
package pro.shushi.pamirs.demo.api.model;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
@Model.model(PetDogItem.MODEL_MODEL)
@Model(displayName = "宠狗商品",summary="宠狗商品")
public class PetDogItem extends PetItem{
public static final String MODEL_MODEL="demo.PetDogItem";
@Field(displayName = "品种")
@Field.many2one
private PetDogType type;
@Field(displayName = "原产地")
private String provenance;
}
- 新建萌猫商品模型继承宠物商品模型,注意这里把type字段类型覆盖为PetType的子类PetCatType。
package pro.shushi.pamirs.demo.api.model;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
@Model.model(PetCatItem.MODEL_MODEL)
@Model(displayName = "萌猫商品",summary="萌猫商品")
public class PetCatItem extends PetItem{
public static final String MODEL_MODEL="demo.PetCatItem";
@Field(displayName = "品种")
@Field.many2one
private PetCatType type;
}
- 为了更好帮助大家更好地理解,这里先把对应的菜单配置上,从页面来看看效果。下面以注解方式进行菜单配置,更多详见3.5.1【构建第一个Menu】一文。
package pro.shushi.pamirs.demo.core.init;
import pro.shushi.pamirs.boot.base.constants.ViewActionConstants;
import pro.shushi.pamirs.boot.base.ux.annotation.action.UxRoute;
import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxMenu;
import pro.shushi.pamirs.boot.base.ux.annotation.navigator.UxMenus;
import pro.shushi.pamirs.demo.api.model.*;
@UxMenus
public class DemoMenus implements ViewActionConstants {
@UxMenu("商品管理")@UxRoute(PetItem.MODEL_MODEL) class ItemMenu{}
@UxMenu("宠狗商品")@UxRoute(PetDogItem.MODEL_MODEL) class DogItemMenu{}
@UxMenu("萌猫商品")@UxRoute(PetCatItem.MODEL_MODEL) class CatItemMenu{}
@UxMenu("宠物品种")@UxRoute(PetType.MODEL_MODEL) class PetTypeMenu{}
@UxMenu("萌猫品种")@UxRoute(PetCatType.MODEL_MODEL) class CatTypeMenu{}
@UxMenu("宠狗品种")@UxRoute(PetDogType.MODEL_MODEL) class DogTypeMenu{}
}
至此模型就建完了,还是很简单的吧
Step2 分别新建三条宠物商品、宠狗商品和萌猫商品记录,查看效果
- 父模型宠物商品的商品管理可以看到品类中可选柴犬、加菲猫,数据来源是品种的父模型PetType,店铺下拉框展示为"-",因为店铺模型没有配置label字段,可以在店铺模型的注解上增加“@Model(displayName = "宠物店铺",summary="宠物店铺",labelFields = {"shopName"})”,即可解决。这里先选择第一个往下执行
- 父模型宠狗商品的商品管理可以看到品类中可选柴犬,数据来源是品种的子模型PetDogType,而且新增源产地字段;
- 父模型萌猫商品的商品管理可以看到品类中可选加菲猫,数据来源是品种的子模型PetCatType;
Step3 分别观察宠物商品、宠狗商品和萌猫商品的列表页,我们会发现数据记录都是三条,但是展示字段会随着模型不同而有差异
- 父页面在品种一列中可以显示所有品种名称
- 宠狗页面在品种一列中只能显示PetDogType的品种名称、但多了原产地字段
- 萌猫页面在品种一列中只能显示PetCatType的品种名称
- 对应的数据库表:数据都在一张记录表中,新增了原产地字段
Step4 顺带优化下宠物店铺的展示Labels
- 给PetShop增加@Model(labelFields ={"shopName"} )注解,labelFields是为模型的“数据标题, 用于前端展示”,其默认值为name,但PetShop没有name字段,所以我们列表上展示不出来。更多元数据见4.1.6【模型之元数据详解】一文;
package pro.shushi.pamirs.demo.api.model;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import java.sql.Time;
@Model.model(PetShop.MODEL_MODEL)
@Model(displayName = "宠物店铺",summary="宠物店铺",labelFields ={"shopName"} )
public class PetShop extends AbstractDemoIdModel {
public static final String MODEL_MODEL="demo.PetShop";
@Field(displayName = "店铺名称",required = true)
private String shopName;
@Field(displayName = "开店时间",required = true)
private Time openTime;
@Field(displayName = "闭店时间",required = true)
private Time closeTime;
}
- 重启查看效果,店铺字段可以展示出来
Step5 思考在扩展继承模式下数据隔离问题
多表继承需要自己搞定父、子模型的数据同步问题,同表继承则需要自己搞定数据隔离问题。我们可以重写queryPage这个Function,如我们在3.3.2【模型类型】一文中介绍“代理模型类型”的部分时,涉及的PetShopProxyAction就覆盖了queryPage。这里举例PetCatItem的queryPage覆盖,PetDogItem留给大家自行练习
- 给PetItem显示增加一个typeId字段,方便在PetCatItemAction中用于过滤条件使用。为什么说显示增加呢?因为type字段是一个many2one的字段在没有配置@Field.Relation的情况下,oinone会为PetItem模型自动推断出一个typeId字段去关联PetType模型的Id。@Field.Relation的relationFields为本模型的关联字段,referenceFields为目标模型关联字段。如符合推断规范可以不配置@Field.Relation,就如这个场景配不配效果是一样的。还是那句话“更多元数据详见4.1.6【模型之元数据详解】一文”;
package pro.shushi.pamirs.demo.api.model;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import java.math.BigDecimal;
@Model.model(PetItem.MODEL_MODEL)
@Model(displayName = "宠物商品",summary="宠物商品")
public class PetItem extends AbstractDemoCodeModel{
public static final String MODEL_MODEL="demo.PetItem";
@Field(displayName = "商品名称",required = true)
private String itemName;
@Field(displayName = "商品价格",required = true)
private BigDecimal price;
@Field(displayName = "店铺",required = true)
private PetShop shop;
@Field(displayName = "品种")
@Field.many2one
@Field.Relation(relationFields = {"typeId"},referenceFields = {"id"})
private PetType type;
@Field(displayName = "品种类型",invisible = true)
private Long typeId;
}
- 覆盖PetCatItem名为“queryFilters”的函数,增加typeId的过滤条件为PetCatType表对应的Id列表,则针对PetCatItem的所有查询都会自动加上“queryFilters”的函数返回的过滤条件
package pro.shushi.pamirs.demo.core.action;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import pro.shushi.pamirs.core.common.behavior.impl.DataStatusBehavior;
import pro.shushi.pamirs.demo.api.model.PetCatItem;
import pro.shushi.pamirs.demo.api.model.PetCatType;
import pro.shushi.pamirs.framework.connectors.data.sql.Pops;
import pro.shushi.pamirs.meta.annotation.Action;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
import java.util.List;
import java.util.stream.Collectors;
@Model.model(PetCatItem.MODEL_MODEL)
@Component
public class PetCatItemAction extends DataStatusBehavior {
@Override
protected PetCatItem fetchData(PetCatItem data) {
return data.queryById();
}
@Action(displayName = "启用")
public PetCatItem dataStatusEnable(PetCatItem data){
data = super.dataStatusEnable(data);
data.updateById();
return data;
}
@Function.Advanced(displayName = "查询模型数据的默认过滤条件", type = FunctionTypeEnum.QUERY, managed = true)
@Function(openLevel = {LOCAL})
public String queryFilters() {
StringBuilder sqlWhereCondition = new StringBuilder();
List typeList = new PetCatType().queryList();
if(!CollectionUtils.isEmpty(typeList)){
// sqlWhereCondition.append("type_id");
sqlWhereCondition.append(PStringUtils.fieldName2Column(LambdaUtil.fetchFieldName(PetCatItem::getTypeId)));
sqlWhereCondition.append(StringUtils.SPACE).append(SqlConstants.IN).append(CharacterConstants.LEFT_BRACKET);
for(PetCatType petCatType: typeList){
sqlWhereCondition.append(petCatType.getId()).append(CharacterConstants.SEPARATOR_COMMA);
}
sqlWhereCondition.deleteCharAt(sqlWhereCondition.lastIndexOf(CharacterConstants.SEPARATOR_COMMA));
sqlWhereCondition.append(StringUtils.SPACE).append(CharacterConstants.RIGHT_BRACKET);
}
return sqlWhereCondition.toString();
}
}
- 重启查看效果,萌猫商品只能看到PetCatType对应的数据记录了,赶紧自己动手试试PetDogItem的改造吧
七、代理继承(举例)
代理模型在3.3.2【模型的类型】一文中有介绍过,代理模型看名字就知道其本身是通过继承方式代理另一个存储模型,这里不过多介绍。我们来尝试一下它继承的特殊性“一个代理模型也可以继承任意数量继承相同父类的代理模型”
场景设计(如下图3-3-72所示)
Step1 新建宠物店铺代理模型A和宠物店铺代理模型B,同时修改PetItem
- 新建宠物店铺代理模型A申明为代理模型,新增一个one2many字段items,用@Field.Relation申明关联字段
package pro.shushi.pamirs.demo.api.proxy;
import pro.shushi.pamirs.demo.api.model.PetItem;
import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;
import pro.shushi.pamirs.user.api.model.PamirsUser;
import java.util.List;
@Model.model(PetShopProxyA.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.PROXY)
@Model(displayName = "宠物店铺代理模型A",summary="宠物店铺代理模型A")
public class PetShopProxyA extends PetShop {
public static final String MODEL_MODEL="demo.PetShopProxyA";
@Field.one2many
@Field(displayName = "商品列表")
@Field.Relation(relationFields = {"id"},referenceFields = {"shopId"})
private List items;
}
- 新建宠物店铺代理模型B申明为代理模型,用 @Model.Advanced(inherited ={PetShopProxy.MODEL_MODEL,PetShopProxyA.MODEL_MODEL})声明继承多个同源代理模型,并且新增一个one2many字段catItems用@Field.Relation申明关联字段
package pro.shushi.pamirs.demo.api.proxy;
import pro.shushi.pamirs.demo.api.model.PetCatItem;
import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;
import java.util.List;
@Model.model(PetShopProxyB.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.PROXY,inherited ={PetShopProxy.MODEL_MODEL,PetShopProxyA.MODEL_MODEL} )
@Model(displayName = "宠物店铺代理模型B",summary="宠物店铺代理模型B")
public class PetShopProxyB extends PetShop {
public static final String MODEL_MODEL="demo.PetShopProxyB";
@Field.one2many
@Field(displayName = "萌猫商品列表")
@Field.Relation(relationFields = {"id"},referenceFields = {"shopId"})
private List catItems;
}
- 修改PetItem增加一个labelFields={"itemName"}注解,给PetItem显示增加一个shopId字段方便,在Service中用于过滤条件使用。
package pro.shushi.pamirs.demo.api.model;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import java.math.BigDecimal;
@Model.model(PetItem.MODEL_MODEL)
@Model(displayName = "宠物商品",summary="宠物商品",labelFields={"itemName"})
public class PetItem extends AbstractDemoCodeModel{
public static final String MODEL_MODEL="demo.PetItem";
@Field(displayName = "商品名称",required = true)
private String itemName;
@Field(displayName = "商品价格",required = true)
private BigDecimal price;
@Field(displayName = "店铺",required = true)
private PetShop shop;
@Field(displayName="店铺id",invisible = true)
private Long shopId;
@Field(displayName = "品种")
@Field.many2one
@Field.Relation(relationFields = {"typeId"},referenceFields = {"id"})
private PetType type;
@Field(displayName = "品种类型",invisible = true)
private Long typeId;
}
Step2 覆盖PetShopProxyB的queryPage和queryOne函数
PetShopProxyB的queryPage和queryOne都查catItems\creater\items三个字段
package pro.shushi.pamirs.demo.core.action;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import pro.shushi.pamirs.core.common.behavior.impl.DataStatusBehavior;
import pro.shushi.pamirs.demo.api.model.PetShop;
import pro.shushi.pamirs.demo.api.proxy.PetShopProxy;
import pro.shushi.pamirs.demo.api.proxy.PetShopProxyB;
import pro.shushi.pamirs.framework.faas.utils.ArgUtils;
import pro.shushi.pamirs.meta.annotation.Action;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
@Model.model(PetShopProxyB.MODEL_MODEL)
@Component
public class PetShopProxyBAction {
@Function.Advanced(type= FunctionTypeEnum.QUERY)
@Function.fun(FunctionConstants.queryPage)
@Function(openLevel = {FunctionOpenEnum.API})
public Pagination queryPage(Pagination page, IWrapper queryWrapper){
Pagination result = new PetShopProxy().queryPage(page,queryWrapper);
if(!CollectionUtils.isEmpty(result.getContent())) {
//自身继承【宠物店铺】后扩展的字段catItems
new PetShopProxyB().listFieldQuery(result.getContent(),PetShopProxyB::getCatItems);
//继承【宠物店铺代理模型】而来的字段creater,java语言限制【宠物店铺代理模型B】没有getCreate()方法
new PetShopProxyB().listFieldQuery(result.getContent(),"creater");
//继承【宠物店铺代理模型A】而来的字段items,java语言限制【宠物店铺代理模型B】没有getItems()方法
new PetShopProxyB().listFieldQuery(result.getContent(),"items");
}
return result;
}
@Function.Advanced(type= FunctionTypeEnum.QUERY)
@Function.fun(FunctionConstants.queryByEntity)
@Function(openLevel = {FunctionOpenEnum.API})
public PetShopProxyB queryOne(PetShopProxyB query){
query = query.queryById();
//自身继承【宠物店铺】后扩展的字段catItems
query.fieldQuery(PetShopProxyB::getCatItems);
//继承【宠物店铺代理模型】而来的字段creater,java语言限制【宠物店铺代理模型B】没有getCreate()方法
query.fieldQuery("creater");
//继承【宠物店铺代理模型A】而来的字段items,java语言限制【宠物店铺代理模型B】没有getItems()方法
query.fieldQuery("items");
return query;
}
}
Step3 配置菜单,并重启看效果
-
配置菜单:请参考本节前面介绍自行增加三个菜单商店管理、商店管理A、商店管理B分别对应宠物店铺代理模型、宠物店铺代理模型A、宠物店铺代理模型B的管理入口
-
宠物店铺代理模型覆盖了queryPage方法但没有覆盖queryOne方法,所以列表页有显示新增字段创建者,但详情页没有。具体详见3.3.2【模型的类型】一文中的“代理模型”介绍部分;
- 宠物店铺代理模型A只增加了一个商品列表字段,没有覆盖对应query相关方法,所以列表页有这个字段但没有值;
- 宠物店铺代理模型B因为同源多表继承了宠物店铺代理模型和宠物店铺代理模型A所以它拥有商品列表、萌猫商品列表、创建者三个字段,并且同时覆盖queryPage和queryOne函数,所以列表和详情都有值。
八、临时继承(举例)
参考前文3.3.2【模型的类型】一文中对传输模型的介绍,差别在于一个继承抽象基类,一个继承其他模型类型,不管什么类型都只是复用父类的字段,自己还是传输模型
九、总结
在我们的很多项目中,客户都是有个性化需求的,就像我们不能找到两件一模一样的东西,何况是企业的经营与管理思路,多少都会有差异。常规的方式只能去修改标准产品的逻辑来适配客户的需求。导致后续标品维护非常困难。而在介绍完这节以后是不是让你更加清晰认知到我们2.4.2【oinone独特性之每一个需求都可以是一个模块】一文中所表达的特性带来的好处呢?
Oinone社区 作者:史, 昂原创文章,如若转载,请注明出处:https://doc.oinone.top/oio4/9235.html
访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验