diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/LambdaCrudChainWrapper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/LambdaCrudChainWrapper.java index 10cde6708..f696aa467 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/LambdaCrudChainWrapper.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/LambdaCrudChainWrapper.java @@ -13,7 +13,10 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import org.dromara.common.mybatis.core.query.AggregateSelectUtils; import org.dromara.common.mybatis.core.query.LambdaQueryCondition; +import org.dromara.common.mybatis.core.query.SqlAggregateFunction; +import org.dromara.common.mybatis.core.query.SubQuery; import java.math.BigDecimal; import java.util.ArrayList; @@ -21,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -124,6 +128,338 @@ public class LambdaCrudChainWrapper extends AbstractLambdaWrapper selectSum(SFunction column) { + return selectSum(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 SUM 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return this + */ + public LambdaCrudChainWrapper selectSum(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.SUM, column, alias); + } + + /** + * 指定 SUM 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return this + */ + public LambdaCrudChainWrapper selectSum(SFunction column, SFunction alias) { + return selectSum(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @return this + */ + public LambdaCrudChainWrapper selectMax(SFunction column) { + return selectMax(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return this + */ + public LambdaCrudChainWrapper selectMax(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.MAX, column, alias); + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return this + */ + public LambdaCrudChainWrapper selectMax(SFunction column, SFunction alias) { + return selectMax(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @return this + */ + public LambdaCrudChainWrapper selectMin(SFunction column) { + return selectMin(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return this + */ + public LambdaCrudChainWrapper selectMin(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.MIN, column, alias); + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return this + */ + public LambdaCrudChainWrapper selectMin(SFunction column, SFunction alias) { + return selectMin(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @return this + */ + public LambdaCrudChainWrapper selectAvg(SFunction column) { + return selectAvg(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return this + */ + public LambdaCrudChainWrapper selectAvg(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.AVG, column, alias); + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return this + */ + public LambdaCrudChainWrapper selectAvg(SFunction column, SFunction alias) { + return selectAvg(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @return this + */ + public LambdaCrudChainWrapper selectCount(SFunction column) { + return selectCount(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return this + */ + public LambdaCrudChainWrapper selectCount(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.COUNT, column, alias); + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return this + */ + public LambdaCrudChainWrapper selectCount(SFunction column, SFunction alias) { + return selectCount(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 COUNT(*) 聚合查询字段。 + * + * @param alias 查询别名 + * @return this + */ + public LambdaCrudChainWrapper selectCountAll(String alias) { + return selectAggregate(SqlAggregateFunction.COUNT, "*", alias); + } + + /** + * 指定 COUNT(*) 聚合查询字段。 + * + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return this + */ + public LambdaCrudChainWrapper selectCountAll(SFunction alias) { + return selectCountAll(AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 COUNT(DISTINCT column) 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return this + */ + public LambdaCrudChainWrapper selectCountDistinct(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.COUNT, "DISTINCT " + columnToString(column), alias); + } + + /** + * 指定 COUNT(DISTINCT column) 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return this + */ + public LambdaCrudChainWrapper selectCountDistinct(SFunction column, SFunction alias) { + return selectCountDistinct(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定子查询字段。 + *
{@code
+     * userMapper.lambda()
+     *     .select(SysUser::getUserId, SysUser::getUserName)
+     *     .selectSub(SysUserRole.class, sub -> sub
+     *         .selectCountAll()
+     *         .eqColumn(SysUserRole::getUserId, SysUser::getUserId),
+     *         UserStatVo::getRoleCount)
+     *     .voList();
+     * }
+ * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param alias 查询别名 + * @param 子查询实体类型 + * @return this + */ + public LambdaCrudChainWrapper selectSub(Class entityClass, Consumer> consumer, String alias) { + sqlSelect.setStringValue(AggregateSelectUtils.appendSelect(sqlSelect.getStringValue(), + AggregateSelectUtils.subquerySelect(buildSubQuery(entityClass, consumer), alias))); + return typedThis; + } + + /** + * 指定子查询字段。 + * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param alias 查询别名字段 + * @param 子查询实体类型 + * @param
查询结果类型 + * @return this + */ + public LambdaCrudChainWrapper selectSub(Class entityClass, Consumer> consumer, SFunction alias) { + return selectSub(entityClass, consumer, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 添加等于子查询条件。 + *
{@code
+     * userMapper.lambda()
+     *     .eqSub(SysUser::getDeptId, SysDept.class, sub -> sub
+     *         .select(SysDept::getDeptId)
+     *         .eq(SysDept::getDeptName, deptName))
+     *     .voList();
+     * }
+ * + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return this + */ + public LambdaCrudChainWrapper eqSub(SFunction column, Class entityClass, Consumer> consumer) { + return super.eqSql(true, column, buildSubQuery(entityClass, consumer)); + } + + /** + * 添加 IN 子查询条件。 + *
{@code
+     * userMapper.lambda()
+     *     .inSub(SysUser::getUserId, SysUserRole.class, sub -> sub
+     *         .select(SysUserRole::getUserId)
+     *         .eq(SysUserRole::getRoleId, roleId))
+     *     .voList();
+     * }
+ * + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return this + */ + public LambdaCrudChainWrapper inSub(SFunction column, Class entityClass, Consumer> consumer) { + return super.inSql(true, column, buildSubQuery(entityClass, consumer)); + } + + /** + * 添加 NOT IN 子查询条件。 + * + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return this + */ + public LambdaCrudChainWrapper notInSub(SFunction column, Class entityClass, Consumer> consumer) { + return super.notInSql(true, column, buildSubQuery(entityClass, consumer)); + } + + /** + * 添加 EXISTS 子查询条件。 + *
{@code
+     * userMapper.lambda()
+     *     .existsSub(SysUserRole.class, sub -> sub
+     *         .selectCountAll()
+     *         .eqColumn(SysUserRole::getUserId, SysUser::getUserId)
+     *         .eq(SysUserRole::getRoleId, roleId))
+     *     .voList();
+     * }
+ * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return this + */ + public LambdaCrudChainWrapper existsSub(Class entityClass, Consumer> consumer) { + return super.exists(true, buildSubQuery(entityClass, consumer)); + } + + /** + * 添加 NOT EXISTS 子查询条件。 + * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return this + */ + public LambdaCrudChainWrapper notExistsSub(Class entityClass, Consumer> consumer) { + return super.notExists(true, buildSubQuery(entityClass, consumer)); + } + /** * 按字段过滤条件选择查询字段。 * @@ -153,6 +489,46 @@ public class LambdaCrudChainWrapper extends AbstractLambdaWrapper selectAggregate(SqlAggregateFunction function, SFunction column, String alias) { + return selectAggregate(function, columnToString(column), alias); + } + + /** + * 指定聚合查询字段。 + * + * @param function 聚合函数 + * @param column 聚合字段 SQL + * @param alias 查询别名 + * @return this + */ + private LambdaCrudChainWrapper selectAggregate(SqlAggregateFunction function, String column, String alias) { + sqlSelect.setStringValue(AggregateSelectUtils.appendSelect(sqlSelect.getStringValue(), + AggregateSelectUtils.aggregateSelect(function, column, alias))); + return typedThis; + } + + /** + * 构建子查询 SQL。 + * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 子查询 SQL + */ + private String buildSubQuery(Class entityClass, Consumer> consumer) { + SubQuery subQuery = SubQuery.of(entityClass, value -> formatParam(null, value)); + consumer.accept(subQuery); + return subQuery.build(); + } + /** * 按条件设置更新字段。 * diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/AggregateLambdaQueryWrapper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/AggregateLambdaQueryWrapper.java new file mode 100644 index 000000000..66d22d929 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/AggregateLambdaQueryWrapper.java @@ -0,0 +1,92 @@ +package org.dromara.common.mybatis.core.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; + +/** + * 支持追加聚合查询字段的 Lambda 查询包装器。 + * + * @param 实体类型 + * @author Lion Li + */ +class AggregateLambdaQueryWrapper extends LambdaQueryWrapper { + + private String aggregateSqlSelect; + + AggregateLambdaQueryWrapper(Class entityClass) { + super(entityClass); + } + + /** + * 追加聚合查询字段。 + * + * @param columnSql 查询字段 SQL + */ + void appendSelectSql(String columnSql) { + aggregateSqlSelect = AggregateSelectUtils.appendSelect(getSqlSelect(), columnSql); + } + + /** + * 追加子查询字段。 + * + * @param subquerySql 子查询 SQL + * @param alias 查询别名 + * @param params 子查询参数 + */ + void appendSelectSub(String subquerySql, String alias, Object... params) { + appendSelectSql(AggregateSelectUtils.subquerySelect(formatSqlMaybeWithParam(subquerySql, params), alias)); + } + + /** + * 格式化子查询 SQL。 + * + * @param subquerySql 子查询 SQL + * @param params 子查询参数 + * @return 格式化后的子查询 SQL + */ + String formatSubquerySql(String subquerySql, Object... params) { + return formatSqlMaybeWithParam(subquerySql, params); + } + + /** + * 格式化子查询参数。 + * + * @param value 参数值 + * @return MyBatis 参数占位符 + */ + String formatSubqueryParam(Object value) { + return formatParam(null, value); + } + + /** + * 清空聚合查询字段。 + */ + void resetAggregateSelect() { + aggregateSqlSelect = null; + } + + /** + * 获取字段对应的数据库列名。 + * + * @param column 字段 + * @return 数据库列名 + */ + String columnName(SFunction column) { + return columnToString(column); + } + + @Override + public String getSqlSelect() { + if (aggregateSqlSelect != null) { + return aggregateSqlSelect; + } + return super.getSqlSelect(); + } + + @Override + public void clear() { + super.clear(); + aggregateSqlSelect = null; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/AggregateSelectUtils.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/AggregateSelectUtils.java new file mode 100644 index 000000000..f048470e1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/AggregateSelectUtils.java @@ -0,0 +1,84 @@ +package org.dromara.common.mybatis.core.query; + +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import org.apache.ibatis.reflection.property.PropertyNamer; + +import java.util.regex.Pattern; + +/** + * 聚合查询字段 SQL 构造工具。 + * + * @author Lion Li + */ +public final class AggregateSelectUtils { + + private static final Pattern ALIAS_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*"); + + private AggregateSelectUtils() { + } + + /** + * 追加查询字段 SQL。 + * + * @param current 已有查询字段 SQL + * @param fragment 新增查询字段 SQL + * @return 合并后的查询字段 SQL + */ + public static String appendSelect(String current, String fragment) { + if (StringUtils.isBlank(current)) { + return fragment; + } + return current + Constants.COMMA + fragment; + } + + /** + * 生成聚合查询字段 SQL。 + * + * @param function 聚合函数 + * @param column 字段 SQL + * @param alias 查询别名 + * @return 聚合查询字段 SQL + */ + public static String aggregateSelect(SqlAggregateFunction function, String column, String alias) { + return function.format(column) + " AS " + checkAlias(alias); + } + + /** + * 生成子查询字段 SQL。 + * + * @param subquerySql 子查询 SQL + * @param alias 查询别名 + * @return 子查询字段 SQL + */ + public static String subquerySelect(String subquerySql, String alias) { + return "(" + subquerySql + ") AS " + checkAlias(alias); + } + + /** + * 从 Lambda Getter 解析属性名作为查询别名。 + * + * @param alias 别名字段 Getter + * @return 查询别名 + */ + public static String aliasName(SFunction alias) { + LambdaMeta meta = com.baomidou.mybatisplus.core.toolkit.LambdaUtils.extract(alias); + return PropertyNamer.methodToProperty(meta.getImplMethodName()); + } + + /** + * 检查查询别名是否为通用 SQL 标识符。 + * + * @param alias 查询别名 + * @return 查询别名 + */ + public static String checkAlias(String alias) { + Assert.isTrue(StringUtils.isNotBlank(alias) && ALIAS_PATTERN.matcher(alias).matches(), + "查询别名只能包含字母、数字、下划线且不能以数字开头: %s", alias); + return alias; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/LambdaJoinQueryBuilder.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/LambdaJoinQueryBuilder.java index 9dbe5cf1a..0b74890b7 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/LambdaJoinQueryBuilder.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/LambdaJoinQueryBuilder.java @@ -1,10 +1,12 @@ package org.dromara.common.mybatis.core.query; import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.github.yulichang.toolkit.LambdaUtils; import com.github.yulichang.toolkit.support.ColumnCache; import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.github.yulichang.wrapper.enums.DefaultFuncEnum; import com.github.yulichang.wrapper.segments.SelectCache; import com.github.yulichang.wrapper.segments.SelectNormal; import org.dromara.common.core.utils.StringUtils; @@ -149,6 +151,343 @@ public final class LambdaJoinQueryBuilder { return this; } + /** + * 指定子查询字段。 + *
{@code
+     * QueryBuilder.lambdaJoin("u", SysUser.class)
+     *     .selectAs("u", SysUser::getUserId, UserStatVo::getUserId)
+     *     .selectSub(SysUserRole.class, sub -> sub
+     *         .selectCountAll()
+     *         .eqColumn(SysUserRole::getUserId, "u", SysUser::getUserId),
+     *         UserStatVo::getRoleCount)
+     *     .list(UserStatVo.class);
+     * }
+ * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param alias 查询别名 + * @param 子查询实体类型 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectSub(Class entityClass, Consumer> consumer, String alias) { + SubQuery subQuery = buildPlaceholderSubQuery(entityClass, consumer); + wrapper.selectFunc("(" + subQuery.build() + ")", func -> func.values(subQuery.params()), + AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定子查询字段。 + * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param alias 查询别名字段 + * @param 子查询实体类型 + * @param 查询结果类型 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectSub(Class entityClass, Consumer> consumer, SFunction alias) { + return selectSub(entityClass, consumer, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 SUM 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectSum(SFunction column, String alias) { + wrapper.selectSum(column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 SUM 聚合查询字段。 + * + * @param tableAlias 表别名 + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectSum(String tableAlias, SFunction column, String alias) { + wrapper.selectFunc(DefaultFuncEnum.SUM, tableAlias, column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 SUM 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectSum(SFunction column, SFunction alias) { + return selectSum(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectMax(SFunction column, String alias) { + wrapper.selectMax(column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param tableAlias 表别名 + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectMax(String tableAlias, SFunction column, String alias) { + wrapper.selectFunc(DefaultFuncEnum.MAX, tableAlias, column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectMax(SFunction column, SFunction alias) { + return selectMax(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectMin(SFunction column, String alias) { + wrapper.selectMin(column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param tableAlias 表别名 + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectMin(String tableAlias, SFunction column, String alias) { + wrapper.selectFunc(DefaultFuncEnum.MIN, tableAlias, column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectMin(SFunction column, SFunction alias) { + return selectMin(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectAvg(SFunction column, String alias) { + wrapper.selectAvg(column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param tableAlias 表别名 + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectAvg(String tableAlias, SFunction column, String alias) { + wrapper.selectFunc(DefaultFuncEnum.AVG, tableAlias, column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectAvg(SFunction column, SFunction alias) { + return selectAvg(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectCount(SFunction column, String alias) { + wrapper.selectCount(column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param tableAlias 表别名 + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectCount(String tableAlias, SFunction column, String alias) { + wrapper.selectFunc(DefaultFuncEnum.COUNT, tableAlias, column, AggregateSelectUtils.checkAlias(alias)); + return this; + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectCount(SFunction column, SFunction alias) { + return selectCount(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 COUNT(*) 聚合查询字段。 + * + * @param alias 查询别名 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectCountAll(String alias) { + wrapper.select(AggregateSelectUtils.aggregateSelect(SqlAggregateFunction.COUNT, "*", alias)); + return this; + } + + /** + * 指定 COUNT(*) 聚合查询字段。 + * + * @param alias 查询别名字段 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder selectCountAll(SFunction alias) { + return selectCountAll(AggregateSelectUtils.aliasName(alias)); + } + + /** + * 添加等于子查询条件。 + * + * @param alias 表别名 + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 字段实体类型 + * @param 子查询实体类型 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder eqSub(String alias, SFunction column, Class entityClass, + Consumer> consumer) { + SubQuery subQuery = buildPlaceholderSubQuery(entityClass, consumer); + wrapper.apply(true, qualifiedColumn(alias, column) + " = (" + subQuery.build() + ")", subQuery.params()); + return this; + } + + /** + * 添加 IN 子查询条件。 + *
{@code
+     * QueryBuilder.lambdaJoin("u", SysUser.class)
+     *     .inSub("u", SysUser::getUserId, SysUserRole.class, sub -> sub
+     *         .select(SysUserRole::getUserId)
+     *         .eq(SysUserRole::getRoleId, roleId))
+     *     .list(SysUserVo.class);
+     * }
+ * + * @param alias 表别名 + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 字段实体类型 + * @param 子查询实体类型 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder inSub(String alias, SFunction column, Class entityClass, + Consumer> consumer) { + SubQuery subQuery = buildPlaceholderSubQuery(entityClass, consumer); + wrapper.apply(true, qualifiedColumn(alias, column) + " IN (" + subQuery.build() + ")", subQuery.params()); + return this; + } + + /** + * 添加 NOT IN 子查询条件。 + * + * @param alias 表别名 + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 字段实体类型 + * @param 子查询实体类型 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder notInSub(String alias, SFunction column, Class entityClass, + Consumer> consumer) { + SubQuery subQuery = buildPlaceholderSubQuery(entityClass, consumer); + wrapper.apply(true, qualifiedColumn(alias, column) + " NOT IN (" + subQuery.build() + ")", subQuery.params()); + return this; + } + + /** + * 添加 EXISTS 子查询条件。 + *
{@code
+     * QueryBuilder.lambdaJoin("u", SysUser.class)
+     *     .existsSub(SysUserRole.class, sub -> sub
+     *         .selectCountAll()
+     *         .eqColumn(SysUserRole::getUserId, "u", SysUser::getUserId)
+     *         .eq(SysUserRole::getRoleId, roleId))
+     *     .list(SysUserVo.class);
+     * }
+ * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder existsSub(Class entityClass, Consumer> consumer) { + SubQuery subQuery = buildPlaceholderSubQuery(entityClass, consumer); + wrapper.apply(true, "EXISTS (" + subQuery.build() + ")", subQuery.params()); + return this; + } + + /** + * 添加 NOT EXISTS 子查询条件。 + * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 当前联表查询构造辅助对象 + */ + public LambdaJoinQueryBuilder notExistsSub(Class entityClass, Consumer> consumer) { + SubQuery subQuery = buildPlaceholderSubQuery(entityClass, consumer); + wrapper.apply(true, "NOT EXISTS (" + subQuery.build() + ")", subQuery.params()); + return this; + } + /** * 添加左联表。 * @@ -577,4 +916,15 @@ public final class LambdaJoinQueryBuilder { return wrapper; } + private SubQuery buildPlaceholderSubQuery(Class entityClass, Consumer> consumer) { + SubQuery subQuery = SubQuery.ofPlaceholders(entityClass); + consumer.accept(subQuery); + return subQuery; + } + + private String qualifiedColumn(String alias, SFunction column) { + return AggregateSelectUtils.checkAlias(alias) + StringPool.DOT + ColumnCache.getMapField(LambdaUtils.getEntityClass(column)) + .get(LambdaUtils.getName(column)).getColumn(); + } + } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/LambdaQueryBuilder.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/LambdaQueryBuilder.java index a8e77015e..24fe8015c 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/LambdaQueryBuilder.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/LambdaQueryBuilder.java @@ -39,10 +39,347 @@ public final class LambdaQueryBuilder implements LambdaQueryCondition select(SFunction... columns) { + aggregateWrapper().resetAggregateSelect(); wrapper.select(columns); return this; } + /** + * 指定 SUM 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectSum(SFunction column) { + return selectSum(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 SUM 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectSum(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.SUM, column, alias); + } + + /** + * 指定 SUM 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param
查询结果类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectSum(SFunction column, SFunction alias) { + return selectSum(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectMax(SFunction column) { + return selectMax(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectMax(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.MAX, column, alias); + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectMax(SFunction column, SFunction alias) { + return selectMax(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectMin(SFunction column) { + return selectMin(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectMin(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.MIN, column, alias); + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectMin(SFunction column, SFunction alias) { + return selectMin(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectAvg(SFunction column) { + return selectAvg(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectAvg(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.AVG, column, alias); + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectAvg(SFunction column, SFunction alias) { + return selectAvg(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectCount(SFunction column) { + return selectCount(column, AggregateSelectUtils.aliasName(column)); + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectCount(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.COUNT, column, alias); + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectCount(SFunction column, SFunction alias) { + return selectCount(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 COUNT(*) 聚合查询字段。 + * + * @param alias 查询别名 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectCountAll(String alias) { + return selectAggregate(SqlAggregateFunction.COUNT, "*", alias); + } + + /** + * 指定 COUNT(*) 聚合查询字段。 + * + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectCountAll(SFunction alias) { + return selectCountAll(AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定 COUNT(DISTINCT column) 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectCountDistinct(SFunction column, String alias) { + return selectAggregate(SqlAggregateFunction.COUNT, "DISTINCT " + aggregateWrapper().columnName(column), alias); + } + + /** + * 指定 COUNT(DISTINCT column) 聚合查询字段。 + * + * @param column 聚合字段 + * @param alias 查询别名字段 + * @param 查询结果类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectCountDistinct(SFunction column, SFunction alias) { + return selectCountDistinct(column, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 指定子查询字段。 + *
{@code
+     * QueryBuilder.lambda(SysUser.class)
+     *     .select(SysUser::getUserId, SysUser::getUserName)
+     *     .selectSub(SysUserRole.class, sub -> sub
+     *         .selectCountAll()
+     *         .eqColumn(SysUserRole::getUserId, SysUser::getUserId),
+     *         UserStatVo::getRoleCount)
+     *     .build();
+     * }
+ * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param alias 查询别名 + * @param 子查询实体类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectSub(Class entityClass, Consumer> consumer, String alias) { + aggregateWrapper().appendSelectSql(AggregateSelectUtils.subquerySelect(buildSubQuery(entityClass, consumer), alias)); + return this; + } + + /** + * 指定子查询字段。 + * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param alias 查询别名字段 + * @param 子查询实体类型 + * @param
查询结果类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder selectSub(Class entityClass, Consumer> consumer, SFunction alias) { + return selectSub(entityClass, consumer, AggregateSelectUtils.aliasName(alias)); + } + + /** + * 添加等于子查询条件。 + *
{@code
+     * QueryBuilder.lambda(SysUser.class)
+     *     .eqSub(SysUser::getDeptId, SysDept.class, sub -> sub
+     *         .select(SysDept::getDeptId)
+     *         .eq(SysDept::getDeptName, deptName))
+     *     .build();
+     * }
+ * + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder eqSub(SFunction column, Class entityClass, Consumer> consumer) { + wrapper.eqSql(column, buildSubQuery(entityClass, consumer)); + return this; + } + + /** + * 添加 IN 子查询条件。 + *
{@code
+     * QueryBuilder.lambda(SysUser.class)
+     *     .inSub(SysUser::getUserId, SysUserRole.class, sub -> sub
+     *         .select(SysUserRole::getUserId)
+     *         .eq(SysUserRole::getRoleId, roleId))
+     *     .build();
+     * }
+ * + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder inSub(SFunction column, Class entityClass, Consumer> consumer) { + wrapper.inSql(column, buildSubQuery(entityClass, consumer)); + return this; + } + + /** + * 添加 NOT IN 子查询条件。 + * + * @param column 字段 + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder notInSub(SFunction column, Class entityClass, Consumer> consumer) { + wrapper.notInSql(column, buildSubQuery(entityClass, consumer)); + return this; + } + + /** + * 添加 EXISTS 子查询条件。 + *
{@code
+     * QueryBuilder.lambda(SysUser.class)
+     *     .existsSub(SysUserRole.class, sub -> sub
+     *         .selectCountAll()
+     *         .eqColumn(SysUserRole::getUserId, SysUser::getUserId)
+     *         .eq(SysUserRole::getRoleId, roleId))
+     *     .build();
+     * }
+ * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder existsSub(Class entityClass, Consumer> consumer) { + wrapper.exists(buildSubQuery(entityClass, consumer)); + return this; + } + + /** + * 添加 NOT EXISTS 子查询条件。 + * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 当前查询构造辅助对象 + */ + public LambdaQueryBuilder notExistsSub(Class entityClass, Consumer> consumer) { + wrapper.notExists(buildSubQuery(entityClass, consumer)); + return this; + } + /** * 添加等于条件。 * @@ -987,4 +1324,53 @@ public final class LambdaQueryBuilder implements LambdaQueryCondition selectAggregate(SqlAggregateFunction function, SFunction column, String alias) { + return selectAggregate(function, aggregateWrapper().columnName(column), alias); + } + + /** + * 指定聚合查询字段。 + * + * @param function 聚合函数 + * @param column 聚合字段 SQL + * @param alias 查询别名 + * @return 当前查询构造辅助对象 + */ + private LambdaQueryBuilder selectAggregate(SqlAggregateFunction function, String column, String alias) { + aggregateWrapper().appendSelectSql(AggregateSelectUtils.aggregateSelect(function, column, alias)); + return this; + } + + /** + * 构建子查询 SQL。 + * + * @param entityClass 子查询实体类型 + * @param consumer 子查询构造函数 + * @param 子查询实体类型 + * @return 子查询 SQL + */ + private String buildSubQuery(Class entityClass, Consumer> consumer) { + SubQuery subQuery = SubQuery.of(entityClass, aggregateWrapper()::formatSubqueryParam); + consumer.accept(subQuery); + return subQuery.build(); + } + + /** + * 获取支持聚合查询字段的包装器。 + * + * @return 聚合查询包装器 + */ + @SuppressWarnings("unchecked") + private AggregateLambdaQueryWrapper aggregateWrapper() { + return (AggregateLambdaQueryWrapper) wrapper; + } + } diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/QueryBuilder.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/QueryBuilder.java index 7997603ca..74c46325f 100644 --- a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/QueryBuilder.java +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/QueryBuilder.java @@ -1,6 +1,5 @@ package org.dromara.common.mybatis.core.query; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.github.yulichang.toolkit.JoinWrappers; /** @@ -21,7 +20,7 @@ public final class QueryBuilder { * @return Lambda 查询构造辅助对象 */ public static LambdaQueryBuilder lambda(Class entityClass) { - return new LambdaQueryBuilder<>(Wrappers.lambdaQuery(entityClass)); + return new LambdaQueryBuilder<>(new AggregateLambdaQueryWrapper<>(entityClass)); } /** diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SqlAggregateFunction.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SqlAggregateFunction.java new file mode 100644 index 000000000..62a2c4ca2 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SqlAggregateFunction.java @@ -0,0 +1,51 @@ +package org.dromara.common.mybatis.core.query; + +/** + * SQL 标准聚合函数。 + * + * @author Lion Li + */ +public enum SqlAggregateFunction { + + /** + * 求和。 + */ + SUM("SUM"), + + /** + * 最大值。 + */ + MAX("MAX"), + + /** + * 最小值。 + */ + MIN("MIN"), + + /** + * 平均值。 + */ + AVG("AVG"), + + /** + * 计数。 + */ + COUNT("COUNT"); + + private final String name; + + SqlAggregateFunction(String name) { + this.name = name; + } + + /** + * 生成聚合函数 SQL 片段。 + * + * @param expression 函数入参表达式 + * @return 聚合函数 SQL + */ + public String format(String expression) { + return name + "(" + expression + ")"; + } + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SqlParamFormatter.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SqlParamFormatter.java new file mode 100644 index 000000000..e6dd1d65c --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SqlParamFormatter.java @@ -0,0 +1,19 @@ +package org.dromara.common.mybatis.core.query; + +/** + * SQL 参数格式化器。 + * + * @author Lion Li + */ +@FunctionalInterface +public interface SqlParamFormatter { + + /** + * 格式化 SQL 参数。 + * + * @param value 参数值 + * @return MyBatis 参数占位符 + */ + String format(Object value); + +} diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SubQuery.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SubQuery.java new file mode 100644 index 000000000..474c6d473 --- /dev/null +++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/query/SubQuery.java @@ -0,0 +1,447 @@ +package org.dromara.common.mybatis.core.query; + +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.core.toolkit.LambdaUtils; +import com.baomidou.mybatisplus.core.toolkit.StringPool; +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache; +import com.baomidou.mybatisplus.core.toolkit.support.LambdaMeta; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import org.apache.ibatis.reflection.property.PropertyNamer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * Lambda 子查询构造器。 + *

+ * 常用于外层查询的 {@code selectSub}、{@code inSub}、{@code existsSub} 等方法中。 + *

+ *

+ * 注意:子查询 SQL 由本构造器直接生成,默认会根据 MyBatis-Plus 表元数据追加逻辑删除条件, + * 但不会自动追加项目数据权限条件;如果子查询实体也需要数据权限过滤,请在子查询条件中显式添加。 + *

+ *
{@code
+ * userMapper.lambda()
+ *     .inSub(SysUser::getUserId, SysUserRole.class, sub -> sub
+ *         .select(SysUserRole::getUserId)
+ *         .eq(SysUserRole::getRoleId, roleId))
+ *     .voList();
+ *
+ * userMapper.lambda()
+ *     .select(SysUser::getUserId, SysUser::getUserName)
+ *     .selectSub(SysUserRole.class, sub -> sub
+ *         .selectCountAll()
+ *         .eqColumn(SysUserRole::getUserId, SysUser::getUserId),
+ *         UserStatVo::getRoleCount)
+ *     .voList();
+ * }
+ * + * @param 子查询实体类型 + * @author Lion Li + */ +public final class SubQuery { + + private final Class entityClass; + private final SqlParamFormatter paramFormatter; + private final boolean placeholderParamMode; + private final List selects = new ArrayList<>(); + private final List conditions = new ArrayList<>(); + private final List params = new ArrayList<>(); + private boolean withLogicDelete = true; + + private SubQuery(Class entityClass, SqlParamFormatter paramFormatter) { + this(entityClass, paramFormatter, false); + } + + private SubQuery(Class entityClass, SqlParamFormatter paramFormatter, boolean placeholderParamMode) { + this.entityClass = entityClass; + this.paramFormatter = paramFormatter; + this.placeholderParamMode = placeholderParamMode; + } + + /** + * 创建子查询。 + * + * @param entityClass 子查询实体类型 + * @param 子查询实体类型 + * @return 子查询构造器 + */ + public static SubQuery of(Class entityClass) { + return new SubQuery<>(entityClass, null); + } + + /** + * 创建子查询。 + * + * @param entityClass 子查询实体类型 + * @param paramFormatter SQL 参数格式化器 + * @param 子查询实体类型 + * @return 子查询构造器 + */ + public static SubQuery of(Class entityClass, SqlParamFormatter paramFormatter) { + return new SubQuery<>(entityClass, paramFormatter); + } + + /** + * 创建使用 {@code {0}} 参数占位符的子查询。 + * + * @param entityClass 子查询实体类型 + * @param 子查询实体类型 + * @return 子查询构造器 + */ + static SubQuery ofPlaceholders(Class entityClass) { + return new SubQuery<>(entityClass, null, true); + } + + /** + * 指定子查询字段。 + *
{@code
+     * sub.select(SysUserRole::getUserId)
+     * }
+ * + * @param column 查询字段 + * @return 当前子查询构造器 + */ + public SubQuery select(SFunction column) { + selects.add(columnName(column)); + return this; + } + + /** + * 指定 COUNT(*) 查询字段。 + *
{@code
+     * sub.selectCountAll()
+     * }
+ * + * @return 当前子查询构造器 + */ + public SubQuery selectCountAll() { + selects.add(SqlAggregateFunction.COUNT.format("*")); + return this; + } + + /** + * 指定 SUM 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前子查询构造器 + */ + public SubQuery selectSum(SFunction column) { + return selectAggregate(SqlAggregateFunction.SUM, column); + } + + /** + * 指定 MAX 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前子查询构造器 + */ + public SubQuery selectMax(SFunction column) { + return selectAggregate(SqlAggregateFunction.MAX, column); + } + + /** + * 指定 MIN 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前子查询构造器 + */ + public SubQuery selectMin(SFunction column) { + return selectAggregate(SqlAggregateFunction.MIN, column); + } + + /** + * 指定 AVG 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前子查询构造器 + */ + public SubQuery selectAvg(SFunction column) { + return selectAggregate(SqlAggregateFunction.AVG, column); + } + + /** + * 指定 COUNT 聚合查询字段。 + * + * @param column 聚合字段 + * @return 当前子查询构造器 + */ + public SubQuery selectCount(SFunction column) { + return selectAggregate(SqlAggregateFunction.COUNT, column); + } + + /** + * 禁用子查询逻辑删除条件。 + *
{@code
+     * sub.disableLogicDelete()
+     * }
+ * + * @return 当前子查询构造器 + */ + public SubQuery disableLogicDelete() { + this.withLogicDelete = false; + return this; + } + + /** + * 添加等于条件。 + *
{@code
+     * sub.eq(SysUserRole::getRoleId, roleId)
+     * }
+ * + * @param column 字段 + * @param value 条件值 + * @return 当前子查询构造器 + */ + public SubQuery eq(SFunction column, Object value) { + return condition(column, Constants.EQUALS, value); + } + + /** + * 添加大于条件。 + * + * @param column 字段 + * @param value 条件值 + * @return 当前子查询构造器 + */ + public SubQuery gt(SFunction column, Object value) { + return condition(column, ">", value); + } + + /** + * 添加大于等于条件。 + * + * @param column 字段 + * @param value 条件值 + * @return 当前子查询构造器 + */ + public SubQuery ge(SFunction column, Object value) { + return condition(column, ">=", value); + } + + /** + * 添加小于条件。 + * + * @param column 字段 + * @param value 条件值 + * @return 当前子查询构造器 + */ + public SubQuery lt(SFunction column, Object value) { + return condition(column, "<", value); + } + + /** + * 添加小于等于条件。 + * + * @param column 字段 + * @param value 条件值 + * @return 当前子查询构造器 + */ + public SubQuery le(SFunction column, Object value) { + return condition(column, "<=", value); + } + + /** + * 添加模糊匹配条件。 + * + * @param column 字段 + * @param value 条件值 + * @return 当前子查询构造器 + */ + public SubQuery like(SFunction column, Object value) { + return condition(column, "LIKE", "%" + value + "%"); + } + + /** + * 添加 IN 条件。 + *
{@code
+     * sub.in(SysUserRole::getRoleId, roleIds)
+     * }
+ * + * @param column 字段 + * @param values 条件值集合 + * @return 当前子查询构造器 + */ + public SubQuery in(SFunction column, Collection values) { + if (CollectionUtils.isEmpty(values)) { + return this; + } + conditions.add(columnName(column) + " IN (" + values.stream() + .map(this::formatParam) + .collect(Collectors.joining(Constants.COMMA)) + ")"); + return this; + } + + /** + * 添加 IN 条件。 + * + * @param column 字段 + * @param values 条件值数组 + * @return 当前子查询构造器 + */ + public SubQuery in(SFunction column, Object... values) { + if (values == null || values.length == 0) { + return this; + } + return in(column, Arrays.asList(values)); + } + + /** + * 添加 BETWEEN 条件。 + * + * @param column 字段 + * @param begin 起始值 + * @param end 结束值 + * @return 当前子查询构造器 + */ + public SubQuery between(SFunction column, Object begin, Object end) { + conditions.add(columnName(column) + " BETWEEN " + formatParam(begin) + " AND " + formatParam(end)); + return this; + } + + /** + * 添加字段相等条件,用于关联外层查询字段。 + *
{@code
+     * sub.eqColumn(SysUserRole::getUserId, SysUser::getUserId)
+     * }
+ * + * @param column 子查询字段 + * @param otherColumn 其他表字段 + * @param 其他表实体类型 + * @return 当前子查询构造器 + */ + public SubQuery eqColumn(SFunction column, SFunction otherColumn) { + conditions.add(columnName(column) + Constants.EQUALS + qualifiedColumnName(otherColumn)); + return this; + } + + /** + * 添加字段相等条件,用于关联外层查询字段。 + *
{@code
+     * sub.eqColumn(SysUserRole::getUserId, "u", SysUser::getUserId)
+     * }
+ * + * @param column 子查询字段 + * @param tableAlias 其他表别名 + * @param otherColumn 其他表字段 + * @param 其他表实体类型 + * @return 当前子查询构造器 + */ + public SubQuery eqColumn(SFunction column, String tableAlias, SFunction otherColumn) { + conditions.add(columnName(column) + Constants.EQUALS + AggregateSelectUtils.checkAlias(tableAlias) + + StringPool.DOT + columnName(otherColumn)); + return this; + } + + /** + * 按条件添加子查询条件。 + * + * @param condition 是否添加 + * @param consumer 子查询条件 + * @return 当前子查询构造器 + */ + public SubQuery when(boolean condition, Consumer> consumer) { + if (condition) { + consumer.accept(this); + } + return this; + } + + /** + * 构建子查询 SQL。 + * + * @return 子查询 SQL + */ + public String build() { + Assert.notEmpty(selects, "子查询必须指定查询字段"); + String sql = "SELECT " + String.join(Constants.COMMA, selects) + " FROM " + tableName(); + List whereConditions = buildWhereConditions(); + if (!whereConditions.isEmpty()) { + sql += " WHERE " + String.join(" AND ", whereConditions); + } + return sql; + } + + /** + * 获取子查询参数。 + * + * @return 子查询参数数组 + */ + Object[] params() { + return params.toArray(); + } + + private SubQuery selectAggregate(SqlAggregateFunction function, SFunction column) { + selects.add(function.format(columnName(column))); + return this; + } + + private SubQuery condition(SFunction column, String operator, Object value) { + conditions.add(columnName(column) + StringPool.SPACE + operator + StringPool.SPACE + formatParam(value)); + return this; + } + + private String formatParam(Object value) { + if (placeholderParamMode) { + params.add(value); + return "{" + (params.size() - 1) + "}"; + } + Assert.notNull(paramFormatter, "子查询参数需要在外层查询方法中构造"); + return paramFormatter.format(value); + } + + private String tableName() { + return tableInfo().getTableName(); + } + + private List buildWhereConditions() { + List whereConditions = new ArrayList<>(); + String logicDeleteSql = logicDeleteSql(); + if (StringUtils.isNotBlank(logicDeleteSql)) { + whereConditions.add(logicDeleteSql); + } + whereConditions.addAll(conditions); + return whereConditions; + } + + private String logicDeleteSql() { + if (!withLogicDelete) { + return StringPool.EMPTY; + } + return tableInfo().getLogicDeleteSql(false, true); + } + + private TableInfo tableInfo() { + TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass); + Assert.notNull(tableInfo, "无法获取实体表信息: %s", entityClass.getName()); + return tableInfo; + } + + private String qualifiedColumnName(SFunction column) { + Class columnEntityClass = LambdaUtils.extract(column).getInstantiatedClass(); + TableInfo tableInfo = TableInfoHelper.getTableInfo(columnEntityClass); + Assert.notNull(tableInfo, "无法获取实体表信息: %s", columnEntityClass.getName()); + return tableInfo.getTableName() + StringPool.DOT + columnName(column); + } + + private static String columnName(SFunction column) { + LambdaMeta meta = LambdaUtils.extract(column); + String fieldName = PropertyNamer.methodToProperty(meta.getImplMethodName()); + Map columnMap = LambdaUtils.getColumnMap(meta.getInstantiatedClass()); + Assert.notNull(columnMap, "can not find lambda cache for this entity [%s]", meta.getInstantiatedClass().getName()); + ColumnCache cache = columnMap.get(LambdaUtils.formatKey(fieldName)); + Assert.notNull(cache, "can not find lambda cache for this property [%s]", fieldName); + return cache.getColumn(); + } + +}