【发布时间】:2019-08-24 23:53:51
【问题描述】:
我们使用 SpringBoot 1.5 有以下工作查询:
@Query(value = "SELECT DISTINCT c FROM Customer c INNER JOIN c.industry i WHERE " +
"c.role IN :roleFilter " +
"AND (:#{#industryFilter.size()} = 1 OR i.id IN :industryFilter) " +
"AND (:searchString IS NULL " +
"OR CONCAT_WS(' ', c.name, c.name2) LIKE CONCAT('%', :searchString, '%') " +
"OR CONCAT_WS(' ', c.name2, c.name) LIKE CONCAT('%', :searchString, '%')) " +
"AND (:includeDeleted = true OR c.deletedDate is NULL)",
countQuery = "SELECT COUNT(DISTINCT c) FROM Customer c INNER JOIN c.industry i WHERE " +
"c.role IN :roleFilter AND " +
"(:#{#industryFilter.size()} = 1 OR i.id IN :industryFilter) " +
"AND (:searchString IS NULL " +
"OR CONCAT_WS(' ', c.name, c.name2) LIKE CONCAT('%', :searchString, '%') " +
"OR CONCAT_WS(' ', c.name2, c.name) LIKE CONCAT('%', :searchString, '%')) " +
"AND (:includeDeleted = true OR c.deletedDate is NULL)")
Page<Customer> findCustomers(@Param("roleFilter") Set<Role> roleFilter,
@Param("industryFilter") Set<String> industryFilter,
@Param("searchString") String searchString,
@Param("includeDeleted") boolean includeDeleted, Pageable pageable);
请注意我们如何将输入传递给 LIKE:CONCAT('%', :searchString, '%')
从springBootVersion = '1.5.17.RELEASE' 升级到springBootVersion = '2.1.3.RELEASE'(我们使用Gradle)后,查询将在运行时失败并出现异常:
org.hibernate.QueryException:命名参数未绑定:includeDeleted
将CONCAT('%', :searchString, '%') 替换为%:searchString% 可以解决问题。
我的问题是:为什么?
通过进入调试模式并遵循完整的调用堆栈,我可以看到从方法调用中正确检索到的参数,正如在第 205 行的JdkDynamicAopProxy 中观察到的那样,调用Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); 会导致:
argsToUse = {Object[5]@15562}
0 = {HashSet@15491} size = 4
1 = {HashSet@15628} size = 1
2 = null
3 = {Boolean@15629} false
4 = {PageRequest@15490} "Page request [number: 0, size 20, sort: name: ASC,name2: ASC]"
到目前为止一切顺利。然后,我们继续,调用的方法也正确解析:
parameterTypes = {Class[5]@15802}
0 = {Class@198} "interface java.util.Set"
1 = {Class@198} "interface java.util.Set"
2 = {Class@311} "class java.lang.String"
3 = {Class@15811} "boolean"
4 = {Class@9875} "interface org.springframework.data.domain.Pageable"
然后我们走得更远一点,我们到达RepositoryFactorySupport 第 599 行调用 private Object doInvoke(MethodInvocation invocation) throws Throwable,它使用内部类 private final Map<Method, RepositoryQuery> queries; 中的 private final Map<Method, RepositoryQuery> queries; public class QueryExecutorMethodInterceptor implements MethodInterceptor(我不确定这个变量是何时/如何创建和填充的),它包含在我的存储库界面中使用@Query 注释的所有查询。
对于我们的具体情况,它包含一个与我正在调用的查询匹配的条目(最后一个)(findCustomers):
queries = {HashMap@16041} size = 3
0 = {HashMap$Node@16052} "public abstract com.swisscom.psp.domain.Customer com.swisscom.psp.repository.CustomerRepository.getOne(java.lang.String)" ->
1 = {HashMap$Node@16055} "public abstract boolean com.swisscom.psp.repository.CustomerRepository.existsWithRole(java.lang.String,java.util.Set)" ->
2 = {HashMap$Node@16058} "public abstract org.springframework.data.domain.Page com.swisscom.psp.repository.CustomerRepository.findCustomers(java.util.Set,java.util.Set,java.lang.String,boolean,org.springframework.data.domain.Pageable)" ->
扩展该条目我可以看到错误来自哪里,:includeDeleted 命名参数的绑定根本不存在:
value = {SimpleJpaQuery@16060}
query = {ExpressionBasedStringQuery@16069}
query = "SELECT DISTINCT c FROM Customer c INNER JOIN c.industry i WHERE c.role IN :roleFilter AND (:__$synthetic$__1 = 1 OR i.id IN :industryFilter) AND (:searchString IS NULL OR CONCAT_WS(' ', c.name, c.name2) LIKE CONCAT('%', :searchString, '%') OR CONCAT_WS(' ', c.name2, c.name) LIKE CONCAT('%', :searchString, '%')) AND (:includeDeleted = true OR c.deletedDate is NULL)"
bindings = {ArrayList@16089} size = 6
0 = {StringQuery$InParameterBinding@16092} "ParameterBinding [name: roleFilter, position: null, expression: null]"
1 = {StringQuery$ParameterBinding@16093} "ParameterBinding [name: __$synthetic$__1, position: null, expression: #industryFilter.size()]"
2 = {StringQuery$InParameterBinding@16094} "ParameterBinding [name: industryFilter, position: null, expression: null]"
3 = {StringQuery$ParameterBinding@16095} "ParameterBinding [name: searchString, position: null, expression: null]"
4 = {StringQuery$ParameterBinding@16096} "ParameterBinding [name: searchString, position: null, expression: null]"
5 = {StringQuery$ParameterBinding@16097} "ParameterBinding [name: searchString, position: null, expression: null]"
现在,我已经完成了前面提到的修复,但我仍然非常想知道以下内容以供将来参考:
-
private final Map<Method, RepositoryQuery> queries变量是何时以及如何创建和填充的? - 究竟是什么导致了这个错误?我在升级过程中错过了什么吗?我是否使用/混合了已弃用的逻辑/错误的逻辑,是否应该进一步更改代码?
我们的数据库是 MariaDB 10.1.36
编辑:在所有发生此行为的地方(在某些情况下仍然发生),未绑定参数始终是最后一个
EDIT2:别人升级后也有类似的行为,为什么会这样? reference
EDIT3:reference 并且已经报告了这种奇怪的行为。有趣的是,如果我将已经连接的输入传递给 :searchString(例如:%SOMETHING%),我不会得到异常,如果我离开 %:searchString% 会得到异常。是的,最终移动这些参数可以解决我在绑定时遇到的一些错误。
EDIT4:可能与bug有关?
显然发生了一些奇怪的事情,所以:这种绑定解决方案究竟是如何发生的?
提前致谢,祝你有美好的一天
【问题讨论】:
标签: spring spring-boot spring-data-jpa spring-data spring2.x