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