Spring boot 2 @Query Разрешение значения привязки именованного параметра портится после обновления с 1.5
У нас есть следующий рабочий запрос с использованием 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%
устраняет проблему
У меня вопрос: почему?
Войдя в режим отладки и следуя полному стеку вызовов, я мог видеть, что параметры правильно извлекаются из вызова метода, как это наблюдалось в JdkDynamicAopProxy
на линии 205 звонит 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;
из внутреннего класса 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: кто-то еще имеет аналогичное поведение после обновления, почему это происходит? ссылка
EDIT3: ссылка, а также это странное поведение было сообщено. Интересно, что я не получаю исключение, если я передаю уже сцепленные входные данные:searchString (например: %SOMETHING%), и я получаю исключение, если я оставляю%: searchString%. И да, перемещение этих параметров в конце решает некоторые ошибки, которые я имел с привязкой.
EDIT4: Может быть, связана ошибка?
Очевидно, что происходит нечто странное, поэтому: как именно происходит это обязательное разрешение?
Спасибо заранее и хорошего дня
1 ответ
На самом деле, насколько я знаю, ни один из ваших двух подходов не является правильным для использования здесь для обработки LIKE
с подстановочным знаком. Вместо этого LIKE
выражение должно быть:
LIKE :searchString
К этому параметру :searchString
Вы должны быть обязательными:
String searchString = "bananas";
String param = "%" + searchString + "%";
// then bind param to :searchString
То есть вы связываете всю строку, с %
подстановочный знак, вместе. Затем позвольте базе данных беспокоиться о том, как избежать этого.