多模型联表查询

多模型联表查询

  • 多对一或者一对一关联关系,通过关联模型的字段查询数据

模型结构定义

模型A

@Model(displayName = "A")
@Model.model(A.MODEL_MODEL)
public class A extends IdModel {
    public final static String MODEL_MODEL = "test.A";

    @Field(displayName = "b")
    @Field.many2one
    @Field.Relation(relationFields = {"bId"}, referenceFields = {"id"})
    private B b;

    @Field(displayName = "bId")
    @Field.Integer
    private Long bId;

    @Field(displayName = "B审批状态")
    @Field.Enum
    @Field.Related(related = {"b", "approvalEnum"})
    private ApprovalEnum approvalEnum;
}

模型B

@Model(displayName = "B")
@Model.model(B.MODEL_MODEL)
public class B extends IdModel {
      public final static String MODEL_MODEL = "test.B";

    @Field(displayName = "审批状态")
    @Field.Enum
    private ApprovalEnum approvalEnum;
}

页面设计

在界面设计器中, 设计相对应的表格页面。 A模型related字段拖到搜索栏中。
多模型联表查询

发布页面

自定义Hook

import cz.jirutka.rsql.parser.ast.RSQLOperators;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Component;
import pro.shushi.pamirs.framework.connectors.data.sql.AbstractWrapper;
import pro.shushi.pamirs.framework.connectors.data.sql.query.QueryWrapper;
import pro.shushi.pamirs.meta.annotation.Hook;
import pro.shushi.pamirs.meta.api.Models;
import pro.shushi.pamirs.meta.api.core.faas.HookBefore;
import pro.shushi.pamirs.meta.api.core.orm.convert.ClientDataConverter;
import pro.shushi.pamirs.meta.api.core.orm.template.context.ModelComputeContext;
import pro.shushi.pamirs.meta.api.dto.config.ModelConfig;
import pro.shushi.pamirs.meta.api.dto.config.ModelFieldConfig;
import pro.shushi.pamirs.meta.api.dto.fun.Function;
import pro.shushi.pamirs.meta.api.session.PamirsSession;
import pro.shushi.pamirs.meta.base.D;
import pro.shushi.pamirs.meta.common.spi.Spider;
import pro.shushi.pamirs.meta.domain.model.ModelField;
import pro.shushi.pamirs.meta.enmu.TtypeEnum;
import pro.shushi.pamirs.resource.api.constants.FieldConstants;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 通用 queryData处理。
 */
@Slf4j
@Component
public class QueryDataHook implements HookBefore {

    @Override
    @Hook(priority = 30)
    public Object run(Function function, Object... args) {

        getValueByType(args);

        return function;
    }

