Browse Source

Polishing.

Add Javadoc and since tags. Refine value lookup. Include missing properties in Meta.hashCode and equals. Update documentation using boolean Meta.allowDiskUse. Add assertions to prevent accidental null propagation.

See: #4667
Original pull request: #5035
pull/5044/head
Mark Paluch 4 months ago
parent
commit
41fd839bfb
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java
  2. 2
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java
  3. 7
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java
  4. 18
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/DiskUse.java
  5. 35
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java
  6. 17
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java
  7. 19
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java
  8. 2
      src/main/antora/modules/ROOT/pages/mongodb/aot.adoc
  9. 4
      src/main/antora/modules/ROOT/pages/mongodb/repositories/query-methods.adoc

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/MongoTemplate.java

@ -3558,7 +3558,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Meta meta = query.getMeta(); Meta meta = query.getMeta();
HintFunction hintFunction = HintFunction.from(query.getHint()); HintFunction hintFunction = HintFunction.from(query.getHint());
if (skip <= 0 && limit <= 0 && ObjectUtils.isEmpty(sortObject) && hintFunction.isEmpty() && !meta.hasValues() if (skip <= 0 && limit <= 0 && ObjectUtils.isEmpty(sortObject) && hintFunction.isEmpty() && meta.isEmpty()
&& query.getCollation().isEmpty()) { && query.getCollation().isEmpty()) {
return cursorToUse; return cursorToUse;
} }

2
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/ReactiveMongoTemplate.java

@ -3419,7 +3419,7 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
HintFunction hintFunction = HintFunction.from(query.getHint()); HintFunction hintFunction = HintFunction.from(query.getHint());
Meta meta = query.getMeta(); Meta meta = query.getMeta();
if (skip <= 0 && limit <= 0 && ObjectUtils.isEmpty(sortObject) && hintFunction.isEmpty() && !meta.hasValues()) { if (skip <= 0 && limit <= 0 && ObjectUtils.isEmpty(sortObject) && hintFunction.isEmpty() && meta.isEmpty()) {
return findPublisherToUse; return findPublisherToUse;
} }

7
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/AggregationOptions.java

