Skip to content

Commit 34e16e3

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

File tree

4 files changed

+37
-43
lines changed

4 files changed

+37
-43
lines changed

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

+5-7
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 jakarta.persistence.EntityManager;
@@ -24,11 +25,7 @@
2425
import jakarta.persistence.metamodel.Metamodel;
2526
import jakarta.persistence.metamodel.SingularAttribute;
2627

27-
import java.util.Collection;
28-
import java.util.Collections;
29-
import java.util.List;
30-
import java.util.NoSuchElementException;
31-
import java.util.Set;
28+
import java.util.*;
3229

3330
import org.eclipse.persistence.config.QueryHints;
3431
import org.eclipse.persistence.jpa.JpaQuery;
@@ -53,6 +50,7 @@
5350
* @author Mark Paluch
5451
* @author Jens Schauder
5552
* @author Greg Turnquist
53+
* @author Yuriy Tsarkov
5654
*/
5755
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment {
5856

@@ -330,7 +328,7 @@ public static Object condense(Object value) {
330328
Class<?> typeParameterValue = ClassUtils.forName("org.hibernate.query.TypedParameterValue", classLoader);
331329

332330
if (typeParameterValue.isInstance(value)) {
333-
return "";
331+
return null;
334332
}
335333
} catch (ClassNotFoundException | LinkageError o_O) {
336334
return value;

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

+11-15
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@
1818
import jakarta.persistence.criteria.CriteriaBuilder;
1919
import jakarta.persistence.criteria.ParameterExpression;
2020

21-
import java.util.ArrayList;
22-
import java.util.Arrays;
23-
import java.util.Collection;
24-
import java.util.Collections;
25-
import java.util.Iterator;
26-
import java.util.List;
21+
import java.util.*;
2722
import java.util.function.Supplier;
2823
import java.util.stream.Collectors;
2924

@@ -50,6 +45,7 @@
5045
* @author Christoph Strobl
5146
* @author Jens Schauder
5247
* @author Andrey Kovalev
48+
* @author Yuriy Tsarkov
5349
*/
5450
class ParameterMetadataProvider {
5551

@@ -237,28 +233,28 @@ public Object prepare(Object value) {
237233

238234
Assert.notNull(value, "Value must not be null");
239235

240-
Class<? extends T> expressionType = expression.getJavaType();
236+
Object condensedValue = PersistenceProvider.condense(value);
241237

242-
if (expressionType == null) {
243-
return value;
238+
if (condensedValue == null || expression.getJavaType() == null) {
239+
return condensedValue;
244240
}
245241

246-
if (String.class.equals(expressionType) && !noWildcards) {
242+
if (String.class.equals(expression.getJavaType()) && !noWildcards) {
247243

248244
switch (type) {
249245
case STARTING_WITH:
250-
return String.format("%s%%", escape.escape(PersistenceProvider.condense(value).toString()));
246+
return String.format("%s%%", escape.escape(condensedValue.toString()));
251247
case ENDING_WITH:
252-
return String.format("%%%s", escape.escape(PersistenceProvider.condense(value).toString()));
248+
return String.format("%%%s", escape.escape(condensedValue.toString()));
253249
case CONTAINING:
254250
case NOT_CONTAINING:
255-
return String.format("%%%s%%", escape.escape(PersistenceProvider.condense(value).toString()));
251+
return String.format("%%%s%%", escape.escape(condensedValue.toString()));
256252
default:
257-
return PersistenceProvider.condense(value);
253+
return condensedValue;
258254
}
259255
}
260256

261-
return Collection.class.isAssignableFrom(expressionType) //
257+
return Collection.class.isAssignableFrom(expression.getJavaType()) //
262258
? upperIfIgnoreCase(ignoreCase, toCollection(value)) //
263259
: value;
264260
}

spring-data-jpa/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

@@ -638,7 +640,7 @@ static class LikeParameterBinding extends ParameterBinding {
638640

639641
/**
640642
* Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type}.
641-
*
643+
*
642644
* @param name must not be {@literal null} or empty.
643645
* @param type must not be {@literal null}.
644646
*/
@@ -649,7 +651,7 @@ static class LikeParameterBinding extends ParameterBinding {
649651
/**
650652
* Creates a new {@link LikeParameterBinding} for the parameter with the given name and {@link Type} and parameter
651653
* binding input.
652-
*
654+
*
653655
* @param name must not be {@literal null} or empty.
654656
* @param type must not be {@literal null}.
655657
* @param expression may be {@literal null}.
@@ -713,20 +715,21 @@ public Type getType() {
713715
@Override
714716
public Object prepare(@Nullable Object value) {
715717

716-
if (value == null) {
718+
Object condensedValue = PersistenceProvider.condense(value);
719+
if (condensedValue == null) {
717720
return null;
718721
}
719722

720723
switch (type) {
721724
case STARTING_WITH:
722-
return String.format("%s%%", PersistenceProvider.condense(value));
725+
return String.format("%s%%", condensedValue);
723726
case ENDING_WITH:
724-
return String.format("%%%s", PersistenceProvider.condense(value));
727+
return String.format("%%%s", condensedValue);
725728
case CONTAINING:
726-
return String.format("%%%s%%", PersistenceProvider.condense(value));
729+
return String.format("%%%s%%", condensedValue);
727730
case LIKE:
728731
default:
729-
return PersistenceProvider.condense(value);
732+
return condensedValue;
730733
}
731734
}
732735

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

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

18-
import static org.assertj.core.api.Assertions.*;
18+
import static org.assertj.core.api.Assertions.assertThat;
1919

2020
import jakarta.persistence.EntityManagerFactory;
2121

@@ -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)