从源码角度对比 java 中 myibatis plus 的 lambdaQuery 的使用来看在 kotlin 使用中的问题
正确使用方法:
1. `LambdaQueryWrapper<PoemsAuthor> queryWrapper = Wrappers.<PoemsAuthor>lambdaQuery();`
2. `SFunction<PoemsAuthor, String> function = PoemsAuthor::getName;`
3. `queryWrapper.eq(function,"苏轼");`
4. `PoemsAuthor poemsAuthor = poemsAuthorService.getOne(queryWrapper);`
错误使用方法:
1. `LambdaQueryWrapper<PoemsAuthor> queryWrapper = Wrappers.<PoemsAuthor>lambdaQuery();`
2. `//@Note 这里如果是新建一个对象而不是使用lambda的写法,在myibatis-plus的内部com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda.resolve中无法识别,将会报错`
3. `SFunction<PoemsAuthor, String> sFunction = new SFunction<PoemsAuthor, String>() {`
4. `@Override`
5. `public String apply(PoemsAuthor poemsAuthor) {`
6. `return poemsAuthor.getName();`
7. `}`
8. `};`
9. `queryWrapper.eq(sFunction,"苏轼");`
10. `PoemsAuthor poemsAuthor = poemsAuthorService.getOne(queryWrapper);`
这里在使用时会在调用 eq 方法时报错,错误信息为"该方法仅能传入 lambda 表达式产生的合成类"
第一步调用 com.baomidou.mybatisplus.core.conditions.interfaces.Compare#eq(R, java.lang.Object)方法:
1. `/**`
2. `* ignore`
3. `*/`
4. `default Children eq(R column, Object val) {`
5. `return eq(true, column, val);`
6. `}`
之所以能够这样调用是因为 wrapper 类的继承关系,详细继承关系如下图:
我们再来看看 LambdaQueryWrapper 的父类 AbstractLambdaWrapper 类的签名如下:
1. `public abstract class AbstractLambdaWrapper<T, Children extends AbstractLambdaWrapper<T, Children>>`
2. `extends AbstractWrapper<T, SFunction<T, ?>, Children>`
我们再看一下 AbstractLambdaWrapper 的父类 AbstractWrapper 的签名:
1. `public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T>`
2. `implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R>`
可以看到泛型 R 对应的类型为 SFunction,而这个 SFunction 是个什么东东呢,我们来看一看:
1. `@FunctionalInterface`
2. `public interface SFunction<T, R> extends Serializable {`
3.
4. `/**`
5. `* Applies this function to the given argument.`
6. `*`
7. `* @param t the function argument`
8. `* @return the function result`
9. `*/`
10. `R apply(T t);`
11. `}`
看到这个我们应该会想到 java8 的 lambda 中的方法引用,比如:
1. `Function
<
CharSequence
,
Boolean
>
isBlank
=
StringUtils
::
isBlank
;`
它也可以写成:
1. `SFunction
<
CharSequence
,
Boolean
>
isBlank1
=
StringUtils
::
isBlank
;`
闲话少说,我们接着看 eq 方法的处理流程,接下来会调用 com.baomidou.mybatisplus.extension.service.additional.AbstractChainWrapper#eq 方法:
1. `@Override`
2. `public Children eq(boolean condition, R column, Object val) {`
3. `getWrapper().eq(condition, column, val);`
4. `return typedThis;`
5. `}`
然后调用到 com.baomidou.mybatisplus.core.conditions.AbstractWrapper#eq 方法:
1. `@Override`
2. `public Children eq(boolean condition, R column, Object val) {`
3. `return addCondition(condition, column, EQ, val);`
4. `}`
进入 addCondition 方法:
1. `/**`
2. `* 普通查询条件`
3. `*`
4. `* @param condition 是否执行`
5. `* @param column 属性`
6. `* @param sqlKeyword SQL 关键词`
7. `* @param val 条件值`
8. `*/`
9. `protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) {`
10. `return doIt(condition, () -> columnToString(column), sqlKeyword, () -> formatSql("{0}", val));`
11. `}`
它调用的 columnToString 方法为 com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper#columnToString(com.baomidou.mybatisplus.core.toolkit.support.SFunction
1. `@Override`
2. `protected String columnToString(SFunction<T, ?> column) {`
3. `return columnToString(column, true);`
4. `}`
进入重载方法的代码为:
1. `protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {`
2. `return getColumn(LambdaUtils.resolve(column), onlyColumn);`
3. `}`
这里分为两步,先通过 LambdaUtils 的 resolve 方法校验传入的 lambda 表达式(也就是一个 Function),如果校验失败就会报错,校验成功则返回 SerializedLambda 对象。com.baomidou.mybatisplus.core.toolkit.LambdaUtils#resolve 的代码为:
1. `/**`
2. `* 解析 lambda 表达式`
3. `*`
4. `* @param func 需要解析的 lambda 对象`
5. `* @param <T> 类型,被调用的 Function 对象的目标类型`
6. `* @return 返回解析后的结果`
7. `*/`
8. `public static <T> SerializedLambda resolve(SFunction<T, ?> func) {`
9. `Class clazz = func.getClass();`
10. `return Optional.ofNullable(FUNC_CACHE.get(clazz))`
11. `.map(WeakReference::get)`
12. `.orElseGet(() -> {`
13. `SerializedLambda lambda = SerializedLambda.resolve(func);`
14. `FUNC_CACHE.put(clazz, new WeakReference<>(lambda));`
15. `return lambda;`
16. `});`
17. `}`
- 如果是正确的 lambda 表示式,这里获取到的 clazz 是形如 class com.ambition.poetry.JavaTest$647/1708990865
- 如果是传入 SFunction 对象这里获取到的 clazz 是形如 com.ambition.poetry.JavaTest$1
下面会调用 SerializedLambda.resolve 方法,负责将一个支持序列的 Function 序列化为 SerializedLambda,它的代码为:
1. `/**`
2. `* 通过反序列化转换 lambda 表达式,该方法只能序列化 lambda 表达式,不能序列化接口实现或者正常非 lambda 写法的对象`
3. `*`
4. `* @param lambda lambda对象`
5. `* @return 返回解析后的 SerializedLambda`
6. `*/`
7. `public static SerializedLambda resolve(SFunction lambda) {`
8. `if (!lambda.getClass().isSynthetic()) {`
9. `throw ExceptionUtils.mpe("该方法仅能传入 lambda 表达式产生的合成类");`
10. `}`
11. `try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) {`
12. `@Override`
13. `protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {`
14. `Class<?> clazz = super.resolveClass(objectStreamClass);`
15. `return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;`
16. `}`
17. `}) {`
18. `return (SerializedLambda) objIn.readObject();`
19. `} catch (ClassNotFoundException | IOException e) {`
20. `throw ExceptionUtils.mpe("This is impossible to happen", e);`
21. `}`
22. `}`
会先通过 isSynthetic 对传入的 lambda 表达式的合法性进行校验,如果不合法就会抛出异常和错误信息"该方法仅能传入 lambda 表达式产生的合成类" 一如文首所提到的。我们看下校验方法 isSynthetic 代码:
1. `public boolean isSynthetic() {`
2. `return (getModifiers() & SYNTHETIC) != 0;`
3. `}`
- SYNTHETIC 的值为 4096
- 正确的 lambda 传入时 getModifiers()取到的值为 4112,最后解析返回的 SerializedLambda 对象格式如下:
- 非 lambda 传入时 getModifiers()取到的值为 0,检验将无法通过,抛出异常。
处理完成后将解析获取到的 SerializedLambda 对象传入 com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper#getColumn 方法:
1. `private String getColumn(SerializedLambda lambda, boolean onlyColumn) {`
2. `//通过get方法获取属性名`
3. `String fieldName = StringUtils.resolveFieldName(lambda.getImplMethodName());`
4. `if (!initColumnMap || !columnMap.containsKey(fieldName.toUpperCase(Locale.ENGLISH))) {`
5. `String entityClassName = lambda.getImplClassName();`
6. `//获取实体属性与库中字段的映射关系`
7. `columnMap = LambdaUtils.getColumnMap(entityClassName);`
8. `Assert.notEmpty(columnMap, "cannot find column's cache for \"%s\", so you cannot used \"%s\"!",`
9. `entityClassName, typedThis.getClass());`
10. `initColumnMap = true;`
11. `}`
12. `return Optional.ofNullable(columnMap.get(fieldName.toUpperCase(Locale.ENGLISH)))`
13. `.map(onlyColumn ? ColumnCache::getColumn : ColumnCache::getColumnSelect)`
14. `.orElseThrow(() -> ExceptionUtils.mpe("your property named \"%s\" cannot find the corresponding database column name!", fieldName));`
15. `}`
上面的代码逻辑大致是先通过传入的 lambda 对象解析出 field 名称,然后去实体类与表的列之间的映射中获取实际的列名。这里我们可以稍微瞄一眼 resolveFieldName 方法:
1. `/**`
2. `* 解析 getMethodName -> propertyName`
3. `*`
4. `* @param getMethodName 需要解析的`
5. `* @return 返回解析后的字段名称`
6. `*/`
7. `public static String resolveFieldName(String getMethodName) {`
8. `if (getMethodName.startsWith("get")) {`
9. `getMethodName = getMethodName.substring(3);`
10. `} else if (getMethodName.startsWith(IS)) {`
11. `getMethodName = getMethodName.substring(2);`
12. `}`
13. `// 小写第一个字母`
14. `return StringUtils.firstToLowerCase(getMethodName);`
15. `}`
无非是将传入的 lambda 表达式中的属性值提取出来,前提是传入的是属性的 get 方法的引用格式的 lambda 表达式。
方式一:
1. `val function = SFunction<PoemsAuthor, String> { it.name }`
2. `val lambdaQuery = Wrappers.lambdaQuery<PoemsAuthor>()`
3. `lambdaQuery.eq(function,"杜甫")`
或者:
1. `val sFunction = SFunction<PoemsAuthor, String> { poemsAuthor -> poemsAuthor.name }`
2. `val lambdaQuery = Wrappers.lambdaQuery<PoemsAuthor>()`
3. `lambdaQuery.eq(sFunction,"杜甫")`
很明显,这种方式是通过新建 SFunction 对象来处理的。最终会抛出异常。
方式二: 本来是想尝试和 java 一样的写法,但是编译无法通过,因为
这里会将 PoemsAuthor::getName 识别成 KFunction 类型,KFunction 是 kotlin 中的高阶函数,与 kotlin 中的 lambda 表达式有着极其密切的关系,其中 KFunction 接受的类型如下:
| KFunction | Analogue | ReceiverFunction |
|---|---|---|
| KFunction1 | (Interface) -> Result | Interface.() -> Result |
| KFunction2 | (Interface, Input) -> Result | Interface.(Input) -> Result |
| KFunction3 | (Interface, In1, In2) -> Result | Interface.(In1, In2) -> Result |
| KSuspendFunction1 | suspend (Interface) -> Result | suspend Interface.() -> Result |
| KSuspendFunction2 | suspend (Interface, Input) -> Result | suspend Interface.(Input) -> Result |
在 kotlin 中 Lambdas 表达式是花括号括起来的代码块。如果要实现一个 java 的函数式接口,需要类型加上 lambda 的方式,如:
1. `SFunction<PoemsAuthor, String> { poemsAuthor -> poemsAuthor.name }`
但是这种方式在 myibatis-plus 中又会识别出不是原生的 java lambda 表达式,从而解析出错。更多关于 kotlin 的 lambda 的内容参考:https://kotlinlang.org/docs/reference/lambdas.html
kotlin 中用 myibatis-plus 进行查询时不使用 lambdaQuery,改用普通的 Query,问题解决。
