Skip to content

Commit e8c4c53

Browse files
committed
Properly handle null mixed with LIKE.
Resolves: #2653. Related: #2548, #2683, #2655, #2461, possibly #2544
1 parent 87f8263 commit e8c4c53

File tree

4 files changed

+42
-32
lines changed

4 files changed

+42
-32
lines changed

src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
*/
1616
package org.springframework.data.jpa.provider;
1717

18-
import static org.springframework.data.jpa.provider.JpaClassUtils.*;
18+
import static org.springframework.data.jpa.provider.JpaClassUtils.isEntityManagerOfType;
19+
import static org.springframework.data.jpa.provider.JpaClassUtils.isMetamodelOfType;
1920
import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*;
2021

2122
import java.util.Collections;
@@ -33,7 +34,6 @@
3334
import org.hibernate.ScrollMode;
3435
import org.hibernate.ScrollableResults;
3536
import org.hibernate.proxy.HibernateProxy;
36-
3737
import org.springframework.data.jpa.repository.query.JpaParameters;
3838
import org.springframework.data.jpa.repository.query.JpaParametersParameterAccessor;
3939
import org.springframework.data.util.CloseableIterator;
@@ -50,6 +50,8 @@
5050
* @author Thomas Darimont
5151
* @author Mark Paluch
5252
* @author Jens Schauder
53+
* @author Greg Turnquist
54+
* @author Yuriy Tsarkov
5355
*/
5456
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
5557

@@ -119,7 +121,8 @@ public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
119121
}
120122

