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 模型的继承

图3-3-4-1 继承的使用场景

四、抽象基类(举例)

参考前文中3.3.2【模型的类型】一文中关于抽象模型的介绍

五、多表继承(举例)

场景设计如下

3.3.4 模型的继承

图3-3-4-2 多表继承设计场景

Step1 新建宠物品种、宠狗品种和萌猫品种模型

  1. 新建宠物品种模型,用@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 多表继承示例代码

  1. 新建宠狗品种模型,用@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 多表继承示例代码

  1. 新建萌猫品种模型,用@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,"小","小");
}

图3-3-4-5 多表继承示例代码

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;
}

图3-3-4-6 多表继承示例代码

Step2 配置菜单后,重启查看效果

  1. 为了更好帮助大家更好地理解,这里先把对应的菜单配置上,从页面来看看效果。下面以注解方式进行菜单配置,更多详见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{}
}

图3-3-4-7 对应菜单配置

  1. 在宠狗品种中新增、修改、列表都隐藏了品种类型字段,并附上了默认值DOG

3.3.4 模型的继承

图3-3-4-8 宠狗品种新增页

3.3.4 模型的继承

图3-3-4-9 宠狗品种列表页

3.3.4 模型的继承

图3-3-4-10 宠狗品种数据表

  1. 在萌猫品种中新增、修改、列表都增加宠物体型枚举字段,并且隐藏了品种类型字段,并附上了默认值CAT

3.3.4 模型的继承

图3-3-4-11 萌猫品种新增页

3.3.4 模型的继承

图3-3-4-12 萌猫品种列表页

3.3.4 模型的继承

图3-3-4-13 萌猫品种数据表

Step3 为宠狗和萌猫品种模型实现默认读写扩展点,保证父子表数据同步

多表继承默认父子表是不同步的,不过oinone为每一个模型的默认数据管理器都提供的默认读写扩展点,我们可以主动利用这个特性来进行手动同步,更多扩展点知识详见3.4.3【函数相关特性】部分中的扩展点一文,会介绍除了利用系统默认提供的扩展点,还有如何自定义扩展。

  1. 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;
    }
}

图3-3-4-14 相同逻辑提取AbstractPetTypeExtPoint

  1. 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);
    }
}

图3-3-4-15 PetCatTypeExtPoint继承AbstractPetTypeExtPoint类

  1. 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);
    }
}

图3-3-4-16 PetDogTypeExtPoint继承AbstractPetTypeExtPoint类

Step4 重启查看效果:在萌猫品种和宠狗品种下新增、修改、删除,则会在宠物品种下看到对应记录变化

3.3.4 模型的继承

图3-3-4-17 父模型宠物品种列表页

3.3.4 模型的继承

图3-3-4-18 父模型宠物品种数据表

六、扩展继承(举例)

场景设计(如下图3-3-55所示)

3.3.4 模型的继承

图3-3-4-19 扩展继承场景设计

Step1 新建宠物商品、宠狗商品和萌猫商品模型,并配置菜单

  1. 新建宠物商品模型为普通存储模型,用扩展继承父类设置公共字段如:店铺,品种等,注意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;
}

图3-3-4-20 新建宠物商品模型为普通存储模型

  1. 新建宠狗商品模型继承宠物商品模型,注意这里把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;
}

图3-3-4-21 新建宠狗商品模型继承宠物商品模型

  1. 新建萌猫商品模型继承宠物商品模型,注意这里把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-3-4-22 新建萌猫商品模型继承宠物商品模型

  1. 为了更好帮助大家更好地理解,这里先把对应的菜单配置上,从页面来看看效果。下面以注解方式进行菜单配置,更多详见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{}
}

图3-3-4-23 以注解方式进行菜单配置

至此模型就建完了,还是很简单的吧

