Spring JPA Repository首次访问正常后续请求返回500错误问题
这是Spring Boot 2.6.x 版本绑定的Spring Data JPA模块在处理派生查询时的已知逻辑缺陷,触发点是Containing这类自动生成LIKE语句的查询关键字、无@Param注解的参数绑定、SQL/绑定器缓存三个条件同时满足,你的三个疑问可以逐个拆解:
1. 为什么加@Param就能修复?@Param不是非必要操作吗?
正常情况下只要Java编译时开启了-parameters参数保留方法形参名,Spring Data JPA确实不需要强制加@Param就能自动匹配参数和查询占位符,但这个自动匹配逻辑在2.6.x版本里有分支漏洞:
- 框架对带
Containing/StartingWith/EndingWith的派生查询,会自动给参数拼接%通配符,同时处理参数内特殊字符的转义。如果方法参数没加@Param,第二次及之后复用缓存的参数绑定器时,框架不会正确读取方法传入的实参,反而会把内部转义逻辑用的临时值当成查询参数传入,直接触发类型不匹配错误。 - 加上
@Param("username")之后,框架会切换到显式命名参数的绑定分支,直接按注解指定的参数名从方法实参里取值,绕开了那个有bug的无注解参数绑定逻辑,自然就恢复正常了。
你项目里其他没加@Param的方法没出问题,本质是那些方法没有用到需要自动拼接通配符、做LIKE转义的查询关键字,根本碰不到这个bug分支。
2. 为什么首次访问永远正常,后续访问才报错?
Spring Data JPA的Repository方法是懒加载+本地缓存的:
- 第一次调用接口时,框架还没缓存这个方法对应的SQL模板、参数绑定器实例,会走完整的初始化流程:解析方法名生成查询语句、反射读取参数名、创建参数绑定器、执行转义逻辑、绑定实参、执行查询,整个流程参数取值是对的,所以能正常返回200。执行完成后,生成好的SQL、参数绑定器会被缓存下来,后续调用直接复用,不再走初始化流程。
- 第二次及之后调用时,框架直接拿缓存的参数绑定器用,但2.6.x版本里,无
@Param的LIKE类派生查询的绑定器,错误缓存了转义逻辑里的临时值引用,没有保留方法实参的取值入口,调用时直接把临时值传去查库,就会抛错。
3. 报错里的参数值[]从哪来?
这个\是Spring Data JPA处理LIKE查询时用的默认转义符:
生成LIKE查询时,框架会自动把传入参数里的%、_这类LIKE通配符用\转义,避免用户输入的特殊字符打乱查询逻辑。
这个bug的核心是2.6.x版本重构派生查询参数绑定时,漏处理了无@Param场景下转义符的引用传递:第一次初始化时,转义逻辑里临时用的\字符没有被正确释放,反而被存进了缓存的参数绑定器。后续调用时绑定器根本不读你传入的username参数,直接把存的\(char类型)传给了需要String类型的查询参数,JPA做参数类型校验时发现类型不匹配,就抛出了你看到的异常。
除了加
@Param的修复方式,你也可以选择升级Spring Boot到2.7.0及以上版本,官方在这个版本已经修复了该参数绑定bug;或者在配置文件中添加spring.data.jpa.repositories.escape-backslash=false关闭默认反斜杠转义,也能临时规避问题。
内容的提问来源于stack exchange,提问作者Woodchuck

