diff --git a/src/main/java/com/github/yulichang/metadata/TableFieldInfo.java b/src/main/java/com/github/yulichang/metadata/TableFieldInfo.java index 414925f..a6d26f5 100644 --- a/src/main/java/com/github/yulichang/metadata/TableFieldInfo.java +++ b/src/main/java/com/github/yulichang/metadata/TableFieldInfo.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.core.config.GlobalConfig; import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils; import lombok.*; import org.apache.ibatis.mapping.ResultMapping; import org.apache.ibatis.reflection.Reflector; @@ -42,6 +43,10 @@ public class TableFieldInfo implements Constants { * 属性表达式#{property}, 可以指定jdbcType, typeHandler等 */ private final String el; + /** + * jdbcType, typeHandler等部分 + */ + private final String mapping; /** * 属性类型 */ @@ -140,6 +145,32 @@ public class TableFieldInfo implements Constants { */ private Class> typeHandler; + /** + * 是否存在OrderBy注解 + */ + private boolean isOrderBy; + /** + * 排序类型 + */ + private String orderByType; + /** + * 排序顺序 + */ + private short orderBySort; + + /** + * 全新的 存在 TableField 注解时使用的构造函数 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, TableField tableField, + Reflector reflector, boolean existTableLogic,boolean isOrderBy) { + this(dbConfig,tableInfo,field,tableField,reflector,existTableLogic); + this.isOrderBy = isOrderBy; + if(isOrderBy){ + initOrderBy(field); + } + } + /** * 全新的 存在 TableField 注解时使用的构造函数 */ @@ -163,7 +194,7 @@ public class TableFieldInfo implements Constants { String el = this.property; if (JdbcType.UNDEFINED != jdbcType) { this.jdbcType = jdbcType; - el += (COMMA + "jdbcType=" + jdbcType.name()); + el += (COMMA + SqlScriptUtils.mappingJdbcType(jdbcType)); } if (UnknownTypeHandler.class != typeHandler) { this.typeHandler = (Class>) typeHandler; @@ -183,12 +214,14 @@ public class TableFieldInfo implements Constants { } el += (COMMA + "javaType=" + javaType); } - el += (COMMA + "typeHandler=" + typeHandler.getName()); + el += (COMMA + SqlScriptUtils.mappingTypeHandler(this.typeHandler)); } if (StringUtils.isNotBlank(numericScale)) { - el += (COMMA + "numericScale=" + numericScale); + el += (COMMA + SqlScriptUtils.mappingNumericScale(Integer.valueOf(numericScale))); } this.el = el; + int index = el.indexOf(COMMA); + this.mapping = index > 0 ? el.substring(++index) : null; this.initLogicDelete(dbConfig, field, existTableLogic); String column = tableField.value(); @@ -211,7 +244,7 @@ public class TableFieldInfo implements Constants { this.column = column; this.sqlSelect = column; if (tableInfo.getResultMap() == null && !tableInfo.isAutoInitResultMap() && - TableInfoHelper.checkRelated(tableInfo.isUnderCamel(), this.property, this.column)) { + TableInfoHelper.checkRelated(tableInfo.isUnderCamel(), this.property, this.column)) { /* 未设置 resultMap 也未开启自动构建 resultMap, 字段规则又不符合 mybatis 的自动封装规则 */ String propertyFormat = dbConfig.getPropertyFormat(); String asProperty = this.property; @@ -241,6 +274,17 @@ public class TableFieldInfo implements Constants { return fromAnnotation == FieldStrategy.DEFAULT ? fromDbConfig : fromAnnotation; } + /** + * 不存在 TableField 注解时, 使用的构造函数 + */ + public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, Reflector reflector, + boolean existTableLogic,boolean isOrderBy) { + this(dbConfig,tableInfo,field,reflector,existTableLogic); + this.isOrderBy = isOrderBy; + if(isOrderBy){ + initOrderBy(field); + } + } /** * 不存在 TableField 注解时, 使用的构造函数 */ @@ -254,6 +298,7 @@ public class TableFieldInfo implements Constants { this.isPrimitive = this.propertyType.isPrimitive(); this.isCharSequence = StringUtils.isCharSequence(this.propertyType); this.el = this.property; + this.mapping = null; this.insertStrategy = dbConfig.getInsertStrategy(); this.updateStrategy = dbConfig.getUpdateStrategy(); this.whereStrategy = dbConfig.getSelectStrategy(); @@ -277,7 +322,7 @@ public class TableFieldInfo implements Constants { this.column = column; this.sqlSelect = column; if (tableInfo.getResultMap() == null && !tableInfo.isAutoInitResultMap() && - TableInfoHelper.checkRelated(tableInfo.isUnderCamel(), this.property, this.column)) { + TableInfoHelper.checkRelated(tableInfo.isUnderCamel(), this.property, this.column)) { /* 未设置 resultMap 也未开启自动构建 resultMap, 字段规则又不符合 mybatis 的自动封装规则 */ String propertyFormat = dbConfig.getPropertyFormat(); String asProperty = this.property; @@ -288,6 +333,21 @@ public class TableFieldInfo implements Constants { } } + /** + * 排序初始化 + * @param field 字段 + */ + private void initOrderBy(Field field){ + OrderBy orderBy = field.getAnnotation(OrderBy.class); + if (null != orderBy) { + this.isOrderBy = true; + this.orderBySort = orderBy.sort(); + this.orderByType = orderBy.isDesc()?"desc":"asc"; + }else{ + this.isOrderBy = false; + } + } + /** * 逻辑删除初始化 * diff --git a/src/main/java/com/github/yulichang/metadata/TableInfo.java b/src/main/java/com/github/yulichang/metadata/TableInfo.java index 1a8ecdf..271cc3e 100644 --- a/src/main/java/com/github/yulichang/metadata/TableInfo.java +++ b/src/main/java/com/github/yulichang/metadata/TableInfo.java @@ -13,6 +13,7 @@ import lombok.experimental.Accessors; import org.apache.ibatis.session.Configuration; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -148,6 +149,13 @@ public class TableInfo implements Constants { @Setter(AccessLevel.NONE) private TableFieldInfo versionFieldInfo; + /** + * 排序列表 + */ + @Getter + @Setter + public List orderByFields; + public TableInfo(Class entityType) { this.entityType = entityType; } @@ -198,6 +206,12 @@ public class TableInfo implements Constants { if (i.isWithUpdateFill()) { this.withUpdateFill = true; } + if (i.isOrderBy()) { + if (null == this.orderByFields) { + this.orderByFields = new LinkedList<>(); + } + this.orderByFields.add(i); + } if (i.isVersion()) { this.withVersion = true; this.versionFieldInfo = i; diff --git a/src/main/java/com/github/yulichang/metadata/TableInfoHelper.java b/src/main/java/com/github/yulichang/metadata/TableInfoHelper.java index 1f27e73..b3fbe24 100644 --- a/src/main/java/com/github/yulichang/metadata/TableInfoHelper.java +++ b/src/main/java/com/github/yulichang/metadata/TableInfoHelper.java @@ -96,7 +96,7 @@ public class TableInfoHelper { tableInfo.setTableName(targetTableName); /* 开启了自定义 KEY 生成器 */ - if (null != dbConfig.getKeyGenerator()) { + if (CollectionUtils.isNotEmpty(dbConfig.getKeyGenerators())) { tableInfo.setKeySequence(clazz.getAnnotation(KeySequence.class)); } return excludeProperty; @@ -146,6 +146,8 @@ public class TableInfoHelper { boolean existTableId = isExistTableId(list); // 是否存在 @TableLogic 注解 boolean existTableLogic = isExistTableLogic(list); + // 是否存在 @OrderBy 注解 + boolean existOrderBy = isExistOrderBy(list); List fieldList = new ArrayList<>(list.size()); for (Field field : list) { @@ -175,12 +177,12 @@ public class TableInfoHelper { /* 有 @TableField 注解的字段初始化 */ if (tableField != null) { - fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, tableField, reflector, existTableLogic)); + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, tableField, reflector, existTableLogic, existOrderBy)); continue; } - /* 无 @TableField 注解的字段初始化 */ - fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic)); + /* 无 @TableField 注解的字段初始化 */ + fieldList.add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, existOrderBy)); } /* 字段列表 */ @@ -216,6 +218,18 @@ public class TableInfoHelper { return list.stream().anyMatch(field -> field.isAnnotationPresent(TableLogic.class)); } + /** + *

+ * 判断排序注解是否存在 + *

+ * + * @param list 字段列表 + * @return true 为存在 @TableId 注解; + */ + public static boolean isExistOrderBy(List list) { + return list.stream().anyMatch(field -> field.isAnnotationPresent(OrderBy.class)); + } + /** *

* 主键属性初始化 @@ -233,7 +247,7 @@ public class TableInfoHelper { final String property = field.getName(); if (field.getAnnotation(TableField.class) != null) { logger.warn(String.format("This \"%s\" is the table primary key by @TableId annotation in Class: \"%s\",So @TableField annotation will not work!", - property, tableInfo.getEntityType().getName())); + property, tableInfo.getEntityType().getName())); } /* 主键策略( 注解 > 全局 ) */ // 设置 Sequence 其他策略无效 @@ -260,12 +274,12 @@ public class TableInfoHelper { final Class keyType = reflector.getGetterType(property); if (keyType.isPrimitive()) { logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", - property, tableInfo.getEntityType().getName())); + property, tableInfo.getEntityType().getName())); } tableInfo.setKeyRelated(checkRelated(underCamel, property, column)) - .setKeyColumn(column) - .setKeyProperty(property) - .setKeyType(keyType); + .setKeyColumn(column) + .setKeyProperty(property) + .setKeyType(keyType); } /** @@ -284,7 +298,7 @@ public class TableInfoHelper { if (DEFAULT_ID_NAME.equalsIgnoreCase(property)) { if (field.getAnnotation(TableField.class) != null) { logger.warn(String.format("This \"%s\" is the table primary key by default name for `id` in Class: \"%s\",So @TableField will not work!", - property, tableInfo.getEntityType().getName())); + property, tableInfo.getEntityType().getName())); } String column = property; if (dbConfig.isCapitalMode()) { @@ -293,13 +307,13 @@ public class TableInfoHelper { final Class keyType = reflector.getGetterType(property); if (keyType.isPrimitive()) { logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", - property, tableInfo.getEntityType().getName())); + property, tableInfo.getEntityType().getName())); } tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), property, column)) - .setIdType(dbConfig.getIdType()) - .setKeyColumn(column) - .setKeyProperty(property) - .setKeyType(keyType); + .setIdType(dbConfig.getIdType()) + .setKeyColumn(column) + .setKeyProperty(property) + .setKeyType(keyType); return true; } return false; @@ -322,7 +336,7 @@ public class TableInfoHelper { if (underCamel) { // 开启了驼峰并且 column 包含下划线 return !(propertyUpper.equals(columnUpper) || - propertyUpper.equals(columnUpper.replace(StringPool.UNDERSCORE, StringPool.EMPTY))); + propertyUpper.equals(columnUpper.replace(StringPool.UNDERSCORE, StringPool.EMPTY))); } else { // 未开启驼峰,直接判断 property 是否与 column 相同(全大写) return !propertyUpper.equals(columnUpper); @@ -340,10 +354,10 @@ public class TableInfoHelper { public static List getAllFields(Class clazz) { List fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz)); return fieldList.stream() - .filter(field -> { - /* 过滤注解非表字段属性 */ - TableField tableField = field.getAnnotation(TableField.class); - return (tableField == null || tableField.exist()); - }).collect(toList()); + .filter(field -> { + /* 过滤注解非表字段属性 */ + TableField tableField = field.getAnnotation(TableField.class); + return (tableField == null || tableField.exist()); + }).collect(toList()); } } diff --git a/src/main/java/com/github/yulichang/toolkit/LambdaUtils.java b/src/main/java/com/github/yulichang/toolkit/LambdaUtils.java index 2039e71..e8e098c 100644 --- a/src/main/java/com/github/yulichang/toolkit/LambdaUtils.java +++ b/src/main/java/com/github/yulichang/toolkit/LambdaUtils.java @@ -1,29 +1,120 @@ package com.github.yulichang.toolkit; -import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache; -import com.baomidou.mybatisplus.core.toolkit.support.SFunction; -import com.github.yulichang.exception.MPJException; +import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; +import com.baomidou.mybatisplus.core.metadata.TableInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; +import com.baomidou.mybatisplus.core.toolkit.support.*; import org.apache.ibatis.reflection.property.PropertyNamer; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static java.util.Locale.ENGLISH; /** - * @author yulichang - * @see com.baomidou.mybatisplus.core.toolkit.LambdaUtils - * @see org.apache.ibatis.reflection.property.PropertyNamer + * copy {@link com.baomidou.mybatisplus.core.toolkit.LambdaUtils} */ public final class LambdaUtils { - /** - * 获取属性名 - */ + /* ******* 自定义方法 *********** */ public static String getName(SFunction fn) { - return PropertyNamer.methodToProperty(com.baomidou.mybatisplus.core.toolkit.LambdaUtils.resolve(fn).getImplMethodName()); + return PropertyNamer.methodToProperty(extract(fn).getImplMethodName()); } @SuppressWarnings("unchecked") public static Class getEntityClass(SFunction fn) { - return (Class) com.baomidou.mybatisplus.core.toolkit.LambdaUtils.resolve(fn).getInstantiatedType(); + return (Class) extract(fn).getInstantiatedClass(); } + /* ******* 自定义方法 结束 以下代码均为拷贝 *********** */ + + /** + * 字段映射 + */ + private static final Map> COLUMN_CACHE_MAP = new ConcurrentHashMap<>(); + + /** + * 该缓存可能会在任意不定的时间被清除 + * + * @param func 需要解析的 lambda 对象 + * @param 类型,被调用的 Function 对象的目标类型 + * @return 返回解析后的结果 + */ + public static LambdaMeta extract(SFunction func) { + try { + Method method = func.getClass().getDeclaredMethod("writeReplace"); + return new SerializedLambdaMeta((SerializedLambda) ReflectionKit.setAccessible(method).invoke(func)); + } catch (NoSuchMethodException e) { + if (func instanceof Proxy) return new ProxyLambdaMeta((Proxy) func); + String message = "Cannot find method writeReplace, please make sure that the lambda composite class is currently passed in"; + throw new MybatisPlusException(message); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new MybatisPlusException(e); + } + } + + /** + * 格式化 key 将传入的 key 变更为大写格式 + * + *

+     *     Assert.assertEquals("USERID", formatKey("userId"))
+     * 
+ * + * @param key key + * @return 大写的 key + */ + public static String formatKey(String key) { + return key.toUpperCase(ENGLISH); + } + + /** + * 将传入的表信息加入缓存 + * + * @param tableInfo 表信息 + */ + public static void installCache(TableInfo tableInfo) { + COLUMN_CACHE_MAP.put(tableInfo.getEntityType().getName(), createColumnCacheMap(tableInfo)); + } + + /** + * 缓存实体字段 MAP 信息 + * + * @param info 表信息 + * @return 缓存 map + */ + private static Map createColumnCacheMap(TableInfo info) { + Map map; + + if (info.havePK()) { + map = CollectionUtils.newHashMapWithExpectedSize(info.getFieldList().size() + 1); + map.put(formatKey(info.getKeyProperty()), new ColumnCache(info.getKeyColumn(), info.getKeySqlSelect())); + } else { + map = CollectionUtils.newHashMapWithExpectedSize(info.getFieldList().size()); + } + + info.getFieldList().forEach(i -> + map.put(formatKey(i.getProperty()), new ColumnCache(i.getColumn(), i.getSqlSelect(), i.getMapping())) + ); + return map; + } + + /** + * 获取实体对应字段 MAP + * + * @param clazz 实体类 + * @return 缓存 map + */ + public static Map getColumnMap(Class clazz) { + return CollectionUtils.computeIfAbsent(COLUMN_CACHE_MAP, clazz.getName(), key -> { + TableInfo info = TableInfoHelper.getTableInfo(clazz); + return info == null ? null : createColumnCacheMap(info); + }); + } + } diff --git a/src/main/java/com/github/yulichang/wrapper/MPJAbstractWrapper.java b/src/main/java/com/github/yulichang/wrapper/MPJAbstractWrapper.java index b716e42..3fc1fe6 100644 --- a/src/main/java/com/github/yulichang/wrapper/MPJAbstractWrapper.java +++ b/src/main/java/com/github/yulichang/wrapper/MPJAbstractWrapper.java @@ -3,17 +3,18 @@ package com.github.yulichang.wrapper; import com.baomidou.mybatisplus.core.conditions.ISqlSegment; import com.baomidou.mybatisplus.core.conditions.SharedString; import com.baomidou.mybatisplus.core.conditions.Wrapper; -import com.baomidou.mybatisplus.core.conditions.interfaces.Join; import com.baomidou.mybatisplus.core.conditions.interfaces.Nested; import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments; import com.baomidou.mybatisplus.core.enums.SqlKeyword; import com.baomidou.mybatisplus.core.enums.SqlLike; import com.baomidou.mybatisplus.core.toolkit.*; +import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils; import com.baomidou.mybatisplus.core.toolkit.sql.SqlUtils; import com.baomidou.mybatisplus.core.toolkit.sql.StringEscape; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.github.yulichang.wrapper.interfaces.Compare; import com.github.yulichang.wrapper.interfaces.Func; +import com.github.yulichang.wrapper.interfaces.Join; import java.util.Arrays; import java.util.Collection; @@ -46,6 +47,11 @@ public abstract class MPJAbstractWrapper paramNameValuePairs; + /** + * 其他 + */ + /* mybatis plus 3.4.3新增 这个时wrapper的别名 不是MPJ的别名 */ + protected SharedString paramAlias; protected SharedString lastSql; /** * SQL注释 @@ -176,14 +182,14 @@ public abstract class MPJAbstractWrapper Children between(boolean condition, SFunction column, Object val1, Object val2) { - return doIt(condition, () -> columnToString(column), BETWEEN, () -> formatSql("{0}", val1), AND, - () -> formatSql("{0}", val2)); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), BETWEEN, + () -> formatParam(null, val1), AND, () -> formatParam(null, val2))); } @Override public Children notBetween(boolean condition, SFunction column, Object val1, Object val2) { - return doIt(condition, () -> columnToString(column), NOT_BETWEEN, () -> formatSql("{0}", val1), AND, - () -> formatSql("{0}", val2)); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), NOT_BETWEEN, + () -> formatParam(null, val1), AND, () -> formatParam(null, val2))); } @Override @@ -208,12 +214,13 @@ public abstract class MPJAbstractWrapper appendSqlSegments(OR)); } @Override - public Children apply(boolean condition, String applySql, Object... value) { - return doIt(condition, APPLY, () -> formatSql(applySql, value)); + public Children apply(boolean condition, String applySql, Object... values) { + return maybeDo(condition, () -> appendSqlSegments(APPLY, + () -> formatSqlMaybeWithParam(applySql, null, values))); } @Override @@ -241,77 +248,101 @@ public abstract class MPJAbstractWrapper String.format("(%s)", existsSql)); + public Children exists(boolean condition, String existsSql, Object... values) { + return maybeDo(condition, () -> appendSqlSegments(EXISTS, + () -> String.format("(%s)", formatSqlMaybeWithParam(existsSql, null, values)))); } @Override - public Children notExists(boolean condition, String existsSql) { - return not(condition).exists(condition, existsSql); + public Children notExists(boolean condition, String existsSql, Object... values) { + return not(condition).exists(condition, existsSql, values); } @Override public Children isNull(boolean condition, SFunction column) { - return doIt(condition, () -> columnToString(column), IS_NULL); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), IS_NULL)); } @Override public Children isNotNull(boolean condition, SFunction column) { - return doIt(condition, () -> columnToString(column), IS_NOT_NULL); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), IS_NOT_NULL)); } @Override public Children in(boolean condition, SFunction column, Collection coll) { - return doIt(condition, () -> columnToString(column), IN, inExpression(coll)); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), IN, inExpression(coll))); + } + + @Override + public Children in(boolean condition, SFunction column, Object... values) { + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), IN, inExpression(values))); } @Override public Children notIn(boolean condition, SFunction column, Collection coll) { - return doIt(condition, () -> columnToString(column), NOT_IN, inExpression(coll)); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), NOT_IN, inExpression(coll))); + } + + @Override + public Children notIn(boolean condition, SFunction column, Object... values) { + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), NOT_IN, inExpression(values))); } @Override public Children inSql(boolean condition, SFunction column, String inValue) { - return doIt(condition, () -> columnToString(column), IN, () -> String.format("(%s)", inValue)); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), IN, + () -> String.format("(%s)", inValue))); } @Override public Children notInSql(boolean condition, SFunction column, String inValue) { - return doIt(condition, () -> columnToString(column), NOT_IN, () -> String.format("(%s)", inValue)); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), NOT_IN, + () -> String.format("(%s)", inValue))); } @Override - public Children groupBy(boolean condition, SFunction... columns) { - if (ArrayUtils.isEmpty(columns)) { - return typedThis; - } - return doIt(condition, GROUP_BY, - () -> columns.length == 1 ? columnToString(columns[0]) : columnsToString(columns)); + public Children groupBy(boolean condition, SFunction column, SFunction... columns) { + return maybeDo(condition, () -> { + String one = columnToString(column); + if (ArrayUtils.isNotEmpty(columns)) { + one += (StringPool.COMMA + columnsToString(columns)); + } + final String finalOne = one; + appendSqlSegments(GROUP_BY, () -> finalOne); + }); } @Override - public Children orderBy(boolean condition, boolean isAsc, SFunction... columns) { - if (ArrayUtils.isEmpty(columns)) { - return typedThis; - } - SqlKeyword mode = isAsc ? ASC : DESC; - for (SFunction column : columns) { - doIt(condition, ORDER_BY, () -> columnToString(column), mode); - } - return typedThis; + public Children orderBy(boolean condition, boolean isAsc, SFunction column, SFunction... columns) { + return maybeDo(condition, () -> { + final SqlKeyword mode = isAsc ? ASC : DESC; + appendSqlSegments(ORDER_BY, columnToSqlSegment(column), mode); + if (ArrayUtils.isNotEmpty(columns)) { + Arrays.stream(columns).forEach(c -> appendSqlSegments(ORDER_BY, + columnToSqlSegment(columnSqlInjectFilter(c)), mode)); + } + }); + } + + /** + * 字段 SQL 注入过滤处理,子类重写实现过滤逻辑 + * + * @param column 字段内容 + * @return + */ + protected SFunction columnSqlInjectFilter(SFunction column) { + return column; } @Override public Children having(boolean condition, String sqlHaving, Object... params) { - return doIt(condition, HAVING, () -> formatSqlIfNeed(condition, sqlHaving, params)); + return maybeDo(condition, () -> appendSqlSegments(HAVING, + () -> formatSqlMaybeWithParam(sqlHaving, null, params))); } @Override public Children func(boolean condition, Consumer consumer) { - if (condition) { - consumer.accept(typedThis); - } - return typedThis; + return maybeDo(condition, () -> consumer.accept(typedThis)); } /** @@ -319,7 +350,7 @@ public abstract class MPJAbstractWrapperNOT 关键词

*/ protected Children not(boolean condition) { - return doIt(condition, NOT); + return maybeDo(condition, () -> appendSqlSegments(NOT)); } /** @@ -327,7 +358,7 @@ public abstract class MPJAbstractWrapper拼接 AND

*/ protected Children and(boolean condition) { - return doIt(condition, AND); + return maybeDo(condition, () -> appendSqlSegments(AND)); } /** @@ -335,7 +366,8 @@ public abstract class MPJAbstractWrapper拼接 LIKE 以及 值

*/ protected Children likeValue(boolean condition, SqlKeyword keyword, SFunction column, Object val, SqlLike sqlLike) { - return doIt(condition, () -> columnToString(column), keyword, () -> formatSql("{0}", SqlUtils.concatLike(val, sqlLike))); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), keyword, + () -> formatParam(null, SqlUtils.concatLike(val, sqlLike)))); } /** @@ -347,7 +379,8 @@ public abstract class MPJAbstractWrapper Children addCondition(boolean condition, SFunction column, SqlKeyword sqlKeyword, Object val) { - return doIt(condition, () -> columnToString(column), sqlKeyword, () -> formatSql("{0}", val)); + return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), sqlKeyword, + () -> formatParam(null, val))); } /** @@ -356,12 +389,11 @@ public abstract class MPJAbstractWrapper consumer) { - if (condition) { + return maybeDo(condition, () -> { final Children instance = instance(); consumer.accept(instance); - return doIt(true, APPLY, instance); - } - return typedThis; + appendSqlSegments(APPLY, instance); + }); } /** @@ -370,56 +402,80 @@ public abstract class MPJAbstractWrapper - * 根据需要格式化SQL
- *
- * Format SQL for methods: EntityQ.where/and/or...("name={0}", value); - * ALL the {i} will be replaced with #{MPGENVALi}
- *
- * ew.where("sample_name={0}", "haha").and("sample_age >{0} - * and sample_age<{1}", 18, 30) TO - * sample_name=#{MPGENVAL1} and sample_age>#{MPGENVAL2} and - * sample_age<#{MPGENVAL3}
- *

+ * 支持 "{0}" 这种,或者 "sql {0} sql" 这种 * - * @param need 是否需要格式化 - * @param sqlStr SQL语句部分 - * @param params 参数集 - * @return sql + * @param sqlStr 可能是sql片段 + * @param mapping 例如: "javaType=int,jdbcType=NUMERIC,typeHandler=xxx.xxx.MyTypeHandler" 这种 + * @param params 参数 + * @return sql片段 */ - protected final String formatSqlIfNeed(boolean need, String sqlStr, Object... params) { - if (!need || StringUtils.isBlank(sqlStr)) { + protected final String formatSqlMaybeWithParam(String sqlStr, String mapping, Object... params) { + if (StringUtils.isBlank(sqlStr)) { + // todo 何时会这样? return null; } if (ArrayUtils.isNotEmpty(params)) { for (int i = 0; i < params.length; ++i) { - String genParamName = Constants.WRAPPER_PARAM + paramNameSeq.incrementAndGet(); - sqlStr = sqlStr.replace(String.format("{%s}", i), - String.format(Constants.WRAPPER_PARAM_FORMAT, Constants.WRAPPER, genParamName)); - paramNameValuePairs.put(genParamName, params[i]); + final String target = Constants.LEFT_BRACE + i + Constants.RIGHT_BRACE; + sqlStr = sqlStr.replace(target, formatParam(mapping, params[i])); } } return sqlStr; } + /** + * 处理入参 + * + * @param mapping 例如: "javaType=int,jdbcType=NUMERIC,typeHandler=xxx.xxx.MyTypeHandler" 这种 + * @param param 参数 + * @return value + */ + protected final String formatParam(String mapping, Object param) { + final String genParamName = Constants.WRAPPER_PARAM + paramNameSeq.incrementAndGet(); + final String paramStr = getParamAlias() + Constants.WRAPPER_PARAM_MIDDLE + genParamName; + paramNameValuePairs.put(genParamName, param); + return SqlScriptUtils.safeParam(paramStr, mapping); + } + + /** + * 函数化的做事 + * + * @param condition 做不做 + * @param something 做什么 + * @return Children + */ + protected final Children maybeDo(boolean condition, DoSomething something) { + if (condition) { + something.doIt(); + } + return typedThis; + } + /** * 获取in表达式 包含括号 * * @param value 集合 */ - private ISqlSegment inExpression(Collection value) { - return () -> value.stream().map(i -> formatSql("{0}", i)) + protected ISqlSegment inExpression(Collection value) { + if (CollectionUtils.isEmpty(value)) { + return () -> "()"; + } + return () -> value.stream().map(i -> formatParam(null, i)) + .collect(joining(StringPool.COMMA, StringPool.LEFT_BRACKET, StringPool.RIGHT_BRACKET)); + } + + /** + * 获取in表达式 包含括号 + * + * @param values 数组 + */ + protected ISqlSegment inExpression(Object[] values) { + if (ArrayUtils.isEmpty(values)) { + return () -> "()"; + } + return () -> Arrays.stream(values).map(i -> formatParam(null, i)) .collect(joining(StringPool.COMMA, StringPool.LEFT_BRACKET, StringPool.RIGHT_BRACKET)); } @@ -447,17 +503,12 @@ public abstract class MPJAbstractWrapper ISqlSegment columnToSqlSegment(SFunction column) { + return () -> columnToString(column); + } + /** * 获取 columnName */ @@ -511,4 +587,13 @@ public abstract class MPJAbstractWrapper extends MPJAbstractLambdaWrapper MPJLambdaWrapper select(SFunction... columns) { if (ArrayUtils.isNotEmpty(columns)) { for (SFunction s : columns) { - selectColumns.add(new SelectColumn(LambdaUtils.getEntityClass(s), getCache(s).getColumn(), null)); + selectColumns.add(new SelectColumn(com.github.yulichang.toolkit.LambdaUtils.getEntityClass(s), getCache(s).getColumn(), null)); } } return typedThis; @@ -113,14 +113,14 @@ public class MPJLambdaWrapper extends MPJAbstractLambdaWrapper MPJLambdaWrapper selectAs(SFunction columns, SFunction alias) { - return selectAs(columns, LambdaUtils.getName(alias)); + return selectAs(columns, com.github.yulichang.toolkit.LambdaUtils.getName(alias)); } /** * @since 1.1.3 */ public final MPJLambdaWrapper selectAs(SFunction columns, String alias) { - selectColumns.add(new SelectColumn(LambdaUtils.getEntityClass(columns), getCache(columns).getColumn(), alias)); + selectColumns.add(new SelectColumn(com.github.yulichang.toolkit.LambdaUtils.getEntityClass(columns), getCache(columns).getColumn(), alias)); return typedThis; } @@ -150,7 +150,7 @@ public class MPJLambdaWrapper extends MPJAbstractLambdaWrapper MPJLambdaWrapper selectIgnore(SFunction... columns) { if (ArrayUtils.isNotEmpty(columns)) { for (SFunction s : columns) { - ignoreColumns.add(new SelectColumn(LambdaUtils.getEntityClass(s), getCache(s).getColumn(), null)); + ignoreColumns.add(new SelectColumn(com.github.yulichang.toolkit.LambdaUtils.getEntityClass(s), getCache(s).getColumn(), null)); } } return typedThis; diff --git a/src/main/java/com/github/yulichang/wrapper/interfaces/Func.java b/src/main/java/com/github/yulichang/wrapper/interfaces/Func.java index 5f1c84b..a3a29b1 100644 --- a/src/main/java/com/github/yulichang/wrapper/interfaces/Func.java +++ b/src/main/java/com/github/yulichang/wrapper/interfaces/Func.java @@ -3,13 +3,9 @@ package com.github.yulichang.wrapper.interfaces; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import java.io.Serializable; -import java.util.Arrays; import java.util.Collection; -import java.util.Optional; import java.util.function.Consumer; -import static java.util.stream.Collectors.toList; - /** * 将原来的泛型R改成SFunction *

@@ -63,6 +59,7 @@ public interface Func extends Serializable { * 字段 IN (value.get(0), value.get(1), ...) *

例: in("id", Arrays.asList(1, 2, 3, 4, 5))

* + *
  • 注意!集合为空若存在逻辑错误,请在 condition 条件中判断
  • *
  • 如果集合为 empty 则不会进行 sql 拼接
  • * * @param condition 执行条件 @@ -83,6 +80,7 @@ public interface Func extends Serializable { * 字段 IN (v0, v1, ...) *

    例: in("id", 1, 2, 3, 4, 5)

    * + *
  • 注意!数组为空若存在逻辑错误,请在 condition 条件中判断
  • *
  • 如果动态数组为 empty 则不会进行 sql 拼接
  • * * @param condition 执行条件 @@ -90,10 +88,7 @@ public interface Func extends Serializable { * @param values 数据数组 * @return children */ - default Children in(boolean condition, SFunction column, Object... values) { - return in(condition, column, Arrays.stream(Optional.ofNullable(values).orElseGet(() -> new Object[]{})) - .collect(toList())); - } + Children in(boolean condition, SFunction column, Object... values); /** * ignore @@ -129,10 +124,7 @@ public interface Func extends Serializable { * @param values 数据数组 * @return children */ - default Children notIn(boolean condition, SFunction column, Object... values) { - return notIn(condition, column, Arrays.stream(Optional.ofNullable(values).orElseGet(() -> new Object[]{})) - .collect(toList())); - } + Children notIn(boolean condition, SFunction column, Object... values); /** * ignore @@ -177,15 +169,8 @@ public interface Func extends Serializable { /** * ignore */ - default Children groupBy(SFunction column) { - return groupBy(true, column); - } - - /** - * ignore - */ - default Children groupBy(SFunction... columns) { - return groupBy(true, columns); + default Children groupBy(SFunction column, SFunction... columns) { + return groupBy(true, column, columns); } /** @@ -193,23 +178,17 @@ public interface Func extends Serializable { *

    例: groupBy("id", "name")

    * * @param condition 执行条件 + * @param column 单个字段 * @param columns 字段数组 * @return children */ - Children groupBy(boolean condition, SFunction... columns); + Children groupBy(boolean condition, SFunction column, SFunction... columns); /** * ignore */ - default Children orderByAsc(SFunction column) { - return orderByAsc(true, column); - } - - /** - * ignore - */ - default Children orderByAsc(SFunction... columns) { - return orderByAsc(true, columns); + default Children orderByAsc(SFunction column, SFunction... columns) { + return orderByAsc(true, column, columns); } /** @@ -217,25 +196,19 @@ public interface Func extends Serializable { *

    例: orderByAsc("id", "name")

    * * @param condition 执行条件 + * @param column 单个字段 * @param columns 字段数组 * @return children */ - default Children orderByAsc(boolean condition, SFunction... columns) { - return orderBy(condition, true, columns); + default Children orderByAsc(boolean condition, SFunction column, SFunction... columns) { + return orderBy(condition, true, column, columns); } /** * ignore */ - default Children orderByDesc(SFunction column) { - return orderByDesc(true, column); - } - - /** - * ignore - */ - default Children orderByDesc(SFunction... columns) { - return orderByDesc(true, columns); + default Children orderByDesc(SFunction column, SFunction... columns) { + return orderByDesc(true, column, columns); } /** @@ -243,11 +216,12 @@ public interface Func extends Serializable { *

    例: orderByDesc("id", "name")

    * * @param condition 执行条件 + * @param column 单个字段 * @param columns 字段数组 * @return children */ - default Children orderByDesc(boolean condition, SFunction... columns) { - return orderBy(condition, false, columns); + default Children orderByDesc(boolean condition, SFunction column, SFunction... columns) { + return orderBy(condition, false, column, columns); } /** @@ -256,10 +230,11 @@ public interface Func extends Serializable { * * @param condition 执行条件 * @param isAsc 是否是 ASC 排序 + * @param column 单个字段 * @param columns 字段数组 * @return children */ - Children orderBy(boolean condition, boolean isAsc, SFunction... columns); + Children orderBy(boolean condition, boolean isAsc, SFunction column, SFunction... columns); /** * ignore diff --git a/src/main/java/com/github/yulichang/wrapper/interfaces/Join.java b/src/main/java/com/github/yulichang/wrapper/interfaces/Join.java new file mode 100644 index 0000000..b45fee0 --- /dev/null +++ b/src/main/java/com/github/yulichang/wrapper/interfaces/Join.java @@ -0,0 +1,136 @@ +package com.github.yulichang.wrapper.interfaces; + +import java.io.Serializable; + +/** + * copy {@link com.baomidou.mybatisplus.core.conditions.interfaces.Join} + *

    + * 无改动 在mybatis 3.4.2 升级 3.4.3 后有改动 exists 和 not exists + * 为了保证 mybatis plus 3.4.3之前的也能正常使用 + */ +public interface Join extends Serializable { + + /** + * ignore + */ + default Children or() { + return or(true); + } + + /** + * 拼接 OR + * + * @param condition 执行条件 + * @return children + */ + Children or(boolean condition); + + /** + * ignore + */ + default Children apply(String applySql, Object... values) { + return apply(true, applySql, values); + } + + /** + * 拼接 sql + *

    !! 会有 sql 注入风险 !!

    + *

    例1: apply("id = 1")

    + *

    例2: apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")

    + *

    例3: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", LocalDate.now())

    + * + * @param condition 执行条件 + * @param values 数据数组 + * @return children + */ + Children apply(boolean condition, String applySql, Object... values); + + /** + * ignore + */ + default Children last(String lastSql) { + return last(true, lastSql); + } + + /** + * 无视优化规则直接拼接到 sql 的最后(有sql注入的风险,请谨慎使用) + *

    例: last("limit 1")

    + *

    注意只能调用一次,多次调用以最后一次为准

    + * + * @param condition 执行条件 + * @param lastSql sql语句 + * @return children + */ + Children last(boolean condition, String lastSql); + + /** + * ignore + */ + default Children comment(String comment) { + return comment(true, comment); + } + + /** + * sql 注释(会拼接在 sql 的最后面) + * + * @param condition 执行条件 + * @param comment sql注释 + * @return children + */ + Children comment(boolean condition, String comment); + + /** + * ignore + */ + default Children first(String firstSql) { + return first(true, firstSql); + } + + /** + * sql 起始句(会拼接在SQL语句的起始处) + * + * @param condition 执行条件 + * @param firstSql 起始语句 + * @return children + * @since 3.3.1 + */ + Children first(boolean condition, String firstSql); + + /** + * ignore + */ + default Children exists(String existsSql, Object... values) { + return exists(true, existsSql, values); + } + + /** + * 拼接 EXISTS ( sql语句 ) + *

    !! sql 注入方法 !!

    + *

    例: exists("select id from table where age = 1")

    + * + * @param condition 执行条件 + * @param existsSql sql语句 + * @param values 数据数组 + * @return children + */ + Children exists(boolean condition, String existsSql, Object... values); + + /** + * ignore + */ + default Children notExists(String existsSql, Object... values) { + return notExists(true, existsSql, values); + } + + /** + * 拼接 NOT EXISTS ( sql语句 ) + *

    !! sql 注入方法 !!

    + *

    例: notExists("select id from table where age = 1")

    + * + * @param condition 执行条件 + * @param existsSql sql语句 + * @param values 数据数组 + * @return children + */ + Children notExists(boolean condition, String existsSql, Object... values); +}