Polishing.

Use ObjectUtils instead of Enum.valueOf(…), move class presence check into field. Allow force-selection of JSQLParser.

Add more tests.

See #2989
Original pull request: #3623
This commit is contained in:
Mark Paluch
2024-10-01 15:22:40 +02:00
parent b1c349a1e0
commit 4e36b166ee
4 changed files with 88 additions and 43 deletions

View File

@@ -21,7 +21,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.lang.Nullable;
/**
* The implementation of {@link QueryEnhancer} using {@link QueryUtils}.
* The implementation of the Regex-based {@link QueryEnhancer} using {@link QueryUtils}.
*
* @author Diego Krupitza
* @since 2.7.0

View File

@@ -19,8 +19,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.SpringProperties;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -39,7 +39,7 @@ public final class QueryEnhancerFactory {
static {
NATIVE_QUERY_ENHANCER = NativeQueryEnhancer.select(QueryEnhancerFactory.class.getClassLoader());
NATIVE_QUERY_ENHANCER = NativeQueryEnhancer.select();
if (PersistenceProvider.ECLIPSELINK.isPresent()) {
LOG.info("EclipseLink is in classpath; If applicable, EQL parser will be used.");
@@ -81,47 +81,66 @@ public final class QueryEnhancerFactory {
*/
private static QueryEnhancer getNativeQueryEnhancer(DeclaredQuery query) {
if (NATIVE_QUERY_ENHANCER.equals(NativeQueryEnhancer.JSQL)) {
if (NATIVE_QUERY_ENHANCER.equals(NativeQueryEnhancer.JSQLPARSER)) {
return new JSqlParserQueryEnhancer(query);
}
return new DefaultQueryEnhancer(query);
}
/**
* Possible choices for the {@link #NATIVE_PARSER_PROPERTY}. Read current selection via {@link #select(ClassLoader)}.
* Possible choices for the {@link #NATIVE_PARSER_PROPERTY}. Resolve the parser through {@link #select()}.
*
* @since 3.3.5
*/
enum NativeQueryEnhancer {
AUTO, DEFAULT, JSQL;
AUTO, REGEX, JSQLPARSER;
static final String NATIVE_PARSER_PROPERTY = "spring.data.jpa.query.native.parser";
private static NativeQueryEnhancer from(@Nullable String name) {
if (!StringUtils.hasText(name)) {
return AUTO;
static final boolean JSQLPARSER_PRESENT = ClassUtils.isPresent("net.sf.jsqlparser.parser.JSqlParser", null);
/**
* @return the current selection considering classpath availability and user selection via
* {@link #NATIVE_PARSER_PROPERTY}.
*/
static NativeQueryEnhancer select() {
NativeQueryEnhancer selected = resolve();
if (selected.equals(NativeQueryEnhancer.JSQLPARSER)) {
LOG.info("User choice: Using JSqlParser");
return NativeQueryEnhancer.JSQLPARSER;
}
return NativeQueryEnhancer.valueOf(name.toUpperCase());
if (selected.equals(NativeQueryEnhancer.REGEX)) {
LOG.info("Using Regex QueryEnhancer");
return NativeQueryEnhancer.REGEX;
}
if (!JSQLPARSER_PRESENT) {
return NativeQueryEnhancer.REGEX;
}
LOG.info("JSqlParser is in classpath; If applicable, JSqlParser will be used.");
return NativeQueryEnhancer.JSQLPARSER;
}
/**
* @param classLoader ClassLoader to look up available libraries.
* @return the current selection considering classpath avialability and user selection via
* {@link #NATIVE_PARSER_PROPERTY}.
* Resolve {@link NativeQueryEnhancer} from {@link SpringProperties}.
*
* @return the {@link NativeQueryEnhancer} constant.
*/
static NativeQueryEnhancer select(ClassLoader classLoader) {
private static NativeQueryEnhancer resolve() {
if (!ClassUtils.isPresent("net.sf.jsqlparser.parser.JSqlParser", classLoader)) {
return NativeQueryEnhancer.DEFAULT;
String name = SpringProperties.getProperty(NATIVE_PARSER_PROPERTY);
if (StringUtils.hasText(name)) {
return ObjectUtils.caseInsensitiveValueOf(NativeQueryEnhancer.values(), name);
}
NativeQueryEnhancer selected = NativeQueryEnhancer.from(SpringProperties.getProperty(NATIVE_PARSER_PROPERTY));
if (selected.equals(NativeQueryEnhancer.AUTO) || selected.equals(NativeQueryEnhancer.JSQL)) {
LOG.info("JSqlParser is in classpath; If applicable, JSqlParser will be used.");
return NativeQueryEnhancer.JSQL;
}
LOG.info("JSqlParser is in classpath but won't be used due to user choice.");
return selected;
return AUTO;
}
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.jpa.repository.query;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import java.util.stream.Stream;
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.data.jpa.repository.query.QueryEnhancerFactory.NativeQueryEnhancer;
import org.springframework.data.jpa.util.ClassPathExclusions;
import org.springframework.lang.Nullable;
@@ -33,6 +34,7 @@ import org.springframework.lang.Nullable;
* @author Diego Krupitza
* @author Greg Turnquist
* @author Christoph Strobl
* @author Mark Paluch
*/
class QueryEnhancerFactoryUnitTests {
@@ -66,26 +68,48 @@ class QueryEnhancerFactoryUnitTests {
@MethodSource("nativeEnhancerSelectionArgs")
void createsNativeImplementationAccordingToUserChoice(@Nullable String selection, NativeQueryEnhancer enhancer) {
assertThat(NativeQueryEnhancer.JSQLPARSER_PRESENT).isTrue();
withSystemProperty(NativeQueryEnhancer.NATIVE_PARSER_PROPERTY, selection, () -> {
assertThat(NativeQueryEnhancer.select(this.getClass().getClassLoader())).isEqualTo(enhancer);
assertThat(NativeQueryEnhancer.select()).isEqualTo(enhancer);
});
}
static Stream<Arguments> nativeEnhancerSelectionArgs() {
return Stream.of(Arguments.of(null, NativeQueryEnhancer.JSQLPARSER), //
Arguments.of("", NativeQueryEnhancer.JSQLPARSER), //
Arguments.of("auto", NativeQueryEnhancer.JSQLPARSER), //
Arguments.of("regex", NativeQueryEnhancer.REGEX), //
Arguments.of("jsqlparser", NativeQueryEnhancer.JSQLPARSER));
}
@ParameterizedTest // GH-2989
@MethodSource("nativeEnhancerExclusionSelectionArgs")
@ClassPathExclusions(packages = { "net.sf.jsqlparser.parser" })
void createsNativeImplementationAccordingWithoutJsqlParserToUserChoice(@Nullable String selection,
NativeQueryEnhancer enhancer) {
assertThat(NativeQueryEnhancer.JSQLPARSER_PRESENT).isFalse();
withSystemProperty(NativeQueryEnhancer.NATIVE_PARSER_PROPERTY, selection, () -> {
assertThat(NativeQueryEnhancer.select()).isEqualTo(enhancer);
});
}
static Stream<Arguments> nativeEnhancerExclusionSelectionArgs() {
return Stream.of(Arguments.of(null, NativeQueryEnhancer.REGEX), //
Arguments.of("", NativeQueryEnhancer.REGEX), //
Arguments.of("auto", NativeQueryEnhancer.REGEX), //
Arguments.of("regex", NativeQueryEnhancer.REGEX), //
Arguments.of("jsqlparser", NativeQueryEnhancer.JSQLPARSER));
}
@Test // GH-2989
@ClassPathExclusions(packages = { "net.sf.jsqlparser.parser" })
void selectedDefaultImplementationIfJsqlNotAvailable() {
assertThat(assertThat(NativeQueryEnhancer.select(this.getClass().getClassLoader()))
.isEqualTo(NativeQueryEnhancer.DEFAULT));
}
@Test // GH-2989
@ClassPathExclusions(packages = { "net.sf.jsqlparser.parser" })
void selectedDefaultImplementationIfJsqlNotAvailableEvenIfExplicitlyStated/* or should we raise an error? */() {
withSystemProperty(NativeQueryEnhancer.NATIVE_PARSER_PROPERTY, "jsql", () -> {
assertThat(NativeQueryEnhancer.select(this.getClass().getClassLoader())).isEqualTo(NativeQueryEnhancer.DEFAULT);
});
assertThat(NativeQueryEnhancer.JSQLPARSER_PRESENT).isFalse();
assertThat(NativeQueryEnhancer.select()).isEqualTo(NativeQueryEnhancer.REGEX);
}
void withSystemProperty(String property, @Nullable String value, Runnable exeution) {
@@ -108,9 +132,5 @@ class QueryEnhancerFactoryUnitTests {
}
static Stream<Arguments> nativeEnhancerSelectionArgs() {
return Stream.of(Arguments.of(null, NativeQueryEnhancer.JSQL), Arguments.of("", NativeQueryEnhancer.JSQL),
Arguments.of("auto", NativeQueryEnhancer.JSQL), Arguments.of("default", NativeQueryEnhancer.DEFAULT),
Arguments.of("jsql", NativeQueryEnhancer.JSQL));
}
}

View File

@@ -307,7 +307,13 @@ public interface UserRepository extends JpaRepository<User, Long> {
[TIP]
====
It is possible to disable usage of `JSqlParser` for parsing natvie queries although it is available in classpath by setting `spring.data.jpa.query.native.parser=default` via the `spring.properties` file or a system property.
It is possible to disable usage of `JSqlParser` for parsing native queries although it is available on the classpath by setting `spring.data.jpa.query.native.parser=regex` via the `spring.properties` file or a system property.
Valid values are (case-insensitive):
* `auto` (default, automatic selection)
* `regex` (Use the builtin regex-based Query Enhancer)
* `jsqlparser` (Use JSqlParser)
====
A similar approach also works with named native queries, by adding the `.count` suffix to a copy of your query. You probably need to register a result set mapping for your count query, though.