Step2 分别新建三条宠物商品、宠狗商品和萌猫商品记录,查看效果

  1. 父模型宠物商品的商品管理可以看到品类中可选柴犬、加菲猫,数据来源是品种的父模型PetType,店铺下拉框展示为"-",因为店铺模型没有配置label字段,可以在店铺模型的注解上增加“@Model(displayName = "宠物店铺",summary="宠物店铺",labelFields = {"shopName"})”,即可解决。这里先选择第一个往下执行

3.3.4 模型的继承

图3-3-4-24 宠物商品管理

  1. 父模型宠狗商品的商品管理可以看到品类中可选柴犬,数据来源是品种的子模型PetDogType,而且新增源产地字段;

3.3.4 模型的继承

图3-3-4-25 父模型宠狗商品管理

  1. 父模型萌猫商品的商品管理可以看到品类中可选加菲猫,数据来源是品种的子模型PetCatType;

3.3.4 模型的继承

图3-3-4-26 父模型萌猫商品管理

Step3 分别观察宠物商品、宠狗商品和萌猫商品的列表页,我们会发现数据记录都是三条,但是展示字段会随着模型不同而有差异

  1. 父页面在品种一列中可以显示所有品种名称

3.3.4 模型的继承

图3-3-4-27 父页面在品种一列中可以显示所有品种名称

  1. 宠狗页面在品种一列中只能显示PetDogType的品种名称、但多了原产地字段

3.3.4 模型的继承

图3-3-4-28 宠狗页面品种列展示

  1. 萌猫页面在品种一列中只能显示PetCatType的品种名称

3.3.4 模型的继承

图3-3-4-29 萌猫页面品种列中只展示PetCatType的品种名称

  1. 对应的数据库表:数据都在一张记录表中,新增了原产地字段

3.3.4 模型的继承

图3-3-4-30 对应数据库表

Step4 顺带优化下宠物店铺的展示Labels

  1. 给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;
}

图3-3-4-31 给PetShop增加@Model(labelFields ={shopName} )注解

  1. 重启查看效果,店铺字段可以展示出来

3.3.4 模型的继承

图3-3-4-32 店铺字段可展示

Step5 思考在扩展继承模式下数据隔离问题

多表继承需要自己搞定父、子模型的数据同步问题,同表继承则需要自己搞定数据隔离问题。我们可以重写queryPage这个Function,如我们在3.3.2【模型类型】一文中介绍“代理模型类型”的部分时,涉及的PetShopProxyAction就覆盖了queryPage。这里举例PetCatItem的queryPage覆盖,PetDogItem留给大家自行练习

  1. 给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;
}

图3-3-4-33 给PetItem显示增加一个typeId字段

  1. 覆盖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();
    }
}

图3-3-4-34 覆盖函数的代码示例

  1. 重启查看效果,萌猫商品只能看到PetCatType对应的数据记录了,赶紧自己动手试试PetDogItem的改造吧

3.3.4 模型的继承

图3-3-4-35 重启查看效果

七、代理继承(举例)

代理模型在3.3.2【模型的类型】一文中有介绍过,代理模型看名字就知道其本身是通过继承方式代理另一个存储模型,这里不过多介绍。我们来尝试一下它继承的特殊性“一个代理模型也可以继承任意数量继承相同父类的代理模型”

场景设计(如下图3-3-72所示)

3.3.4 模型的继承

图3-3-4-36 代理继承场景设计

Step1 新建宠物店铺代理模型A和宠物店铺代理模型B,同时修改PetItem

  1. 新建宠物店铺代理模型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;
}

图3-3-4-37 新建宠物店铺代理模型A申明为代理模型

  1. 新建宠物店铺代理模型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;
}

图3-3-4-38 新建宠物店铺代理模型B申明为代理模型

  1. 修改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;
}

图3-3-4-39 PetItem代码修改示例

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;
    }
}

图3-3-4-40 函数覆盖代码示例

