After Upgrading to spring boot 2.7.5 from 2.7.4, we can see that the NULL query parameters which involves "LIKE" clause are getting bound as empty strings and not as NULL. The same is bound as NULL, if the query condition is "=".
For example
If the query is as below and the p_user_name is passed as NULL, select user from User where :p_user_name is null or userName = :p_user_name
the parameter is properly getting bound as NULL -> binding parameter [1] as [VARCHAR] - [null] binding parameter [2] as [VARCHAR] - [null]
Whereas if the query is changed to like below select user from User where :p_user_name is null or userName LIKE :p_user_name
the parameter is incorrectly getting bound as EMPTY Strings-> binding parameter [1] as [VARCHAR] - [] binding parameter [2] as [VARCHAR] - []
This is failing the existing functionalities in the application. If I change the version back to 2.7.4, all works fine.
Comment From: wilkinsona
Thanks for the report. I think this is very unlikely to have been caused by Spring Boot itself. More likely is a change in behaviour in either Hibernate or Spring Data JPA (assuming that's what you're using). We upgraded to Hibernate 5.6.12.Final (from 5.6.11.Final) and Spring Data 2021.2.5 (from 2021.2.4) in 2.7.5. Please try downgrading each of these dependencies in turn using the hibernate.version and spring-data-bom.version properties respectively to narrow down the cause of the problem.
Comment From: yuvanakannan
Thanks for the suggestion. Changing the spring-data-bom.version or hibernate.version did not work. While trying to debug more, looks like the problem is triggered from spring-data-jpa.2.7.4 version. Spring boot 2.7.4 is using spring-data-jpa.2.7.3, where everything looks fine. Spring boot 2.7.5 and spring-data-bom 2021.2.4 uses spring-data-jpa.2.7.5 and spring-data-jpa.2.74 respectively. Both fails. There seems to be some changes to StringQuery$LikeParameterBinding class (prepare method). spring-data-jpa.2.7.3 is straight forward:
@Nullable
@Override
public Object prepare(@Nullable Object value) {
if (value == null) {
return null;
}
switch (type) {
case STARTING_WITH:
return String.format("%s%%", value);
case ENDING_WITH:
return String.format("%%%s", value);
case CONTAINING:
return String.format("%%%s%%", value);
case LIKE:
default:
return value;
}
}
from spring-data-jpa-2.7.4 it uses PersistenceProvider.condense method
@Nullable
@Override
public Object prepare(@Nullable Object value) {
if (value == null) {
return null;
}
switch (type) {
case STARTING_WITH:
return String.format("%s%%", PersistenceProvider.condense(value));
case ENDING_WITH:
return String.format("%%%s", PersistenceProvider.condense(value));
case CONTAINING:
return String.format("%%%s%%", PersistenceProvider.condense(value));
case LIKE:
default:
return PersistenceProvider.condense(value);
}
}
This Persistence Provider is returning empty string for some reason when the parameter value is NULL.
public static Object condense(Object value) {
ClassLoader classLoader = PersistenceProvider.class.getClassLoader();
if (ClassUtils.isPresent("org.hibernate.jpa.TypedParameterValue", classLoader)) {
try {
Class<?> typeParameterValue = ClassUtils.forName("org.hibernate.jpa.TypedParameterValue", classLoader);
if (typeParameterValue.isInstance(value)) {
return "";
}
} catch (ClassNotFoundException | LinkageError o_O) {
return value;
}
}
return value;
}
It might not have been a problem if it is just changing the parameter at the LIKE position, But as the same named parameter is used else where, the parameter value is bound as EMPTY string at all places which is creating the problem.
Comment From: wilkinsona
Thanks for narrowing down the cause. Please open a Spring Data JPA issue.