Skip to content

Commit 852ff45

Browse files
committed
Unwrapping of TypedParameterValue, return null instead of empty string for string params.
Fixes spring-projects#2653 Related tickets spring-projects#2548
1 parent 86eb4aa commit 852ff45

File tree

4 files changed

+54
-19
lines changed

4 files changed

+54
-19
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
* @author Thomas Darimont
5151
* @author Mark Paluch
5252
* @author Jens Schauder
53+
* @author Yuriy Tsarkov
5354
*/
5455
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor {
5556

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

351352
if (typeParameterValue.isInstance(value)) {
352-
return "";
353+
return null;
353354
}
354355
} catch (ClassNotFoundException | LinkageError o_O) {
355356
return value;

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

+8-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

@@ -181,6 +182,7 @@ EscapeCharacter getEscape() {
181182
* @author Oliver Gierke
182183
* @author Thomas Darimont
183184
* @author Andrey Kovalev
185+
* @author Yuriy Tsarkov
184186
* @param <T>
185187
*/
186188
static class ParameterMetadata<T> {
@@ -227,23 +229,22 @@ public boolean isIsNullParameter() {
227229
*/
228230
@Nullable
229231
public Object prepare(Object value) {
230-
231-
Assert.notNull(value, "Value must not be null!");
232+
Object unwrappedValue = PersistenceProvider.condense(value);
233+
Assert.notNull(unwrappedValue, "Value must not be null!");
232234

233235
Class<? extends T> expressionType = expression.getJavaType();
234236

235237
if (String.class.equals(expressionType)) {
236-
237238
switch (type) {
238239
case STARTING_WITH:
239-
return String.format("%s%%", escape.escape(PersistenceProvider.condense(value).toString()));
240+
return String.format("%s%%", escape.escape(unwrappedValue.toString()));
240241
case ENDING_WITH:
241-
return String.format("%%%s", escape.escape(PersistenceProvider.condense(value).toString()));
242+
return String.format("%%%s", escape.escape(unwrappedValue.toString()));
242243
case CONTAINING:
243244
case NOT_CONTAINING:
244-
return String.format("%%%s%%", escape.escape(PersistenceProvider.condense(value).toString()));
245+
return String.format("%%%s%%", escape.escape(unwrappedValue.toString()));
245246
default:
246-
return PersistenceProvider.condense(value);
247+
return unwrappedValue;
247248
}
248249
}
249250

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

+8-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* @author Jens Schauder
5050
* @author Diego Krupitza
5151
* @author Greg Turnquist
52+
* @author Yuriy Tsarkov
5253
*/
5354
class StringQuery implements DeclaredQuery {
5455

@@ -680,6 +681,7 @@ public Object prepare(@Nullable Object value) {
680681
*
681682
* @author Oliver Gierke
682683
* @author Thomas Darimont
684+
* @author Yuriy Tsarkov
683685
*/
684686
static class LikeParameterBinding extends ParameterBinding {
685687

@@ -764,21 +766,21 @@ public Type getType() {
764766
@Nullable
765767
@Override
766768
public Object prepare(@Nullable Object value) {
767-
768-
if (value == null) {
769+
Object unwrappedValue = PersistenceProvider.condense(value);
770+
if (unwrappedValue == null) {
769771
return null;
770772
}
771773

772774
switch (type) {
773775
case STARTING_WITH:
774-
return String.format("%s%%", PersistenceProvider.condense(value));
776+
return String.format("%s%%", unwrappedValue);
775777
case ENDING_WITH:
776-
return String.format("%%%s", PersistenceProvider.condense(value));
778+
return String.format("%%%s", unwrappedValue);
777779
case CONTAINING:
778-
return String.format("%%%s%%", PersistenceProvider.condense(value));
780+
return String.format("%%%s%%", unwrappedValue);
779781
case LIKE:
780782
default:
781-
return PersistenceProvider.condense(value);
783+
return unwrappedValue;
782784
}
783785
}
784786

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

+36-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jpa.repository.query;
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1920

2021
import java.util.Arrays;
2122
import java.util.List;
@@ -24,6 +25,7 @@
2425
import javax.persistence.EntityManagerFactory;
2526
import javax.sql.DataSource;
2627

28+
import org.junit.Ignore;
2729
import org.junit.jupiter.api.BeforeEach;
2830
import org.junit.jupiter.api.Test;
2931
import org.junit.jupiter.api.extension.ExtendWith;
@@ -52,13 +54,14 @@
5254
* Verify that {@literal LIKE}s mixed with {@literal NULL}s work properly.
5355
*
5456
* @author Greg Turnquist
57+
* @author Yuriy Tsarkov
5558
*/
5659
@ExtendWith(SpringExtension.class)
5760
@ContextConfiguration(classes = QueryWithNullLikeHibernateIntegrationTests.Config.class)
5861
@Transactional
5962
public class QueryWithNullLikeHibernateIntegrationTests {
6063

61-
@Autowired EmpoyeeWithNullLikeRepository repository;
64+
@Autowired EmployeeWithNullLikeRepository repository;
6265

6366
@BeforeEach
6467
void setUp() {
@@ -98,8 +101,7 @@ void customQueryWithNullMatch() {
98101

99102
List<EmployeeWithName> Employees = repository.customQueryWithNullableParam(null);
100103

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

105107
@Test
@@ -128,6 +130,8 @@ void derivedQueryStartsWithWithEmptyStringMatch() {
128130
}
129131

130132
@Test
133+
@Ignore
134+
@Deprecated
131135
void derivedQueryStartsWithWithNullMatch() {
132136

133137
List<EmployeeWithName> Employees = repository.findByNameStartsWith(null);
@@ -136,6 +140,13 @@ void derivedQueryStartsWithWithNullMatch() {
136140
"Bilbo Baggins");
137141
}
138142

143+
@Test // GH-2653
144+
void derivedQueryStartsWithWithNullFail() {
145+
assertThatThrownBy(() -> repository.findByNameStartsWith(null))
146+
.hasCauseExactlyInstanceOf(IllegalArgumentException.class)
147+
.hasMessageStartingWith("Value must not be null!");
148+
}
149+
139150
@Test
140151
void derivedQueryEndsWithWithMultipleMatch() {
141152

@@ -163,6 +174,8 @@ void derivedQueryEndsWithWithEmptyStringMatch() {
163174
}
164175

165176
@Test
177+
@Ignore
178+
@Deprecated
166179
void derivedQueryEndsWithWithNullMatch() {
167180

168181
List<EmployeeWithName> Employees = repository.findByNameEndsWith(null);
@@ -171,6 +184,14 @@ void derivedQueryEndsWithWithNullMatch() {
171184
"Bilbo Baggins");
172185
}
173186

187+
@Test // GH-2653
188+
void derivedQueryEndsWithWithNullFail() {
189+
190+
assertThatThrownBy(() -> repository.findByNameStartsWith(null))
191+
.hasCauseExactlyInstanceOf(IllegalArgumentException.class)
192+
.hasMessageStartingWith("Value must not be null!");
193+
}
194+
174195
@Test
175196
void derivedQueryContainsWithMultipleMatch() {
176197

@@ -198,6 +219,8 @@ void derivedQueryContainsWithEmptyStringMatch() {
198219
}
199220

200221
@Test
222+
@Ignore
223+
@Deprecated
201224
void derivedQueryContainsWithNullMatch() {
202225

203226
List<EmployeeWithName> Employees = repository.findByNameContains(null);
@@ -206,6 +229,14 @@ void derivedQueryContainsWithNullMatch() {
206229
"Bilbo Baggins");
207230
}
208231

232+
@Test // GH-2653
233+
void derivedQueryContainsWithNullFail() {
234+
235+
assertThatThrownBy(() -> repository.findByNameStartsWith(null))
236+
.hasCauseExactlyInstanceOf(IllegalArgumentException.class)
237+
.hasMessageStartingWith("Value must not be null!");
238+
}
239+
209240
@Test
210241
void derivedQueryLikeWithMultipleMatch() {
211242

@@ -233,7 +264,7 @@ void derivedQueryLikeWithEmptyStringMatch() {
233264
}
234265

235266
@Transactional
236-
public interface EmpoyeeWithNullLikeRepository extends JpaRepository<EmployeeWithName, Integer> {
267+
public interface EmployeeWithNullLikeRepository extends JpaRepository<EmployeeWithName, Integer> {
237268

238269
@Query("select e from EmployeeWithName e where e.name like %:partialName%")
239270
List<EmployeeWithName> customQueryWithNullableParam(@Nullable @Param("partialName") String partialName);
@@ -248,7 +279,7 @@ public interface EmpoyeeWithNullLikeRepository extends JpaRepository<EmployeeWit
248279
}
249280

250281
@EnableJpaRepositories(considerNestedRepositories = true, //
251-
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = EmpoyeeWithNullLikeRepository.class))
282+
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = EmployeeWithNullLikeRepository.class))
252283
@EnableTransactionManagement
253284
static class Config {
254285

0 commit comments

Comments
 (0)