From 83b8543f18607da7ecc0ebd4b304c548c01c2bae Mon Sep 17 00:00:00 2001 From: LukewarmMe Date: Wed, 25 Mar 2026 19:13:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=93=E5=B1=95mybatis-plus-join=E7=9A=84?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E7=94=A8=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 + ruoyi-common/ruoyi-common-mybatis/pom.xml | 7 + .../com/github/yulichang/wrapper/ext/Ext.java | 199 ++++++++++++++++++ .../mybatis/core/mapper/BaseMapperPlus.java | 39 ++++ .../common/mybatis/helper/DataBaseHelper.java | 83 ++++++++ 5 files changed, 335 insertions(+) create mode 100644 ruoyi-common/ruoyi-common-mybatis/src/main/java/com/github/yulichang/wrapper/ext/Ext.java diff --git a/pom.xml b/pom.xml index 8b589aa5d..4c020da75 100644 --- a/pom.xml +++ b/pom.xml @@ -208,6 +208,13 @@ com.github.yulichang mybatis-plus-join-boot-starter ${mybatis-plus-join.version} + + + + com.github.yulichang + mybatis-plus-join-wrapper-ext + + diff --git a/ruoyi-common/ruoyi-common-mybatis/pom.xml b/ruoyi-common/ruoyi-common-mybatis/pom.xml index c6b69a558..16364a861 100644 --- a/ruoyi-common/ruoyi-common-mybatis/pom.xml +++ b/ruoyi-common/ruoyi-common-mybatis/pom.xml @@ -45,6 +45,13 @@ com.github.yulichang mybatis-plus-join-boot-starter + + + + com.github.yulichang + mybatis-plus-join-wrapper-ext + + diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/github/yulichang/wrapper/ext/Ext.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/github/yulichang/wrapper/ext/Ext.java new file mode 100644 index 000000000..cb8ae0f8b --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/com/github/yulichang/wrapper/ext/Ext.java @@ -0,0 +1,199 @@ +package com.github.yulichang.wrapper.ext; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.github.yulichang.wrapper.interfaces.IExt; +import org.apache.commons.lang3.ArrayUtils; +import org.dromara.common.mybatis.helper.DataBaseHelper; + +import java.util.Collection; + +/** + * 扩展接口,提供基于MPJ额外的查询方法 + * + * @author LukewarmMe + * @since 2026/3/25 + */ +public interface Ext> extends IExt { + + /** + * JSON字段属性非空查询 + * 假如 SYS_USER 中有一个数据库类型为 JSON 的字段 USER_EXT_INFO + * 实体类中为 private UserExtInfo userExtInfo,UserExtInfo对象有属性workNumber + * 使用方式:wrapper.jsonIsNotNull(SysUser::getUserExtInfo, UserExtInfo::getWorkNumber) + * + * @param jsonColumn 实体中JSON类型字段 + * @param jsonProperty JSON内部对象的属性Lambda + */ + default Children jsonIsNotNull(SFunction jsonColumn, SFunction jsonProperty) { + getChildren().applyFunc( + DataBaseHelper.buildJsonIsNotNullSql(jsonProperty), + arg -> arg.accept(jsonColumn) + ); + return getChildren(); + } + + /** + * JSON字段等值查询 + * 使用方法 wrapper.jsonEqIfExists(SysUser::getUserExtInfo, UserExtInfo::getWorkNumber, userExtInfo.getWorkNumber()); + * + * @param jsonColumn 实体类中JSON类型的字段 + * @param jsonProperty JSON内部对象的属性Lambda + * @param jsonBean JSON对象(传整个对象,无需get取值) + */ + default Children jsonEqIfExists(SFunction jsonColumn, SFunction jsonProperty, E jsonBean) { + // 对象为空直接跳过 + if (jsonBean == null) { + return getChildren(); + } + Object value = jsonProperty.apply(jsonBean); + // 值为空跳过 + if (ObjectUtil.isEmpty(value)) { + return getChildren(); + } + // 拼接SQL + getChildren().applyFunc( + DataBaseHelper.buildJsonEqSql(jsonProperty), + arg -> arg.accept(jsonColumn), + value + ); + return getChildren(); + } + + /** + * JSON字段模糊查询 + * 使用方法 wrapper.jsonLikeIfExists(SysUser::getUserExtInfo, UserExtInfo::getWorkNumber, userExtInfo.getWorkNumber()); + * + * @param jsonColumn 实体类中JSON类型的字段 + * @param jsonProperty JSON内部对象的属性Lambda + * @param jsonBean JSON对象(传整个对象,无需get取值) + */ + default Children jsonLikeIfExists(SFunction jsonColumn, SFunction jsonProperty, E jsonBean) { + // 对象为空直接跳过 + if (jsonBean == null) { + return getChildren(); + } + Object value = jsonProperty.apply(jsonBean); + // 值为空跳过 + if (ObjectUtil.isEmpty(value)) { + return getChildren(); + } + // 拼接SQL + getChildren().applyFunc( + DataBaseHelper.buildJsonLikeSql(jsonProperty), + arg -> arg.accept(jsonColumn), + "%" + value.toString().trim() + "%" + ); + return getChildren(); + } + + + /** + * 根据给定的列名和祖先值查找集合中是否存在匹配的记录 + * + * @param column 祖级列表字段 + * @param ancestorsValue 要查询的值 + */ + default Children findInSetIfExists(SFunction column, Object ancestorsValue) { + // 调用getChildren()获取Children对象,并调用其findInSet方法 + // 查询条件为:ancestorsValue不为null时才进行查询 + getChildren().findInSet(ObjectUtil.isNotNull(ancestorsValue), column, ancestorsValue); + // 返回Children对象以支持链式调用 + return getChildren(); + } + + /** + * 根据条件执行实现 FIND_IN_SET 语句片段 + * + * @param condition 是否执行该查询的条件 + * @param column 祖级列表字段 + * @param ancestorsValue 要查询的值 + */ + default Children findInSet(boolean condition, SFunction column, Object ancestorsValue) { + if (condition) { + getChildren().findInSet(column, ancestorsValue); + } + return getChildren(); + } + + /** + * 实现 FIND_IN_SET 语句片段 + * 例如:根据父部门ID查询其所有子部门的列表 + * new MPJLambdaWrapper() + * .select(SysDept::getDeptId) + * .findInSet(SysDept::getAncestors, parentId) + * + * @param column 祖级列表字段 + * @param ancestorsValue 要查询的值 + */ + default Children findInSet(SFunction column, Object ancestorsValue) { + getChildren().applyFunc( + DataBaseHelper.findInSet(), + arg -> arg.accept(column), + ancestorsValue); + return getChildren(); + } + + + /** + * 如果值存在才进行的时间范围查询 + * + * @param timeFunc 时间查询字段 + * @param start 开始时间 + * @param end 结束时间 + */ + default Children betweenIfExists(SFunction timeFunc, Object start, Object end) { + if (ObjectUtil.isNotNull(start) && ObjectUtil.isNotNull(end)) { + getChildren().between(timeFunc, start, end); + return getChildren(); + } + if (ObjectUtil.isNotNull(start)) { + getChildren().ge(timeFunc, start); + return getChildren(); + } + if (ObjectUtil.isNotNull(end)) { + getChildren().le(timeFunc, end); + return getChildren(); + } + return getChildren(); + } + + /** + * 时间范围查询 + * + * @param column 时间查询字段 + * @param values 时间范围 + */ + default Children betweenIfExists(SFunction column, Object[] values) { + Object val1 = ArrayUtils.get(values, 0); + Object val2 = ArrayUtils.get(values, 1); + return betweenIfExists(column, val1, val2); + } + + /** + * 当传入的集合不为空时才进行in查询 + * + * @param column 查询字段 + * @param coll 集合 + */ + default Children inIfExists(SFunction column, Collection coll) { + if (ObjectUtil.isNotEmpty(coll)) { + getChildren().in(column, coll); + } + return getChildren(); + } + + /** + * 当传入的集合不为空时才进行not in查询 + * + * @param column 列 + * @param coll 集合 + */ + default Children notInIfExists(SFunction column, Collection coll) { + if (ObjectUtil.isNotEmpty(coll)) { + getChildren().notIn(column, coll); + } + return getChildren(); + } +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java index 93541188d..356c30ee7 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java @@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.toolkit.Db; import com.github.yulichang.base.MPJBaseMapper; +import com.github.yulichang.wrapper.MPJLambdaWrapper; import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.LogFactory; import org.dromara.common.core.utils.MapstructUtils; @@ -331,4 +332,42 @@ public interface BaseMapperPlus extends MPJBaseMapper { return StreamUtils.toList(this.selectObjs(wrapper), mapper); } + /** + * 根据条件查询单个VO对象(MPJ版本可减少非VO字段查询) + * + * @param wrapper MPJ查询条件Wrapper + * @return 查询到的单个VO对象 + * @author LukewarmMe + */ + default V selectVoOne(MPJLambdaWrapper wrapper) { + // 查询两个类的交集字段 + wrapper.selectAsClass(this.currentModelClass(), this.currentVoClass()); + return selectJoinOne(this.currentVoClass(), wrapper); + } + + /** + * 根据条件查询VO对象列表(MPJ版本可减少非VO字段查询) + * + * @param wrapper MPJ查询条件Wrapper + * @return 查询到的VO对象列表 + * @author LukewarmMe + */ + default List selectVoList(MPJLambdaWrapper wrapper) { + wrapper.selectAsClass(this.currentModelClass(), this.currentVoClass()); + return this.selectJoinList(this.currentVoClass(), wrapper); + } + + /** + * 根据条件分页查询VO对象列表(MPJ版本可减少非VO字段查询) + * + * @param page 分页信息 + * @param wrapper MPJ查询条件Wrapper + * @return 查询到的VO对象分页列表 + * @author LukewarmMe + */ + default

> P selectVoPage(P page, MPJLambdaWrapper wrapper) { + wrapper.selectAsClass(this.currentModelClass(), this.currentVoClass()); + return this.selectJoinPage(page, this.currentVoClass(), wrapper); + } + } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java index 1a923a30a..452bb87e3 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java @@ -2,6 +2,9 @@ package org.dromara.common.mybatis.helper; import cn.hutool.core.convert.Convert; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; +import com.baomidou.mybatisplus.core.toolkit.LambdaUtils; +import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.dromara.common.core.exception.ServiceException; @@ -81,4 +84,84 @@ public class DataBaseHelper { public static List getDataSourceNameList() { return new ArrayList<>(DS.getDataSources().keySet()); } + + + /** + * 获取findInSet所需的sql片段(使用MPJ的applyFunc来实现) + * @author LukewarmMe + */ + public static String findInSet() { + return switch (getDataBaseType()) { + case ORACLE -> "instr(','||%s||',', ',{0},') <> 0"; + case POSTGRE_SQL -> "strpos(','||%s||',', ',{0},') <> 0"; + case SQL_SERVER -> "charindex(',{0},', ','+%s+',') <> 0"; + default -> "find_in_set({0}, %s) > 0"; + }; + } + + /** + * 构建不同数据库的JSON等值查询SQL模板(使用MPJ的applyFunc来实现) + * @author LukewarmMe + */ + public static String buildJsonEqSql(SFunction jsonProperty) { + String jsonPath = lambdaToJsonPath(jsonProperty); + return switch (DataBaseHelper.getDataBaseType()) { + case ORACLE -> "json_value(%s, '$" + jsonPath + "') = {0}"; + case POSTGRE_SQL -> "%s->>'" + jsonPath.replaceFirst("\\.", "") + "' = {0}"; + case SQL_SERVER -> "JSON_VALUE(%s, '$" + jsonPath + "') = {0}"; + default -> "%s->>'$" + jsonPath + "' = {0}"; + }; + } + + /** + * 构建不同数据库的JSON模糊查询SQL模板(使用MPJ的applyFunc来实现) + * @author LukewarmMe + */ + public static String buildJsonLikeSql(SFunction jsonProperty) { + String jsonPath = lambdaToJsonPath(jsonProperty); + return switch (DataBaseHelper.getDataBaseType()) { + case ORACLE -> "json_value(%s, '$" + jsonPath + "') LIKE {0}"; + case POSTGRE_SQL -> "%s->>'" + jsonPath.replaceFirst("\\.", "") + "' LIKE {0}"; + case SQL_SERVER -> "JSON_VALUE(%s, '$" + jsonPath + "') LIKE {0}"; + default -> "%s->>'$" + jsonPath + "' LIKE {0}"; + }; + } + + /** + * 构建不同数据库的JSON非空判断SQL模板(使用MPJ的applyFunc来实现) + * @author LukewarmMe + */ + public static String buildJsonIsNotNullSql(SFunction jsonProperty) { + String jsonPath = lambdaToJsonPath(jsonProperty); + return switch (DataBaseHelper.getDataBaseType()) { + case ORACLE -> "json_value(%s, '$" + jsonPath + "') IS NOT NULL"; + case POSTGRE_SQL -> "%s->>'" + jsonPath.replaceFirst("\\.", "") + "' IS NOT NULL"; + case SQL_SERVER -> "JSON_VALUE(%s, '$" + jsonPath + "') IS NOT NULL"; + default -> "%s->>'$" + jsonPath + "' IS NOT NULL"; + }; + } + + /** + * 核心:Lambda 解析为 JSON 路径(复用原有逻辑,增加空值校验) + * 示例:SysUser中UserExtInfo::getWorkNumber → .workNumber + * @author LukewarmMe + */ + public static String lambdaToJsonPath(SFunction jsonProperty) { + LambdaMeta lambdaMeta = LambdaUtils.extract(jsonProperty); + String methodName = lambdaMeta.getImplMethodName(); + String propertyName; + if (methodName.startsWith("get")) { + propertyName = methodName.substring(3); + } else if (methodName.startsWith("is")) { + propertyName = methodName.substring(2); + } else if (methodName.startsWith("set")) { + propertyName = methodName.substring(3); + } else { + throw new ServiceException("不支持的Lambda方法类型,仅支持get/is/set开头:" + methodName); + } + if (!propertyName.isEmpty()) { + propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1); + } + return "." + propertyName; + } }