    private void getValueByType(Object... args) {

        if (ArrayUtils.isEmpty(args)) {
            return;
        }

        for (int index = 0; index < args.length && null != args[index]; ++index) {
            if (args[index] instanceof AbstractWrapper) {
                AbstractWrapper<?, ?, ?> wrapper = (AbstractWrapper<?, ?, ?>) args[index];
                Map<String, Object> queryData = wrapper.getQueryData();
                if (null == queryData) {
                    continue;
                }
                //有非存储字段
                ModelConfig modelConfig = PamirsSession.getContext().getModelConfig(wrapper.getModel());
                List<ModelFieldConfig> modelFieldConfigs = modelConfig.getModelFieldConfigList();
                Map<String, ModelField> modelFieldMap = new HashMap<>();
                for (ModelFieldConfig modelFieldConfig : modelFieldConfigs) {
                    modelFieldMap.put(modelFieldConfig.getField(), modelFieldConfig.getModelField());
                }
                for (Map.Entry<String, Object> entry : queryData.entrySet()) {
                    String key = entry.getKey();
                    ModelField field = modelFieldMap.get(key);
                    if (field != null && TtypeEnum.isRelatedType(field.getTtype().value())) {
                        //非存储字段是related字段
                        List<String> relateds = field.getRelated();
                        if (relateds != null && relateds.size() == 2) {
                            ModelField reletedField = modelFieldMap.get(relateds.get(0));
                            if (reletedField != null && TtypeEnum.isRelationType(reletedField.getTtype())) {
                                //是关系字段
                                String reletedModelQueryField = relateds.get(1);
                                String referenceModel = reletedField.getReferences();
                                ModelFieldConfig relatedFieldCfg = PamirsSession.getContext().getModelField(referenceModel, reletedModelQueryField);
                                Map<String, Object> relationMap = new HashMap<>();
                                Object value = entry.getValue();
                                relationMap.put(relatedFieldCfg.getField(), value);
                                relationMap.put(FieldConstants._dFieldName, referenceModel);
                                Object relationObj = Spider.getDefaultExtension(ClientDataConverter.class).in(new ModelComputeContext(), referenceModel, relationMap);
                                if (relationObj instanceof D) {
                                    relationMap = ((D) relationObj).get_d();
                                } else {
                                    relationMap = (Map<String, Object>) relationObj;
                                }

                                String column = relatedFieldCfg.getColumn();
                                QueryWrapper<D> wrapper1 = new QueryWrapper<D>();
                                wrapper1.from(referenceModel);
                                wrapper1.eq(column, relationMap.get(relatedFieldCfg.getField()));
                                List<D> values = Models.data().queryListByWrapper(wrapper1);
                                List<String> referenceFields = reletedField.getReferenceFields();

                                StringBuilder rsqlBuilder = new StringBuilder(wrapper.getRsql());
                                for (String refField : referenceFields) {
                                    List<Object> list = new ArrayList<>();
                                    for (D d : values) {
                                        Object v = d.get_d().get(refField);
                                        if (null != v) {
                                            list.add(v);
                                        }
                                    }

                                    if (CollectionUtils.isNotEmpty(list)) {

                                        if (CollectionUtils.size(list) > 10000) {
                                            log.warn("查询数量过多,请自定义查询逻辑,例如使用联表");
                                        }

                                        String vv = list.stream().map(String::valueOf).collect(Collectors.joining(","));

                                        rsqlBuilder.append(" and ")
                                                .append(reletedField.getRelationFields().get(0)).append(RSQLOperators.IN.getSymbol()).append("(").append(vv).append(")")
                                        ;
                                    } else {
                                        rsqlBuilder.append(" and (1!=1)");
                                    }

                                }
                                wrapper.setRsql(rsqlBuilder.toString());
                            }

                        }
                    }
                }
                break;
            }
        }
    }
}

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

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

(0)
yakir的头像yakir数式员工
上一篇 2025年1月9日 pm4:51
下一篇 2025年1月9日 pm8:38

