Mini Mybatis-Plus(中)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

这一篇是用来过渡的,目的只有一个:模拟MyBatis-Plus的LambdaQueryWrapper。我相信,每一位用过MyBatis-Plus的同学都会惊叹于它对查询条件的精准控制,通过QueryWrapper几乎拼接出任何你想要的查询SQL。

原本我只打算模拟普通的QueryWrapper:

但后来想想,虽然是玩具,但做得逼真一点总是赏心悦目些。

其实LambdaQueryWrapper底层就是为我们自动拼装查询条件,所以实现的难点有两个:

  • 怎么通过Lambda表达式得到当前条件对应的columnName?
  • 怎么收集条件?

第一点是最难的,我原本打算从思路上模拟一下MyBatis-Plus的做法,结果发现实在太复杂了,直接放弃了。

最终我封装的QueryWrapper是这样的:

/**
 * 模拟MyBatis-Plus的LambdaQueryWrapper(思路完全不同,仅仅是形似)
 *
 * @author mx
 */
public class QueryWrapper<T> {
    // conditionMap,收集查询条件
    // {
    //    " LIKE ": {
    //        "name": "mx123"
    //    },
    //    " = ": {
    //        "age": 18
    //    }
    // }
    private final Map<String, SqlParam> conditionMap = new HashMap<>();

    // 操作符类型,比如 name like 'bravo' 中的 LIKE
    private static final String OPERATOR_EQ = " = ";
    private static final String OPERATOR_GT = " > ";
    private static final String OPERATOR_LT = " < ";
    private static final String OPERATOR_LIKE = " LIKE ";

    public QueryWrapper<T> eq(ConditionFunction<T, ?> fn, Object value) {
        String columnName = Reflections.fnToColumnName(fn);
        conditionMap.put(OPERATOR_EQ, new SqlParam(columnName, value));
        return this;
    }

    public QueryWrapper<T> gt(ConditionFunction<T, ?> fn, Object value) {
        String columnName = Reflections.fnToColumnName(fn);
        conditionMap.put(OPERATOR_GT, new SqlParam(columnName, value));
        return this;
    }

    public QueryWrapper<T> lt(ConditionFunction<T, ?> fn, Object value) {
        String columnName = Reflections.fnToColumnName(fn);
        conditionMap.put(OPERATOR_LT, new SqlParam(columnName, value));
        return this;
    }

    public QueryWrapper<T> like(ConditionFunction<T, ?> fn, Object value) {
        String columnName = Reflections.fnToColumnName(fn);
        conditionMap.put(OPERATOR_LIKE, new SqlParam(columnName, "%" + value + "%"));
        return this;
    }

    public Map<String, SqlParam> build() {
        return conditionMap;
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SqlParam {
    private String columnName;
    private Object value;
}

// ************* 辅助工具类 ***********

/**
 * 扩展java.util.function包下的Function接口:支持Serializable
 * 搭配Reflections工具类一起使用,用于获取Lambda表达式的方法名
 *
 * @author mx
 */
@FunctionalInterface
public interface ConditionFunction<T, R> extends Function<T, R>, Serializable {
}

/**
 * 获取Lambda入参的方法名
 *
 * @author mx
 */
public class Reflections {
    private static final Pattern GET_PATTERN = Pattern.compile("^get[A-Z].*");
    private static final Pattern IS_PATTERN = Pattern.compile("^is[A-Z].*");

    /**
     * 注意: 非标准变量(非小驼峰)调用这个方法可能会有问题
     *
     * @param fn
     * @param <T>
     * @return
     */
    public static <T> String fnToColumnName(ConditionFunction<T, ?> fn) {
        try {
            Method method = fn.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(fn);
            String getter = serializedLambda.getImplMethodName();
            // 对于非标准变量生成的Get方法这里可以直接抛出异常,或者打印异常日志
            if (GET_PATTERN.matcher(getter).matches()) {
                getter = getter.substring(3);
            } else if (IS_PATTERN.matcher(getter).matches()) {
                getter = getter.substring(2);
            }
            return Introspector.decapitalize(getter);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }
}

主要思路是:把所有查询条件封装到Map中,key是操作符,比如=、like、>或<,value是SqlParam对象,包括columName和columnValue。

{
  " LIKE ": {
    "name": "mx123"
  },
  " = ": {
    "age": 18
  }
}

key的空格是为了拼接SQL时方便,比如 name LIKE '%mx%',如果不加空格会变成 nameLIKE'%mx%'。

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析
 

相关推荐

  1. Mybatis-pluswrapper的区别

    2023-12-28 11:56:06       59 阅读
  2. MyBatis-PlusLambdaQueryWrapper的探究

    2023-12-28 11:56:06       51 阅读
  3. element-plus的表单校验

    2023-12-28 11:56:06       48 阅读
  4. Element-plus使用遇到的问题

    2023-12-28 11:56:06       34 阅读
  5. vue3element Plus插槽

    2023-12-28 11:56:06       31 阅读
  6. vue3修改element plus 主题色

    2023-12-28 11:56:06       64 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-28 11:56:06       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-28 11:56:06       106 阅读
  3. 在Django里面运行非项目文件

    2023-12-28 11:56:06       87 阅读
  4. Python语言-面向对象

    2023-12-28 11:56:06       96 阅读

热门阅读

  1. 59.0/滤镜的使用(详细版)

    2023-12-28 11:56:06       43 阅读
  2. LeetCode389. Find the Difference

    2023-12-28 11:56:06       58 阅读
  3. 模型部署之——ONNX模型转RKNN

    2023-12-28 11:56:06       52 阅读
  4. sql 随机排序优化

    2023-12-28 11:56:06       51 阅读
  5. flutter websocket发送ping包?

    2023-12-28 11:56:06       61 阅读
  6. 知识付费小程序如何搭建?

    2023-12-28 11:56:06       57 阅读
  7. Python语法知识的笔记

    2023-12-28 11:56:06       46 阅读
  8. django的gunicorn的异步任务执行

    2023-12-28 11:56:06       63 阅读
  9. 模板方法模式(Template Method)

    2023-12-28 11:56:06       55 阅读