拓展mybatis-plus-join的一些用法

This commit is contained in:
LukewarmMe 2026-03-25 19:13:14 +08:00
parent bc827570e4
commit 83b8543f18
5 changed files with 335 additions and 0 deletions

View File

@ -208,6 +208,13 @@
<groupId>com.github.yulichang</groupId> <groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <artifactId>mybatis-plus-join-boot-starter</artifactId>
<version>${mybatis-plus-join.version}</version> <version>${mybatis-plus-join.version}</version>
<!--排除后重写扩展类到目录com.github.yulichang.wrapper.ext-->
<exclusions>
<exclusion>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-wrapper-ext</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- sql性能分析插件 --> <!-- sql性能分析插件 -->

View File

@ -45,6 +45,13 @@
<dependency> <dependency>
<groupId>com.github.yulichang</groupId> <groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <artifactId>mybatis-plus-join-boot-starter</artifactId>
<!--排除后重写扩展类到目录com.github.yulichang.wrapper.ext-->
<exclusions>
<exclusion>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-wrapper-ext</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<!-- sql性能分析插件 --> <!-- sql性能分析插件 -->

View File

@ -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<Children extends MPJLambdaWrapper<?>> extends IExt<Children> {
/**
* JSON字段属性非空查询
* 假如 SYS_USER 中有一个数据库类型为 JSON 的字段 USER_EXT_INFO
* 实体类中为 private UserExtInfo userExtInfoUserExtInfo对象有属性workNumber
* 使用方式wrapper.jsonIsNotNull(SysUser::getUserExtInfo, UserExtInfo::getWorkNumber)
*
* @param jsonColumn 实体中JSON类型字段
* @param jsonProperty JSON内部对象的属性Lambda
*/
default <T, E> Children jsonIsNotNull(SFunction<T, ?> jsonColumn, SFunction<E, ?> 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 <T, E> Children jsonEqIfExists(SFunction<T, ?> jsonColumn, SFunction<E, ?> 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 <T, E> Children jsonLikeIfExists(SFunction<T, ?> jsonColumn, SFunction<E, ?> 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 <T> Children findInSetIfExists(SFunction<T, ?> 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 <T> Children findInSet(boolean condition, SFunction<T, ?> column, Object ancestorsValue) {
if (condition) {
getChildren().findInSet(column, ancestorsValue);
}
return getChildren();
}
/**
* 实现 FIND_IN_SET 语句片段
* 例如根据父部门ID查询其所有子部门的列表
* new MPJLambdaWrapper<SysDept>()
* .select(SysDept::getDeptId)
* .findInSet(SysDept::getAncestors, parentId)
*
* @param column 祖级列表字段
* @param ancestorsValue 要查询的值
*/
default <T> Children findInSet(SFunction<T, ?> column, Object ancestorsValue) {
getChildren().applyFunc(
DataBaseHelper.findInSet(),
arg -> arg.accept(column),
ancestorsValue);
return getChildren();
}
/**
* 如果值存在才进行的时间范围查询
*
* @param timeFunc 时间查询字段
* @param start 开始时间
* @param end 结束时间
*/
default <T> Children betweenIfExists(SFunction<T, ?> 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 <T> Children betweenIfExists(SFunction<T, ?> 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 <T> Children inIfExists(SFunction<T, ?> column, Collection<?> coll) {
if (ObjectUtil.isNotEmpty(coll)) {
getChildren().in(column, coll);
}
return getChildren();
}
/**
* 当传入的集合不为空时才进行not in查询
*
* @param column
* @param coll 集合
*/
default <T> Children notInIfExists(SFunction<T, ?> column, Collection<?> coll) {
if (ObjectUtil.isNotEmpty(coll)) {
getChildren().notIn(column, coll);
}
return getChildren();
}
}

View File

@ -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.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.Db; import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.github.yulichang.base.MPJBaseMapper; import com.github.yulichang.base.MPJBaseMapper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.apache.ibatis.logging.Log; import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.logging.LogFactory;
import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.MapstructUtils;
@ -331,4 +332,42 @@ public interface BaseMapperPlus<T, V> extends MPJBaseMapper<T> {
return StreamUtils.toList(this.selectObjs(wrapper), mapper); return StreamUtils.toList(this.selectObjs(wrapper), mapper);
} }
/**
* 根据条件查询单个VO对象(MPJ版本可减少非VO字段查询)
*
* @param wrapper MPJ查询条件Wrapper
* @return 查询到的单个VO对象
* @author LukewarmMe
*/
default V selectVoOne(MPJLambdaWrapper<T> 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<V> selectVoList(MPJLambdaWrapper<T> 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 extends IPage<V>> P selectVoPage(P page, MPJLambdaWrapper<T> wrapper) {
wrapper.selectAsClass(this.currentModelClass(), this.currentVoClass());
return this.selectJoinPage(page, this.currentVoClass(), wrapper);
}
} }

View File

@ -2,6 +2,9 @@ package org.dromara.common.mybatis.helper;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; 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.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
@ -81,4 +84,84 @@ public class DataBaseHelper {
public static List<String> getDataSourceNameList() { public static List<String> getDataSourceNameList() {
return new ArrayList<>(DS.getDataSources().keySet()); 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 <E> String buildJsonEqSql(SFunction<E, ?> 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 <E> String buildJsonLikeSql(SFunction<E, ?> 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 <E> String buildJsonIsNotNullSql(SFunction<E, ?> 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 <E> String lambdaToJsonPath(SFunction<E, ?> 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;
}
} }