kotlin使用spring-data-jpa+query-dsl踩坑记录
本文最后更新于:2023年8月15日 晚上
背景
使用kotlin+springboot+spring-data-jpa搭建的项目,在引入query-dsl依赖来提供通用查询服务时出现以下错误:

原来的代码
Entity
1 | |
Repository
1 | |
Rest
1 | |
修改后的代码
Repository
1 | |
代码中的
QUser是query-dsl生成的类,包含User的各种信息,用于组成dsl风格的查询语法
Rest
1 | |
排查
首先看错误语句No property customize found for type User!,翻译出来看是User没有可自定义的字段,很迷惑,感觉跟改动的代码扯不上什么关系,看不懂o(╥﹏╥)o
这只能debug了,把断点打在org.springframework.data.mapping.PropertyPath#PropertyPath方法,第90行,也就是抛出错误的地方看看:

可以看到这里原来是多出来一个叫customize的字段,但我们实际上又没有这个字段,所以获取字段信息的时候报错了,跟着堆栈往上看看为什么会多出来这么一个字段吧!一直往上找,知道找到这个customize字段的来源,可以看到PartTreeJpaQuery构造方法,第89行,原来是这里把之前写的customize方法当做字段名来解析了。

看来是被识别为jpa的一个查询方法了,按理来说,customize方法是接口默认方法,不应该被识别出来的,我再往上看看(⊙︿⊙)
追到QueryExecutorMethodInterceptor#mapMethodsToQuery方法,可以看到这里应该是获取了所有查询方法去解析出对应的查询sql,看来是这个获取所有查询方法的方法里错误地将customize方法也放了进来。

点进去看一下,在RepositoryInformation的默认实现DefaultRepositoryInformation.getQueryMethods中,可以看到调用了isQueryMethodCandidate方法来判断是否是查询方法。

看一下这个方法的逻辑:

可以看到是有是否是默认方法的判断的,为什么会失效呢,debug到这里看一下

可以看到,customize方法到这里的时候,并不是default的,甚至可以看到abstract修饰符,这有点不太符合常识,考虑到kotlin实际上是把代码编译成java的字节码在jvm上执行,有可能是kotlin在编译的时候并没有将这个方法编译为default方法,那就反编译出java代码来看一下:

原来并不是我们想的那样编译为了默认方法,而是生成了一个静态内部类,将原本customize方法的逻辑迁到了静态内部类中,这样子,jpa肯定会认为customize方法是查询方法了
原因
搜索了一下相关资料,在这篇文章Kotlin JVM常用注解参数解析中找到了答案:

总而言之,就是kotlin为了适配1.8之前的java版本,采取了另一种方式来实现kotlin接口的默认实现,而springjpa使用jdk1.8之后的逻辑来扫描接口方法,就导致了问题的出现.
解决
上面的文章中已经说明了如何解决问题,我们来尝试一下。
修改Repository
1 | |
在gradle配置文件中增加jvm参数
1 | |
再启动项目,发现已经不抱错了,再看一下反编译出来的的java代码:

可以看到已经没有静态内部类了,customize方法也已经是default方法了,问题解决.
附
新版本的kotlin已经将@JvmDefault标记为Deprecated了,后续可以使用jvm参数-Xjvm-default=all-compatibility或-Xjvm-default=all将所有接口类统一处理