Step3 配置菜单,并重启看效果

  1. 配置菜单:请参考本节前面介绍自行增加三个菜单商店管理、商店管理A、商店管理B分别对应宠物店铺代理模型、宠物店铺代理模型A、宠物店铺代理模型B的管理入口

  2. 宠物店铺代理模型覆盖了queryPage方法但没有覆盖queryOne方法,所以列表页有显示新增字段创建者,但详情页没有。具体详见3.3.2【模型的类型】一文中的“代理模型”介绍部分;

3.3.4 模型的继承

图3-3-4-41 宠物店铺代理模型的列表页

3.3.4 模型的继承

图3-3-4-42 宠物店铺代理模型的详情页

  1. 宠物店铺代理模型A只增加了一个商品列表字段,没有覆盖对应query相关方法,所以列表页有这个字段但没有值;

3.3.4 模型的继承

图3-3-4-43 宠物店铺代理模型A的列表页

  1. 宠物店铺代理模型B因为同源多表继承了宠物店铺代理模型和宠物店铺代理模型A所以它拥有商品列表、萌猫商品列表、创建者三个字段,并且同时覆盖queryPage和queryOne函数,所以列表和详情都有值。

3.3.4 模型的继承

图3-3-4-44 宠物店铺代理模型B的列表页

3.3.4 模型的继承

图3-3-4-45 宠物店铺代理模型B的详情页

八、临时继承(举例)

参考前文3.3.2【模型的类型】一文中对传输模型的介绍,差别在于一个继承抽象基类,一个继承其他模型类型,不管什么类型都只是复用父类的字段,自己还是传输模型

九、总结

在我们的很多项目中,客户都是有个性化需求的,就像我们不能找到两件一模一样的东西,何况是企业的经营与管理思路,多少都会有差异。常规的方式只能去修改标准产品的逻辑来适配客户的需求。导致后续标品维护非常困难。而在介绍完这节以后是不是让你更加清晰认知到我们2.4.2【oinone独特性之每一个需求都可以是一个模块】一文中所表达的特性带来的好处呢?

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

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

(1)
史, 昂的头像史, 昂数式管理员
上一篇 2024年5月23日 am9:42
下一篇 2024年5月23日 am9:49

