Mini Mybatis-Plus(中)

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

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

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

Mini Mybatis-Plus(中)_第1张图片

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

Mini Mybatis-Plus(中)_第2张图片

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

Mini Mybatis-Plus(中)_第3张图片

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

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

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

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

/**
 * 模拟MyBatis-Plus的LambdaQueryWrapper(思路完全不同,仅仅是形似)
 *
 * @author mx
 */
public class QueryWrapper {
    // conditionMap,收集查询条件
    // {
    //    " LIKE ": {
    //        "name": "mx123"
    //    },
    //    " = ": {
    //        "age": 18
    //    }
    // }
    private final Map 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 eq(ConditionFunction fn, Object value) {
        String columnName = Reflections.fnToColumnName(fn);
        conditionMap.put(OPERATOR_EQ, new SqlParam(columnName, value));
        return this;
    }

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

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

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

    public Map 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 extends Function, 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 
     * @return
     */
    public static  String fnToColumnName(ConditionFunction 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源码解析
 

你可能感兴趣的:(mybatis专题,mybatis,java)