【问题标题】:Spring boot 2 @Query named parameter binding value resolution messes up after upgrade from 1.5从 1.5 升级后 Spring boot 2 @Query 命名参数绑定值解析搞砸了
【发布时间】: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&lt;Method, RepositoryQuery&gt; queries; 中的 private final Map&lt;Method, RepositoryQuery&gt; 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]"

现在,我已经完成了前面提到的修复,但我仍然非常想知道以下内容以供将来参考:

  1. private final Map&lt;Method, RepositoryQuery&gt; queries 变量是何时以及如何创建和填充的?
  2. 究竟是什么导致了这个错误?我在升级过程中错过了什么吗?我是否使用/混合了已弃用的逻辑/错误的逻辑,是否应该进一步更改代码?

我们的数据库是 MariaDB 10.1.36

编辑:在所有发生此行为的地方(在某些情况下仍然发生),未绑定参数始终是最后一个

EDIT2:别人升级后也有类似的行为,为什么会这样? reference

EDIT3:reference 并且已经报告了这种奇怪的行为。有趣的是,如果我将已经连接的输入传递给 :searchString(例如:%SOMETHING%),我不会得到异常,如果我离开 %:searchString% 会得到异常。是的,最终移动这些参数可以解决我在绑定时遇到的一些错误。

EDIT4:可能与bug有关?

显然发生了一些奇怪的事情,所以:这种绑定解决方案究竟是如何发生的

提前致谢,祝你有美好的一天

【问题讨论】:

    标签: spring spring-boot spring-data-jpa spring-data spring2.x


    【解决方案1】:

    实际上,据我所知,您的两种方法都不是用于处理带有通配符占位符的LIKE 的正确方法。相反,LIKE 表达式应该是:

    LIKE :searchString
    

    你应该绑定到这个参数:searchString

    String searchString = "bananas";
    String param = "%" + searchString + "%";
    // then bind param to :searchString
    

    也就是说,您将整个字符串与% 通配符绑定在一起。然后,让数据库操心如何逃逸。

    【讨论】:

    • 根据Spring Data JPA reference示例65,支持:@Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%") List&lt;User&gt; findByLastnameWithSpelExpression(@Param("lastname") String lastname);
    • 如果您向下滚动该页面,您会看到一个带有参数的 LIKE 示例和语言 parameter bound with appended %。这正是我的回答所说的。
    • 对不起,我真的没有看到。在提供的链接中,我正在查看示例 65,其中 % 字符在查询本身中未加引号,而不是在绑定之前。该页面中对 % 的其他引用导致了一个带有 StringMatcher 的示例(表 5)以及命名和查询之间的转换(示例 52),但没有提及在绑定之前进行连接。我们指的是同一个源链接吗?还有待确定,为什么在更改为 Spring 2 之前它可以工作?感谢您的帮助
    • 另外,也许不相关,我们拥有的数据库是 MariaDB 10.1.36
    猜你喜欢
    • 1970-01-01
    • 2017-10-03
    • 2017-12-14
    • 1970-01-01
    • 2012-07-31
    • 2019-06-14
    • 1970-01-01
    • 2019-05-30
    • 2019-03-24
    相关资源
    最近更新 更多