@ -146,6 +146,8 @@ public class AggregationOptions implements ReadConcernAware, ReadPreferenceAware
private AggregationOptions(DiskUse diskUse, boolean explain, @Nullable Document cursor, private AggregationOptions(DiskUse diskUse, boolean explain, @Nullable Document cursor,
@Nullable Collation collation, @Nullable String comment, @Nullable Object hint) { @Nullable Collation collation, @Nullable String comment, @Nullable Object hint) {
Assert.notNull(diskUse, "DiskUse must not be null");
this.diskUse = diskUse; this.diskUse = diskUse;
this.explain = explain; this.explain = explain;
this.cursor = Optional.ofNullable(cursor); this.cursor = Optional.ofNullable(cursor);
@ -442,7 +444,7 @@ public class AggregationOptions implements ReadConcernAware, ReadPreferenceAware
*/ */
public static class Builder { public static class Builder {
private @Nullable DiskUse diskUse = DiskUse.DEFAULT; private DiskUse diskUse = DiskUse.DEFAULT;
private boolean explain; private boolean explain;
private @Nullable Document cursor; private @Nullable Document cursor;
private @Nullable Collation collation; private @Nullable Collation collation;
@ -470,10 +472,13 @@ public class AggregationOptions implements ReadConcernAware, ReadPreferenceAware
* *
* @param diskUse use {@literal true} to allow disk use during the aggregation. * @param diskUse use {@literal true} to allow disk use during the aggregation.
* @return this. * @return this.
* @since 5.0
*/ */
@Contract("_ -> this") @Contract("_ -> this")
public Builder diskUse(DiskUse diskUse) { public Builder diskUse(DiskUse diskUse) {
Assert.notNull(diskUse, "DiskUse must not be null");
this.diskUse = diskUse; this.diskUse = diskUse;
return this; return this;
} }

18
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/DiskUse.java

@ -15,31 +15,35 @@
*/ */
package org.springframework.data.mongodb.core.query; package org.springframework.data.mongodb.core.query;
import java.util.Locale;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
* Disk use indicates if the MongoDB server is allowed to write temporary files to disk during query/aggregation * Disk use indicates if the MongoDB server is allowed to write temporary files to disk during query/aggregation
* execution. MongoDB 6.0 server (and later) default for {@literal allowDiskUseByDefault} is {@literal true} on server * execution. MongoDB 6.0 server (and later) default for {@literal allowDiskUseByDefault} is {@literal true} on the
* side. * server side.
* *
* @author Christoph Strobl * @author Christoph Strobl
* @since 5.0 * @since 5.0
* @see com.mongodb.client.FindIterable#allowDiskUse(Boolean)
* @see com.mongodb.reactivestreams.client.FindPublisher#allowDiskUse(Boolean)
*/ */
public enum DiskUse { public enum DiskUse {
/** /**
* Go with the server default value and do not specify any override. * Use the server default value and do not specify any override.
*/ */
DEFAULT, DEFAULT,
/** /**
* Override server default value and explicitly allow disk writes. * Allow disk writes.
*/ */
ALLOW, ALLOW,
/** /**
* Override server default value and explicitly deny disk writes. * Explicitly deny disk writes.
*/ */
DENY; DENY;
@ -69,10 +73,10 @@ public enum DiskUse {
return DEFAULT; return DEFAULT;
} }
return switch (value) { return switch (value.toLowerCase(Locale.ROOT)) {
case "true" -> ALLOW; case "true" -> ALLOW;
case "false" -> DENY; case "false" -> DENY;
default -> valueOf(value.toUpperCase()); default -> valueOf(value.toUpperCase(Locale.ROOT));
}; };
} }
} }

35
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Meta.java

@ -247,15 +247,33 @@ public class Meta {
setDiskUse(DiskUse.of(allowDiskUse)); setDiskUse(DiskUse.of(allowDiskUse));
} }
/**
* Sets the {@link DiskUse} to control whether temporary files are allowed to be written to disk during query.
*
* @param diskUse must not be {@literal null}.
* @since 5.0
*/
public void setDiskUse(DiskUse diskUse) { public void setDiskUse(DiskUse diskUse) {
Assert.notNull(diskUse, "DiskUse must not be null");
this.diskUse = diskUse; this.diskUse = diskUse;
} }
/** /**
* @return * @return {@literal true} there is at least one values, flags, cursor batch size, or disk use set; {@literal false}
* otherwise.
*/ */
public boolean hasValues() { public boolean hasValues() {
return !this.values.isEmpty() || !this.flags.isEmpty() || this.cursorBatchSize != null || !this.diskUse.equals(DiskUse.DEFAULT); return !isEmpty();
}
/**
* @return {@literal true} if no values, flags, cursor batch size and disk use are set; {@literal false} otherwise.
*/
public boolean isEmpty() {
return this.values.isEmpty() && this.flags.isEmpty() && this.cursorBatchSize == null
&& this.diskUse.equals(DiskUse.DEFAULT);
} }
/** /**
@ -303,6 +321,8 @@ public class Meta {
int hash = ObjectUtils.nullSafeHashCode(this.values); int hash = ObjectUtils.nullSafeHashCode(this.values);
hash += ObjectUtils.nullSafeHashCode(this.flags); hash += ObjectUtils.nullSafeHashCode(this.flags);
hash += ObjectUtils.nullSafeHashCode(this.cursorBatchSize);
hash += ObjectUtils.nullSafeHashCode(this.diskUse);
return hash; return hash;
} }
@ -320,7 +340,16 @@ public class Meta {
if (!ObjectUtils.nullSafeEquals(this.values, other.values)) { if (!ObjectUtils.nullSafeEquals(this.values, other.values)) {
return false; return false;
} }
return ObjectUtils.nullSafeEquals(this.flags, other.flags);
if (!ObjectUtils.nullSafeEquals(this.flags, other.flags)) {
return false;
}
if (!ObjectUtils.nullSafeEquals(this.cursorBatchSize, other.cursorBatchSize)) {
return false;
}
return ObjectUtils.nullSafeEquals(this.diskUse, other.diskUse);
} }
/** /**

17
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Query.java

@ -15,9 +15,8 @@
*/ */
package org.springframework.data.mongodb.core.query; package org.springframework.data.mongodb.core.query;
import static org.springframework.data.mongodb.core.query.SerializationUtils.serializeToJsonSafely; import static org.springframework.data.mongodb.core.query.SerializationUtils.*;
import static org.springframework.util.ObjectUtils.nullSafeEquals; import static org.springframework.util.ObjectUtils.*;
import static org.springframework.util.ObjectUtils.nullSafeHashCode;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
@ -32,6 +31,7 @@ import java.util.Set;
import org.bson.Document; import org.bson.Document;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.KeysetScrollPosition;
import org.springframework.data.domain.Limit; import org.springframework.data.domain.Limit;
import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.data.domain.OffsetScrollPosition;
@ -585,6 +585,17 @@ public class Query implements ReadConcernAware, ReadPreferenceAware {
return diskUse(DiskUse.of(allowDiskUse)); return diskUse(DiskUse.of(allowDiskUse));
} }
/**
* Configures writing to temporary files for aggregation stages and queries. When set to {@link DiskUse#ALLOW},
* aggregation stages can write data to the {@code _tmp} subdirectory in the {@code dbPath} directory.
* <p>
* Note that the default value for {@literal allowDiskUseByDefault} is {@literal true} on the server side since
* MongoDB 6.0.
*
* @param diskUse
* @return this.
* @since 5.0
*/
@Contract("_ -> this") @Contract("_ -> this")
public Query diskUse(DiskUse diskUse) { public Query diskUse(DiskUse diskUse) {

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

@ -239,14 +239,16 @@ class QueryBlocks {
if (StringUtils.hasText(source.getQuery().getFieldsString())) { if (StringUtils.hasText(source.getQuery().getFieldsString())) {
VariableSnippet fields = Snippet.declare(builder).variable(Document.class, context.localVariable("fields")) VariableSnippet fields = Snippet.declare(builder).variable(Document.class, context.localVariable("fields"))
.of(MongoCodeBlocks.asDocument(context.getExpressionMarker(), source.getQuery().getFieldsString(), queryParameters.get())); .of(MongoCodeBlocks.asDocument(context.getExpressionMarker(), source.getQuery().getFieldsString(),
queryParameters.get()));
builder.addStatement("$L.setFieldsObject($L)", queryVariableName, fields.getVariableName()); builder.addStatement("$L.setFieldsObject($L)", queryVariableName, fields.getVariableName());
} }
if (StringUtils.hasText(source.getQuery().getSortString())) { if (StringUtils.hasText(source.getQuery().getSortString())) {
VariableSnippet sort = Snippet.declare(builder).variable(Document.class, context.localVariable("sort")) VariableSnippet sort = Snippet.declare(builder).variable(Document.class, context.localVariable("sort"))
.of(MongoCodeBlocks.asDocument(context.getExpressionMarker(), source.getQuery().getSortString(), getQueryParameters())); .of(MongoCodeBlocks.asDocument(context.getExpressionMarker(), source.getQuery().getSortString(),
getQueryParameters()));
builder.addStatement("$L.setSortObject($L)", queryVariableName, sort.getVariableName()); builder.addStatement("$L.setSortObject($L)", queryVariableName, sort.getVariableName());
} }
@ -312,13 +314,11 @@ class QueryBlocks {
} else { } else {
if (getQueryParameters().isEmpty()) { if (getQueryParameters().isEmpty()) {
builder.addStatement( builder.addStatement("$L.collation(collationOf(evaluate($L, $S)))", queryVariableName,
"$L.collation(collationOf(evaluate($L, $S)))", context.getExpressionMarker().enclosingMethod(), collationString);
queryVariableName, context.getExpressionMarker().enclosingMethod(), collationString);
} else { } else {
builder.addStatement( builder.addStatement("$L.collation(collationOf(evaluate($L, $S, $L)))", queryVariableName,
"$L.collation(collationOf(evaluate($L, $S, $L)))", context.getExpressionMarker().enclosingMethod(), collationString, getQueryParameters());
queryVariableName, context.getExpressionMarker().enclosingMethod(), collationString, getQueryParameters());
} }
} }
} }
@ -346,7 +346,8 @@ class QueryBlocks {
if (getQueryParameters().isEmpty()) { if (getQueryParameters().isEmpty()) {
builder.add("createQuery($L, $S)", context.getExpressionMarker().enclosingMethod(), source); builder.add("createQuery($L, $S)", context.getExpressionMarker().enclosingMethod(), source);
} else { } else {
builder.add("createQuery($L, $S, $L)", context.getExpressionMarker().enclosingMethod(), source, getQueryParameters()); builder.add("createQuery($L, $S, $L)", context.getExpressionMarker().enclosingMethod(), source,
getQueryParameters());
} }
return builder.build(); return builder.build();
} else { } else {

2
src/main/antora/modules/ROOT/pages/mongodb/aot.adoc

@ -76,7 +76,7 @@ These are typically all query methods that are not backed by an xref:repositorie
**Limitations** **Limitations**
* `@Meta.allowDiskUse` and `flags` are not evaluated. * `@Meta.flags` is not evaluated.
* Limited `Collation` detection. * Limited `Collation` detection.
* No support for in-clauses with pattern matching / case insensitivity * No support for in-clauses with pattern matching / case insensitivity

4
src/main/antora/modules/ROOT/pages/mongodb/repositories/query-methods.adoc

@ -672,7 +672,7 @@ Use the `@Meta` annotation to set those options via `maxExecutionTimeMs`, `comme
---- ----
interface PersonRepository extends CrudRepository<Person, String> { interface PersonRepository extends CrudRepository<Person, String> {
@Meta(allowDiskUse = true) @Meta(allowDiskUse = "true")
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }") @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames(); List<PersonAggregate> groupByLastnameAndFirstnames();
} }
@ -684,7 +684,7 @@ Or use `@Meta` to create your own annotation as shown in the sample below.
---- ----
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD }) @Target({ ElementType.METHOD })
@Meta(allowDiskUse = true) @Meta(allowDiskUse = "true")
@interface AllowDiskUse { } @interface AllowDiskUse { }
interface PersonRepository extends CrudRepository<Person, String> { interface PersonRepository extends CrudRepository<Person, String> {

Loading…
Cancel
Save