相关推荐

  • action 和 function 有什么区别

    在 Oinone(开源低代码 / 企业应用开发平台) 里,Action 和 Function 都是“可被调用的逻辑单元”,但它们的定位和使用场景不同。可以简单理解为: Function = 纯逻辑函数(偏后端能力) Action = 面向业务操作的动作(偏应用行为 / UI触发) 下面给你详细对比一下。 1️⃣ Function:函数(逻辑能力) Function 更像是一个 可复用的服务方法。 特点 通常是 纯逻辑处理 不直接绑定 UI 可以被 Action / Service / 其他 Function 调用 用来封装 业务计算或工具逻辑 常见用途 比如: 价格计算 数据校验 数据转换 调用第三方 API 复杂业务规则 示例 @Function(openLevel = FunctionOpenEnum.API) @Function.Advanced(type = FunctionTypeEnum.QUERY) public TradeOrder computePrice(TradeOrder data) { return data; } 用途: 订单金额计算逻辑 然后可能被多个地方调用: Action -> 调用 Function Service -> 调用 Function Workflow -> 调用 Function 📌 核心:可复用业务逻辑 2️⃣ Action:动作(业务操作) Action 是一个 业务动作,通常是 用户触发的行为。 特点 通常绑定 UI 可以在 按钮 / 菜单 / API / 工作流 中触发 通常操作 模型数据 可以调用 Function 常见用途 例如: 创建订单 提交审批 发布文章 批量删除 导入数据 示例 @Action public void submitOrder(Order order){ order.setStatus("SUBMITTED"); } UI 可能是: 订单详情页 [提交订单] 按钮 点击按钮 → 调用 Action。 📌 核心:业务行为入口 3️⃣ 核心区别总结 维度 Action Function 定位 业务动作 逻辑函数 是否绑定 UI 通常是 否 是否直接给用户操作 是 否 是否可复用 一般 很高 是否操作模型 常见 不一定 调用关系 可调用 Function 不调用 Action 4️⃣ 调用关系(典型架构) 通常推荐的结构: UI按钮 ↓ Action(业务入口) ↓ Function(业务逻辑) ↓ DAO / Repository 例如: 提交订单按钮 ↓ submitOrderAction ↓ checkInventoryFunction calcPriceFunction createOrderFunction 这样: Action 只负责 流程 Function 负责 逻辑 代码会更清晰。 5️⃣…

    2026年3月12日
    25100
  • 模型字段之序列化方式

    本文核心是带大家全面了解oinone的序列方式,包括支持的序列化类型、注意点、如果新增客户化序列化方式以及字段默认值的反序列化。 字段序列化方式说明 序列化方式 说明 备注 JSON JSON序列化 主要用于模型相关类型字段的序列化,是@Field.serialize默认选项 DOT 点拼接集合元素 COMMA 逗号拼接集合元素 BIT 按位与,2次幂数求和 非@Field.serialize可选项列表,用于二进制枚举序列化不需要配置,由oinone自动推断 字段序列化方式举例 1、给模型PetItemDetail 增加两个字段:petItemDetails类型为List 和 tags类型为List,并设置为不同的序列化方式,petItemDetails为JSON(缺省就是JSON,可不配),tags为COMMA。2、同时设置 @Field.Advanced(columnDefinition = "varchar(1024)"),防止序列化后存储过长。 @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 = "品种") @Field.many2one @Field.Relation(relationFields = {"typeId"},referenceFields = {"id"}) private PetType type; @Field(displayName = "品种类型",invisible = true) private Long typeId; @Field(displayName = "详情", serialize = Field.serialize.JSON, store = NullableBoolEnum.TRUE) @Field.Advanced(columnDefinition = "varchar(1024)") private List<PetItemDetail> petItemDetails; @Field(displayName = "商品标签",serialize = Field.serialize.COMMA,store = NullableBoolEnum.TRUE,multi = true) @Field.Advanced(columnDefinition = "varchar(1024)") private List<String> tags; } 字段序列化注意点 必须使用Field#store属性将字段存储设置为NullableBoolEnum.TRUE。 使用Field#serialize属性指定序列化方式,默认为JSON。 如把PetItemDetail设置为存储模型,须在PetItem的petItemDetails字段上使用Field.Relation#store属性将关联关系存储设置为false。不然会同时存储petItemDetails字段和对应的PetItemDetail表记录 注册自己的序列化器 注册自己的序列化器(实现pro.shushi.pamirs.meta.api.core.orm.serialize.Serializer接口), 如oinone的DOT的序列化方式,用type()方法返回值做匹配,serialize和deserialize分别对应序列化和反序列化方法。 package pro.shushi.pamirs.framework.compute.serialize; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.core.orm.serialize.Serializer; import pro.shushi.pamirs.meta.common.constants.CharacterConstants; import pro.shushi.pamirs.meta.enmu.SerializeEnum; import pro.shushi.pamirs.meta.util.TypeUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 点表达式序列生成处理器实现 * @author shushi@shushi.pro * @version 1.0.0 */ @SuppressWarnings("rawtypes") @Slf4j @Component public class DotSerializeProcessor implements Serializer<Object, String> { @Override public String serialize(String ltype, Object value) { if (null == value) { return null; } if (List.class.isAssignableFrom(value.getClass())) { return StringUtils.join((List) value, CharacterConstants.SEPARATOR_DOT); } else { return StringUtils.join(Collections.singletonList(value), CharacterConstants.SEPARATOR_DOT); } } @SuppressWarnings("unchecked") @Override public Object deserialize(String ltype, String ltypeT, String value,…

    2024年5月24日
    1.9K00
  • JSON转换工具类

    JSON转换工具类 JSON转对象 pro.shushi.pamirs.meta.util.JsonUtils JSON转模型 pro.shushi.pamirs.framework.orm.json.PamirsDataUtils

    2023年11月1日
    2.0K00
  • Oinone环境保护(v5.2.3以上)

    概述 Oinone平台为合作伙伴提供了环境保护功能,以确保在一套环境可以在较为安全前提下修改配置文件,启动多个JVM等部署操作。 本章内容主要介绍与环境保护功能相关的启动参数。 名词解释 本地开发环境:开发人员在本地启动业务工程的环境 公共环境:包含设计器镜像和业务工程的环境 环境保护参数介绍 【注意】参数是加在程序实参 (Program arguments)上,通常可能错误的加在Active Profiles上了 -PenvProtected=${value} 是否启用环境保护,默认为true。 环境保护是通过与最近一次保存在数据库的base_platform_environment表中数据进行比对,并根据每个参数的配置特性进行判断,在启动时将有错误的内容打印在启动日志中,以便于开发者进行问题排查。 除此之外,环境保护功能还提供了一些生产配置的优化建议,开发者可以在启动时关注这些日志,从而对生产环境的配置进行调优。 -PsaveEnvironments=${value} 是否将此次启动的环境参数保存到数据库,默认为true。 在某些特殊情况下,为了避免公共环境中的保护参数发生不必要的变化,我们可以选择不保存此次启动时的配置参数到数据库中,这样就不会影响其他JVM启动时发生校验失败而无法启动的问题。 -PstrictProtected=${value} 是否使用严格校验模式,默认为false 通常我们建议在公共环境启用严格校验模式,这样可以最大程度的保护公共环境的元数据不受其他环境干扰。 PS:在启用严格校验模式时,需避免内外网使用不同连接地址的场景。如无法避免,则无法启用严格校验模式。 常见问题 需要迁移数据库,并更换了数据库连接地址该如何操作? 将原有数据库迁移到新数据库。 修改配置文件中数据库的连接地址。 在启动脚本中增加-PenvProtected=false关闭环境保护。 启动JVM服务可以看到有错误的日志提示,但不会中断本次启动。 移除启动脚本中的-PenvProtected=false或将值改为true,下次启动时将继续进行环境保护检查。 可查看数据库中base_platform_environment表中对应数据库连接配置已发生修改,此时若其他JVM在启动前未正确修改,则无法启动。 本地开发时需要修改Redis连接地址到本地,但希望不影响公共环境的使用该如何操作? PS:由于Redis中的元数据缓存是根据数据库差量进行同步的,此操作会导致公共环境在启动时无法正确刷新Redis中的元数据缓存,需要配合pamirs.distribution.session.allMetaRefresh参数进行操作。如无特殊必要,我们不建议使用该形式进行协同开发,多次修改配置会导致出错的概率增加。 本地环境首次启动时,除了修改Redis相关配置外,还需要配置pamirs.distribution.session.allMetaRefresh=true,将本地新连接的Redis进行初始化。 在本地启动时,增加-PenvProtected=false -PsaveEnvironments=false启动参数,以确保本地启动不会修改公共环境的配置,并且可以正常通过环境保护检测。 本地环境成功启动并正常开发功能后,需要发布到公共环境进行测试时,需要先修改公共环境中业务工程配置pamirs.distribution.session.allMetaRefresh=true后,再启动业务工程。 启动一次业务工程后,将配置还原为pamirs.distribution.session.allMetaRefresh=false。

    2024年10月21日
    1.6K00
  • 自定义createorupdate方法时,关联模型数据怎么保存?

    需要自己手动增加保存关联模型数据的逻辑。 多对一、一对一以及一对多 可直接用fieldSave进行保存即可 //如 data.fieldSave(PamirsEmployee::getPositions); 多对多 需要对数据进行处理,前端提交过来的数据,进行判断,是新增还是修改,或者删除

    2023年11月1日
    1.6K00

Leave a Reply

登录后才能评论