相关推荐

  • 登录日志

    1. 登录日志 登录日志:记录企业成员/用户登录平台的历史明细,包括登录时间、位置、IP、登录设备、登录平台等信息,登录平台包括:PC Web、小程序、H5、APP。登录日志不需要通过审计规则订阅,平台会记录每位用户的登录日志。 操作支持导出、查看详情。

    2024年6月20日
    75000
  • 流程设计

    1.流程设计 进入流程设计页之后可以进行流程名称、流程说明的编辑,可以进行流程设计,流程参数配置,保存和发布。 1.1 流程配置 点击进入流程配置页面,若需要配置一些参数供流程使用,可在此添加和删除。删除流程参数时,若该参数已在流程中被使用则无法删除。参数支持文本、数值、日期、布尔四种类型。 1.2 保存 点击后流程设计进行存档,流程设计不完整也支持保存,下次进入流程设计回到保存的页面。 1.3 发布 第一次发布时右上角发布显示文字为发布流程,后续发布按钮显示文字为更新发布。发布后流程才会按照设计触发,首次发布和更新发布的逻辑一致,若流程中有未解决的错误则无法发布不成功,发布成功后页面跳转到显示全部流程的页面,流程状态为已启用、已更新。

    2024年6月20日
    69600
  • 3.4.3.1 面向对象-继承与多态

    本节为小伙伴们介绍,Function的面向对象的特性:继承与多态; 一、继承 我们在3.4.1【构建第一个Function】一文中伴随模型新增函数和独立类新增函数绑定到模型部分都是在父模型PetShop新增了sayHello的Function,同样其子模型都具备sayHello的Function。因为我们是通过Function的namespace来做依据的,子模型在继承父模型的sayHello函数后会以子模型的编码为namespace,名称则同样为sayHello。 二、多态(举例) oinone的多态,我们只提供覆盖功能,不提供重载,因为oinone相同name和fun的情况下不会去识别参数个数和类型。 Step1 为PetShop新增hello函数 package pro.shushi.pamirs.demo.api.model; …… //import @Model.model(PetShop.MODEL_MODEL) @Model(displayName = "宠物店铺",summary="宠物店铺",labelFields ={"shopName"} ) @Model.Code(sequence = "DATE_ORDERLY_SEQ",prefix = "P",size=6,step=1,initial = 10000,format = "yyyyMMdd") public class PetShop extends AbstractDemoIdModel { public static final String MODEL_MODEL="demo.PetShop"; …… //省略其他代码 @Function(openLevel = FunctionOpenEnum.API) @Function.Advanced(type= FunctionTypeEnum.QUERY) public PetShop sayHello(PetShop shop){ PamirsSession.getMessageHub().info("Hello:"+shop.getShopName()); return shop; } @Function(name = "sayHello2",openLevel = FunctionOpenEnum.API) @Function.Advanced(type= FunctionTypeEnum.QUERY) @Function.fun("sayHello2") public PetShop sayHello(PetShop shop, String s) { PamirsSession.getMessageHub().info("Hello:"+shop.getShopName()+",s:"+s); return shop; } @Function(openLevel = FunctionOpenEnum.API) @Function.Advanced(type= FunctionTypeEnum.QUERY) public PetShop hello(PetShop shop){ PamirsSession.getMessageHub().info("Hello:"+shop.getShopName()); return shop; } } 图3-4-3-1 为PetShop新增hello函数 Step2 为PetShopProxyB新增对应的三个函数 其中PetShopProxyB新增的hello函数,在java中是重载了hello,在代码中new PetShopProxyB()是可以调用父类的sayHello单参方法,也可以调用本类的双参方法。但在oinone的体系中对于PetShopProxyB只有一个可识别的Function就是双参的sayHello 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.Function; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum; import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum; 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<PetCatItem> catItems; @Function(openLevel = FunctionOpenEnum.API) @Function.Advanced(type= FunctionTypeEnum.QUERY) public PetShop sayHello(PetShop shop){ PamirsSession.getMessageHub().info("PetShopProxyB Hello:"+shop.getShopName()); return shop; } @Function(name = "sayHello2",openLevel = FunctionOpenEnum.API) @Function.Advanced(type= FunctionTypeEnum.QUERY) @Function.fun("sayHello2") public PetShop sayHello(PetShop shop,String hello){ PamirsSession.getMessageHub().info("PetShopProxyB say:"+hello+","+shop.getShopName()); return shop; } @Function(openLevel = FunctionOpenEnum.API)…

    2024年5月23日
    89200
  • 4.1.20 框架之Session

    在日常开发中,我们经常需要把一些通用的信息放入程序执行的上下文中,以便业务开发人员快速获取。那么oinone的PamirsSession就是来解决此类问题的。 一、PamirsSession介绍 在oinone的体系中PamirsSession是执行上下文的承载,您能从中获取业务基础信息、指令信息、元数据信息、环境信息、请求参数,以及前后端MessageHub等。在前面的学习过程中我们已经多次接触到了如何使用PamirsSession: 在4.1.19【框架之网关协议-后端占位符】一文中,使用PamirsSession.getUserId()来获取当前登入用户Id,诸如此类的业务基础信息; 在4.1.18【框架之网关协议-variables变量】一文中,使用PamirsSession.getRequestVariables()得到PamirsRequestVariables对象,进而获取前端请求的相关信息; 在4.1.5【模型之持久层配置】一文中,使用PamirsSession.directive(),来操作元位指令系统,进而影响执行策略; 在4.1.13【Action之校验】、3.4.1【构建第一个Function】等文章中,都用到PamirsSession.getMessageHub()来设置返回消息。 二、构建模块自身Session(举例) 不同的应用场景对PamirsSession的诉求是不一样的,这个时候我们就可以去扩展PamirsSession来达到我们的目的 构建模块自身Session的步骤 构建自身特有的数据结构XSessionData 对XSessionData进行线程级缓存封装 利用Hook机制初始化XSessionData并放到ThreadLocal中 定义自身XSessionApi 实现XSessionApi接口、SessionClearApi。在请求结束时会调用SessionClearApi的clear方法 定义XSession继承PamirsSession 扩展PamirsSession的经典案例设计图 图4-1-20-1 扩展PamirsSession的经典案例设计图 构建Demo应用自身Session 下面的例子为给Session放入当前登陆用户 Step1 新建DemoSessionData类 构建自身特有的数据结构DemoSessionData,增加一个模型为PamirsUser的字段user,DemoSessionData用Data注解,注意要用Oinone平台提供的@Data package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.annotation.fun.Data; import pro.shushi.pamirs.user.api.model.PamirsUser; @Data public class DemoSessionData { private PamirsUser user; } 图4-1-20-2 新建DemoSessionData类 Step2 新建DemoSessionCache 对DemoSessionData进行线程级缓存封装 package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.api.CommonApiFactory; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.user.api.service.UserService; public class DemoSessionCache { private static final ThreadLocal<DemoSessionData> BIZ_DATA_THREAD_LOCAL = new ThreadLocal<>(); public static PamirsUser getUser(){ return BIZ_DATA_THREAD_LOCAL.get()==null?null:BIZ_DATA_THREAD_LOCAL.get().getUser(); } public static void init(){ if(getUser()!=null){ return ; } Long uid = PamirsSession.getUserId(); if(uid == null){ return; } PamirsUser user = CommonApiFactory.getApi(UserService.class).queryById(uid); if(user!=null){ DemoSessionData demoSessionData = new DemoSessionData(); demoSessionData.setUser(user); BIZ_DATA_THREAD_LOCAL.set(demoSessionData); } } public static void clear(){ BIZ_DATA_THREAD_LOCAL.remove(); } } 图4-1-20-3 对DemoSessionData进行线程级缓存封装 Step3 新建DemoSessionHook 利用Hook机制,调用DemoSessionCache的init方法初始化DemoSessionData并放到ThreadLocal中。 @Hook(module= DemoModule.MODULE_MODULE), 规定只有增对DemoModule模块访问的请求该拦截器才会生效,不然其他模块的请求都会被DemoSessionHook拦截。 package pro.shushi.pamirs.demo.core.hook; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.session.DemoSessionCache; import pro.shushi.pamirs.meta.annotation.Hook; import pro.shushi.pamirs.meta.api.core.faas.HookBefore; import pro.shushi.pamirs.meta.api.dto.fun.Function; @Component public class DemoSessionHook implements HookBefore { @Override @Hook(priority = 1,module = DemoModule.MODULE_MODULE) public Object run(Function function, Object… args) { DemoSessionCache.init(); return function; } } 图4-1-20-4 新建DemoSessionHook Step4 新建DemoSessionApi package pro.shushi.pamirs.demo.core.session; import pro.shushi.pamirs.meta.api.CommonApi; import pro.shushi.pamirs.user.api.model.PamirsUser; public interface DemoSessionApi extends CommonApi { PamirsUser getUser(); } 图4-1-20-5 新建DemoSessionApi Step5…

    2024年5月23日
    81100
  • 业务域

    1. 业务域介绍 业务域是根据业务域对集成应用、开放接口进行归类管理。在创建集成应用、开发接口时,可选择归属的业务域。 操作入口:集成设计器——业务域。 2. 业务域管理 业务域管理提供新增、删除、搜索操作。 2.1 业务域列表 支持按照编码、名称、描述搜索业务域。 2.2 新增业务域 新增业务域:输入业务域名称、描述新增。 2.3 删除业务域 当前业务域未被其他数据记录引用时,可删除成功,反之如果被引用了,不允许删除。

    2024年6月20日
    92400

Leave a Reply

登录后才能评论