Browse Source

Merge branch 'spring-projects:main' into 5086

pull/5152/head
Choi Tae Hyun 2 weeks ago committed by GitHub
parent
commit
40e6563515
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 66
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java
  2. 103
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java
  3. 22
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java
  4. 4
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java
  5. 22
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  6. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  7. 3
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/DocumentSerializerUnitTests.java
  8. 83
      src/main/antora/modules/ROOT/pages/preface.adoc

66
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotPlaceholders.java

@ -53,7 +53,7 @@ class AotPlaceholders { @@ -53,7 +53,7 @@ class AotPlaceholders {
* @param type
* @return
*/
static Shape geoJson(int index, String type) {
static Placeholder geoJson(int index, String type) {
return new GeoJsonPlaceholder(index, type);
}
@ -63,7 +63,7 @@ class AotPlaceholders { @@ -63,7 +63,7 @@ class AotPlaceholders {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
static Point point(int index) {
static Placeholder point(int index) {
return new PointPlaceholder(index);
}
@ -73,7 +73,7 @@ class AotPlaceholders { @@ -73,7 +73,7 @@ class AotPlaceholders {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
static Shape circle(int index) {
static Placeholder circle(int index) {
return new CirclePlaceholder(index);
}
@ -83,7 +83,7 @@ class AotPlaceholders { @@ -83,7 +83,7 @@ class AotPlaceholders {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
static Shape box(int index) {
static Placeholder box(int index) {
return new BoxPlaceholder(index);
}
@ -93,7 +93,7 @@ class AotPlaceholders { @@ -93,7 +93,7 @@ class AotPlaceholders {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
static Shape sphere(int index) {
static Placeholder sphere(int index) {
return new SpherePlaceholder(index);
}
@ -103,7 +103,7 @@ class AotPlaceholders { @@ -103,7 +103,7 @@ class AotPlaceholders {
* @param index zero-based index referring to the bindable method parameter.
* @return
*/
static Shape polygon(int index) {
static Placeholder polygon(int index) {
return new PolygonPlaceholder(index);
}
@ -111,6 +111,26 @@ class AotPlaceholders { @@ -111,6 +111,26 @@ class AotPlaceholders {
return new RegexPlaceholder(index, options);
}
/**
* Create a placeholder that indicates the value should be treated as list.
*
* @param index zero-based index referring to the bindable method parameter.
* @return new instance of {@link Placeholder}.
*/
static Placeholder asList(int index) {
return asList(indexed(index));
}
/**
* Create a placeholder that indicates the wrapped placeholder should be treated as list.
*
* @param source the target placeholder
* @return new instance of {@link Placeholder}.
*/
static Placeholder asList(Placeholder source) {
return new AsListPlaceholder(source);
}
/**
* A placeholder expression used when rending queries to JSON.
*
@ -120,6 +140,17 @@ class AotPlaceholders { @@ -120,6 +140,17 @@ class AotPlaceholders {
interface Placeholder {
String getValue();
/**
* Unwrap the current {@link Placeholder} to the given target type if possible.
*
* @param targetType
* @return
* @param <T>
*/
default <T extends Placeholder> @Nullable T unwrap(Class<? extends T> targetType) {
return targetType.isInstance(this) ? targetType.cast(this) : null;
}
}
/**
@ -295,4 +326,27 @@ class AotPlaceholders { @@ -295,4 +326,27 @@ class AotPlaceholders {
}
}
record AsListPlaceholder(Placeholder placeholder) implements Placeholder {
@Override
public @Nullable <T extends Placeholder> T unwrap(Class<? extends T> targetType) {
if (targetType.isInstance(placeholder)) {
return targetType.cast(placeholder);
}
return Placeholder.super.unwrap(targetType);
}
@Override
public String toString() {
return getValue();
}
@Override
public String getValue() {
return "[" + placeholder.getValue() + "]";
}
}
}

103
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotQueryCreator.java

@ -48,6 +48,7 @@ import org.springframework.data.mongodb.core.query.Query; @@ -48,6 +48,7 @@ import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
import org.springframework.data.mongodb.repository.VectorSearch;
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.AsListPlaceholder;
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.Placeholder;
import org.springframework.data.mongodb.repository.aot.AotPlaceholders.RegexPlaceholder;
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor;
@ -61,7 +62,6 @@ import org.springframework.data.repository.query.parser.Part; @@ -61,7 +62,6 @@ import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.Part.IgnoreCaseType;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.ClassUtils;
import com.mongodb.DBRef;
@ -132,6 +132,10 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext @@ -132,6 +132,10 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext
return criteria.raw("$regex", param);
}
if (param instanceof AsListPlaceholder asList && !property.isCollectionLike()) {
return super.createContainingCriteria(part, property, criteria, asList.placeholder());
}
return super.createContainingCriteria(part, property, criteria, param);
}
}
@ -172,10 +176,11 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext @@ -172,10 +176,11 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext
@NullUnmarked
static class PlaceholderParameterAccessor implements MongoParameterAccessor {
private final List<Object> placeholders;
private final List<Placeholder> placeholders;
@Nullable
Part getPartForIndex(PartTree partTree, Parameter parameter) {
if (!parameter.isBindable()) {
return null;
}
@ -193,45 +198,74 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext @@ -193,45 +198,74 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext
public PlaceholderParameterAccessor(PartTree partTree, QueryMethod queryMethod) {
if (queryMethod.getParameters().getNumberOfParameters() == 0) {
Parameters<?, ?> parameters = queryMethod.getParameters();
if (parameters.getNumberOfParameters() == 0) {
placeholders = List.of();
} else {
placeholders = new ArrayList<>();
Parameters<?, ?> parameters = queryMethod.getParameters();
placeholders = new ArrayList<>(parameters.getNumberOfParameters());
for (Parameter parameter : parameters.toList()) {
if (ClassUtils.isAssignable(GeoJson.class, parameter.getType())) {
placeholders.add(parameter.getIndex(), AotPlaceholders.geoJson(parameter.getIndex(), ""));
} else if (ClassUtils.isAssignable(Point.class, parameter.getType())) {
placeholders.add(parameter.getIndex(), AotPlaceholders.point(parameter.getIndex()));
} else if (ClassUtils.isAssignable(Circle.class, parameter.getType())) {
placeholders.add(parameter.getIndex(), AotPlaceholders.circle(parameter.getIndex()));
} else if (ClassUtils.isAssignable(Box.class, parameter.getType())) {
placeholders.add(parameter.getIndex(), AotPlaceholders.box(parameter.getIndex()));
} else if (ClassUtils.isAssignable(Sphere.class, parameter.getType())) {
placeholders.add(parameter.getIndex(), AotPlaceholders.sphere(parameter.getIndex()));
} else if (ClassUtils.isAssignable(Polygon.class, parameter.getType())) {
placeholders.add(parameter.getIndex(), AotPlaceholders.polygon(parameter.getIndex()));
} else if (ClassUtils.isAssignable(Pattern.class, parameter.getType())) {
placeholders.add(parameter.getIndex(), AotPlaceholders.regex(parameter.getIndex(), null));
} else {
Part partForIndex = getPartForIndex(partTree, parameter);
if (partForIndex != null
&& (partForIndex.getType().equals(Type.LIKE) || partForIndex.getType().equals(Type.NOT_LIKE))) {
placeholders
.add(parameter.getIndex(),
AotPlaceholders
.regex(parameter.getIndex(),
partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.ALWAYS)
|| partForIndex.shouldIgnoreCase().equals(IgnoreCaseType.WHEN_POSSIBLE) ? "i"
: null));
int index = parameter.getIndex();
placeholders.add(index, getPlaceholder(index, parameter, partTree));
}
}
}
private Placeholder getPlaceholder(int index, Parameter parameter, PartTree partTree) {
Class<?> type = parameter.getType();
if (GeoJson.class.isAssignableFrom(type)) {
return AotPlaceholders.geoJson(index, "");
} else if (Point.class.isAssignableFrom(type)) {
return AotPlaceholders.point(index);
} else if (Circle.class.isAssignableFrom(type)) {
return AotPlaceholders.circle(index);
} else if (Box.class.isAssignableFrom(type)) {
return AotPlaceholders.box(index);
} else if (Sphere.class.isAssignableFrom(type)) {
return AotPlaceholders.sphere(index);
} else if (Polygon.class.isAssignableFrom(type)) {
return AotPlaceholders.polygon(index);
} else if (Pattern.class.isAssignableFrom(type)) {
return AotPlaceholders.regex(index, null);
}
Part partForIndex = getPartForIndex(partTree, parameter);
if (partForIndex != null) {
IgnoreCaseType ignoreCaseType = partForIndex.shouldIgnoreCase();
if (isLike(partForIndex.getType())) {
boolean ignoreCase = !ignoreCaseType.equals(IgnoreCaseType.NEVER);
return AotPlaceholders.regex(index, ignoreCase ? "i" : null);
}
if (isContaining(partForIndex.getType())) {
if (partForIndex.getProperty().isCollection() && !TypeInformation.of(type).isCollectionLike()) {
if (ignoreCaseType.equals(IgnoreCaseType.ALWAYS)) {
return AotPlaceholders.asList(AotPlaceholders.regex(index, "i"));
} else {
placeholders.add(parameter.getIndex(), AotPlaceholders.indexed(parameter.getIndex()));
return AotPlaceholders.asList(index);
}
}
return AotPlaceholders.indexed(index);
}
}
return AotPlaceholders.indexed(index);
}
private static boolean isContaining(Part.Type type) {
return type.equals(Type.IN) || type.equals(Type.NOT_IN) //
|| type.equals(Type.CONTAINING) || type.equals(Type.NOT_CONTAINING);
}
private static boolean isLike(Part.Type type) {
return type.equals(Type.LIKE) || type.equals(Type.NOT_LIKE);
}
@Override
@ -301,8 +335,7 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext @@ -301,8 +335,7 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext
@Override
public @Nullable Object getBindableValue(int index) {
return placeholders.get(index) instanceof Placeholder placeholder ? placeholder.getValue()
: placeholders.get(index);
return placeholders.get(index).getValue();
}
@Override
@ -316,7 +349,7 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext @@ -316,7 +349,7 @@ record AotQueryCreator(MappingContext<?, MongoPersistentProperty> mappingContext
return ((List) placeholders).iterator();
}
public List<Object> getPlaceholders() {
public List<Placeholder> getPlaceholders() {
return placeholders;
}
}

22
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/AotStringQuery.java

@ -47,9 +47,9 @@ class AotStringQuery extends Query { @@ -47,9 +47,9 @@ class AotStringQuery extends Query {
private @Nullable String sort;
private @Nullable String fields;
private List<Object> placeholders = new ArrayList<>();
private List<AotPlaceholders.Placeholder> placeholders = new ArrayList<>();
public AotStringQuery(Query query, List<Object> placeholders) {
public AotStringQuery(Query query, List<AotPlaceholders.Placeholder> placeholders) {
this.delegate = query;
this.placeholders = placeholders;
}
@ -79,20 +79,24 @@ class AotStringQuery extends Query { @@ -79,20 +79,24 @@ class AotStringQuery extends Query {
}
boolean isRegexPlaceholderAt(int index) {
if (this.placeholders.isEmpty()) {
return false;
}
return this.placeholders.get(index) instanceof RegexPlaceholder;
return getRegexPlaceholder(index) != null;
}
@Nullable
String getRegexOptions(int index) {
if (this.placeholders.isEmpty()) {
RegexPlaceholder placeholder = getRegexPlaceholder(index);
return placeholder != null ? placeholder.regexOptions() : null;
}
@Nullable
RegexPlaceholder getRegexPlaceholder(int index) {
if (index >= this.placeholders.size()) {
return null;
}
return this.placeholders.get(index) instanceof RegexPlaceholder rgp ? rgp.regexOptions() : null;
return this.placeholders.get(index).unwrap(RegexPlaceholder.class);
}
@Override

4
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java

@ -195,9 +195,9 @@ class QueryBlocks { @@ -195,9 +195,9 @@ class QueryBlocks {
String regexOptions = source.getQuery().getRegexOptions(i);
if (StringUtils.hasText(regexOptions)) {
formatted.add(CodeBlock.of("toRegex($L)", parameterName));
} else {
formatted.add(CodeBlock.of("toRegex($L, $S)", parameterName, regexOptions));
} else {
formatted.add(CodeBlock.of("toRegex($L)", parameterName));
}
} else {
formatted.add(CodeBlock.of("$L", parameterName));

22
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -1341,6 +1341,20 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie @@ -1341,6 +1341,20 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
assertThat(result).hasSize(1).contains(carter);
}
@Test // GH-5123
void findBySkillsContainsSingleElement() {
List<Person> result = repository.findBySkillsContains("Drums");
assertThat(result).hasSize(1).contains(carter);
}
@Test // GH-5123
void findBySkillsContainsSingleElementWithIgnoreCase() {
List<Person> result = repository.findBySkillsContainsIgnoreCase("drums");
assertThat(result).hasSize(1).contains(carter);
}
@Test // DATAMONGO-1425
void findBySkillsNotContains() {
@ -1349,6 +1363,14 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie @@ -1349,6 +1363,14 @@ public abstract class AbstractPersonRepositoryIntegrationTests implements Dirtie
assertThat(result).doesNotContain(carter);
}
@Test // GH-5123
void findBySkillsNotContainsSingleElement() {
List<Person> result = repository.findBySkillsNotContains("Drums");
assertThat(result).hasSize((int) (repository.count() - 1));
assertThat(result).doesNotContain(carter);
}
@Test // DATAMONGO-1424
void findsPersonsByFirstnameNotLike() {

3
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

@ -117,8 +117,11 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query @@ -117,8 +117,11 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
List<Person> findByFirstnameLikeOrderByLastnameAsc(Pattern firstname, Sort sort);
List<Person> findBySkillsContains(List<String> skills);
List<Person> findBySkillsContains(String skill);
List<Person> findBySkillsContainsIgnoreCase(String skill);
List<Person> findBySkillsNotContains(List<String> skills);
List<Person> findBySkillsNotContains(String skill);
@Query("{'age' : { '$lt' : ?0 } }")
List<Person> findByAgeLessThan(int age, Sort sort);

3
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/DocumentSerializerUnitTests.java

@ -22,6 +22,7 @@ import java.util.List; @@ -22,6 +22,7 @@ import java.util.List;
import org.bson.Document;
import org.junit.jupiter.api.Test;
import org.springframework.data.geo.Shape;
import org.springframework.data.mongodb.core.query.GeoCommand;
/**
@ -35,7 +36,7 @@ class DocumentSerializerUnitTests { @@ -35,7 +36,7 @@ class DocumentSerializerUnitTests {
void writesGeoCommandToJson() {
Document source = new Document();
source.append("foo", new GeoCommand(AotPlaceholders.box(1)));
source.append("foo", new GeoCommand((Shape) AotPlaceholders.box(1)));
String jsonString = DocumentSerializer.toJson(source);

83
src/main/antora/modules/ROOT/pages/preface.adoc

@ -1,16 +1,20 @@ @@ -1,16 +1,20 @@
[[requirements]]
= Requirements
The Spring Data MongoDB 4.x binaries require JDK level 17 and above and https://spring.io/docs[Spring Framework] {springVersion} and above.
In terms of database and driver, you need at least version 4.x of https://www.mongodb.org/[MongoDB] and a compatible MongoDB Java Driver (5.6+).
The Spring Data MongoDB 5.x binaries require JDK level 17 and above and https://spring.io/docs[Spring Framework] {springVersion} and above.
[[compatibility.matrix]]
== Compatibility Matrix
The following compatibility matrix summarizes Spring Data versions to MongoDB driver/database versions.
Database versions show server generations that pass the Spring Data test suite.
You can use newer server versions unless your application uses functionality that is affected by xref:preface.adoc#compatibility.changes[changes in the MongoDB server].
[TIP]
====
Please visit https://spring.io/projects/spring-data-mongodb#support[OSS Support] for detailed Spring Data support timelines. +
For the MongoDB Server Support Policy please refer to the https://www.mongodb.com/legal/support-policy/lifecycles[MongoDB Software Lifecycle Schedule].
====
The following compatibility matrix summarizes Spring Data versions and their required minimum MongoDB client version.
Database versions show server generations that pass the Spring Data test suite, older server versions might have difficulties dealing with new/changed commands.
You may use newer server versions unless your application uses functionality that is affected by xref:preface.adoc#compatibility.changes[changes in the MongoDB server].
See also the https://www.mongodb.com/docs/drivers/java/sync/current/compatibility/[official MongoDB driver compatibility matrix] for driver- and server version compatibility.
====
@ -19,12 +23,22 @@ See also the https://www.mongodb.com/docs/drivers/java/sync/current/compatibilit @@ -19,12 +23,22 @@ See also the https://www.mongodb.com/docs/drivers/java/sync/current/compatibilit
|Spring Data Release Train
|Spring Data MongoDB
|Driver Version
|Database Versions
|Minimum Driver Version
|Tested Database Versions
|2026.0
|5.1.x
|5.6.x
|6.x to 8.x
|2025.1
|5.0.x
|5.6.x
|6.x to 8.x
|2025.0
|4.5.x
|5.6.x
|5.5.x
|6.x to 8.x
|2024.1
@ -37,58 +51,7 @@ See also the https://www.mongodb.com/docs/drivers/java/sync/current/compatibilit @@ -37,58 +51,7 @@ See also the https://www.mongodb.com/docs/drivers/java/sync/current/compatibilit
|4.11.x & 5.x
|4.4.x to 7.x
|2023.1
|4.2.x
|4.9.x
|4.4.x to 7.x
|2023.0 (*)
|4.1.x
|4.9.x
|4.4.x to 6.x
|2022.0 (*)
|4.0.x
|4.7.x
|4.4.x to 6.x
|2021.2 (*)
|3.4.x
|4.6.x
|4.4.x to 5.0.x
|2021.1 (*)
|3.3.x
|4.4.x
|4.4.x to 5.0.x
|2021.0 (*)
|3.2.x
|4.1.x
|4.4.x
|2020.0 (*)
|3.1.x
|4.1.x
|4.4.x
|Neumann (*)
|3.0.x
|4.0.x
|4.4.x
|Moore (*)
|2.2.x
|3.11.x/Reactive Streams 1.12.x
|4.2.x
|Lovelace (*)
|2.1.x
|3.8.x/Reactive Streams 1.9.x
|4.0.x
|===
(*) https://spring.io/projects/spring-data-mongodb#support[End of OSS Support]
====
[[compatibility.changes]]

Loading…
Cancel
Save