121123
@Override
122-
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values, EntityManager em) {
124+
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values,
125+
EntityManager em) {
123126
return new HibernateJpaParametersParameterAccessor(parameters, values, em);
124127
}
125128
},
@@ -211,6 +214,7 @@ public Object getIdentifierFrom(Object entity) {
211214
static ConcurrentReferenceHashMap<Class<?>, PersistenceProvider> CACHE = new ConcurrentReferenceHashMap<>();
212215
private final Iterable<String> entityManagerClassNames;
213216
private final Iterable<String> metamodelClassNames;
217+
214218
/**
215219
* Creates a new {@link PersistenceProvider}.
216220
*
@@ -294,7 +298,8 @@ public static PersistenceProvider fromMetamodel(Metamodel metamodel) {
294298
return cacheAndReturn(metamodelType, GENERIC_JPA);
295299
}
296300

297-
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values, EntityManager em) {
301+
public JpaParametersParameterAccessor getParameterAccessor(JpaParameters parameters, Object[] values,
302+
EntityManager em) {
298303
return new JpaParametersParameterAccessor(parameters, values);
299304
}
300305

@@ -349,7 +354,7 @@ public static Object condense(Object value) {
349354
Class<?> typeParameterValue = ClassUtils.forName("org.hibernate.jpa.TypedParameterValue", classLoader);
350355

351356
if (typeParameterValue.isInstance(value)) {
352-
return "";
357+
return null;
353358
}
354359
} catch (ClassNotFoundException | LinkageError o_O) {
355360
return value;

src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
* @author Christoph Strobl
4646
* @author Jens Schauder
4747
* @author Andrey Kovalev
48+
* @author Yuriy Tsarkov
4849
*/
4950
class ParameterMetadataProvider {
5051

@@ -230,24 +231,28 @@ public Object prepare(Object value) {
230231

231232
Assert.notNull(value, "Value must not be null!");
232233

233-
Class<? extends T> expressionType = expression.getJavaType();
234+
Object condensedValue = PersistenceProvider.condense(value);
234235

235-
if (String.class.equals(expressionType)) {
236+
if (condensedValue == null || expression.getJavaType() == null) {
237+
return condensedValue;
238+
}
239+
240+
if (String.class.equals(expression.getJavaType())) {
236241

237242
switch (type) {
238243
case STARTING_WITH:
239-
return String.format("%s%%", escape.escape(PersistenceProvider.condense(value).toString()));
244+
return String.format("%s%%", escape.escape(condensedValue.toString()));
240245
case ENDING_WITH:
241-
return String.format("%%%s", escape.escape(PersistenceProvider.condense(value).toString()));
246+
return String.format("%%%s", escape.escape(condensedValue.toString()));
242247
case CONTAINING:
243248
case NOT_CONTAINING:
244-
return String.format("%%%s%%", escape.escape(PersistenceProvider.condense(value).toString()));
249+
return String.format("%%%s%%", escape.escape(condensedValue.toString()));
245250
default:
246-
return PersistenceProvider.condense(value);
251+
return condensedValue;
247252
}
248253
}
249254

250-
return Collection.class.isAssignableFrom(expressionType) //
255+
return Collection.class.isAssignableFrom(expression.getJavaType()) //
251256
? upperIfIgnoreCase(ignoreCase, toCollection(value)) //
252257
: value;
253258
}

src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java

+12-9
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static java.util.regex.Pattern.*;
19-
import static org.springframework.util.ObjectUtils.*;
18+
import static java.util.regex.Pattern.CASE_INSENSITIVE;
19+
import static org.springframework.util.ObjectUtils.nullSafeEquals;
20+
import static org.springframework.util.ObjectUtils.nullSafeHashCode;
2021

2122
import java.lang.reflect.Array;
2223
import java.util.ArrayList;
@@ -49,6 +50,7 @@
4950
* @author Jens Schauder
5051
* @author Diego Krupitza
5152
* @author Greg Turnquist
53+
* @author Yuriy Tsarkov
5254
*/
5355
class StringQuery implements DeclaredQuery {
5456

@@ -690,7 +692,7 @@ static class LikeParameterBinding extends ParameterBinding {
690692

691693
/**
692694
* Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type}.
693-
*
695+
*
694696
* @param name must not be {@literal null} or empty.
695697
* @param type must not be {@literal null}.
696698
*/
@@ -701,7 +703,7 @@ static class LikeParameterBinding extends ParameterBinding {
701703
/**
702704
* Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type} and parameter
703705
* binding input.
704-
*
706+
*
705707
* @param name must not be {@literal null} or empty.
706708
* @param type must not be {@literal null}.
707709
* @param expression may be {@literal null}.
@@ -765,20 +767,21 @@ public Type getType() {
765767
@Override
766768
public Object prepare(@Nullable Object value) {
767769

768-
if (value == null) {
770+
Object condensedValue = PersistenceProvider.condense(value);
771+
if (condensedValue == null) {
769772
return null;
770773
}
771774

772775
switch (type) {
773776
case STARTING_WITH:
774-
return String.format("%s%%", PersistenceProvider.condense(value));
777+
return String.format("%s%%", condensedValue);
775778
case ENDING_WITH:
776-
return String.format("%%%s", PersistenceProvider.condense(value));
779+
return String.format("%%%s", condensedValue);
777780
case CONTAINING:
778-
return String.format("%%%s%%", PersistenceProvider.condense(value));
781+
return String.format("%%%s%%", condensedValue);
779782
case LIKE:
780783
default:
781-
return PersistenceProvider.condense(value);
784+
return condensedValue;
782785
}
783786
}
784787

src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeHibernateIntegrationTests.java

+8-11
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,14 @@
5252
* Verify that {@literal LIKE}s mixed with {@literal NULL}s work properly.
5353
*
5454
* @author Greg Turnquist
55+
* @author Yuriy Tsarkov
5556
*/
5657
@ExtendWith(SpringExtension.class)
5758
@ContextConfiguration(classes = QueryWithNullLikeHibernateIntegrationTests.Config.class)
5859
@Transactional
5960
public class QueryWithNullLikeHibernateIntegrationTests {
6061

61-
@Autowired EmpoyeeWithNullLikeRepository repository;
62+
@Autowired EmployeeWithNullLikeRepository repository;
6263

6364
@BeforeEach
6465
void setUp() {
@@ -98,8 +99,7 @@ void customQueryWithNullMatch() {
9899

99100
List<EmployeeWithName> Employees = repository.customQueryWithNullableParam(null);
100101

101-
assertThat(Employees).extracting(EmployeeWithName::getName).containsExactlyInAnyOrder("Frodo Baggins",
102-
"Bilbo Baggins");
102+
assertThat(Employees).extracting(EmployeeWithName::getName).isEmpty();
103103
}
104104

105105
@Test
@@ -132,8 +132,7 @@ void derivedQueryStartsWithWithNullMatch() {
132132

133133
List<EmployeeWithName> Employees = repository.findByNameStartsWith(null);
134134

135-
assertThat(Employees).extracting(EmployeeWithName::getName).containsExactlyInAnyOrder("Frodo Baggins",
136-
"Bilbo Baggins");
135+
assertThat(Employees).extracting(EmployeeWithName::getName).isEmpty();
137136
}
138137

139138
@Test
@@ -167,8 +166,7 @@ void derivedQueryEndsWithWithNullMatch() {
167166

168167
List<EmployeeWithName> Employees = repository.findByNameEndsWith(null);
169168

170-
assertThat(Employees).extracting(EmployeeWithName::getName).containsExactlyInAnyOrder("Frodo Baggins",
171-
"Bilbo Baggins");
169+
assertThat(Employees).extracting(EmployeeWithName::getName).isEmpty();
172170
}
173171

174172
@Test
@@ -202,8 +200,7 @@ void derivedQueryContainsWithNullMatch() {
202200

203201
List<EmployeeWithName> Employees = repository.findByNameContains(null);
204202

205-
assertThat(Employees).extracting(EmployeeWithName::getName).containsExactlyInAnyOrder("Frodo Baggins",
206-
"Bilbo Baggins");
203+
assertThat(Employees).extracting(EmployeeWithName::getName).isEmpty();
207204
}
208205

209206
@Test
@@ -233,7 +230,7 @@ void derivedQueryLikeWithEmptyStringMatch() {
233230
}
234231

235232
@Transactional
236-
public interface EmpoyeeWithNullLikeRepository extends JpaRepository<EmployeeWithName, Integer> {
233+
public interface EmployeeWithNullLikeRepository extends JpaRepository<EmployeeWithName, Integer> {
237234

238235
@Query("select e from EmployeeWithName e where e.name like %:partialName%")
239236
List<EmployeeWithName> customQueryWithNullableParam(@Nullable @Param("partialName") String partialName);
@@ -248,7 +245,7 @@ public interface EmpoyeeWithNullLikeRepository extends JpaRepository<EmployeeWit
248245
}
249246

250247
@EnableJpaRepositories(considerNestedRepositories = true, //
251-
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = EmpoyeeWithNullLikeRepository.class))
248+
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = EmployeeWithNullLikeRepository.class))
252249
@EnableTransactionManagement
253250
static class Config {
254251

0 commit comments

Comments
 (0)