Browse Source

Polishing.

Update tests to work with new defaults and move those with previous String defaulting into a new home.
Revise reference documentation.

Original pull request: #4957
See: #4920
pull/5026/head
Christoph Strobl 7 months ago committed by Mark Paluch
parent
commit
c4f936951c
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/convert/MongoCustomConversions.java
  2. 170
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/BigDecimalToStringConvertingTemplateTests.java
  3. 53
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
  4. 8
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java
  5. 11
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java
  6. 21
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java
  7. 11
      src/main/antora/modules/ROOT/pages/mongodb/mapping/custom-conversions.adoc

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

@ -314,7 +314,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus @@ -314,7 +314,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
/**
* Configures the representation to for {@link java.math.BigDecimal} and {@link java.math.BigInteger} values in
* MongoDB. Defaults to {@link BigDecimalRepresentation#STRING}.
* MongoDB. Defaults to {@link BigDecimalRepresentation#DECIMAL128}.
*
* @param representation the representation to use.
* @return this.

170
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/BigDecimalToStringConvertingTemplateTests.java

@ -0,0 +1,170 @@ @@ -0,0 +1,170 @@
/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.data.auditing.IsNewAwareAuditingHandler;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mongodb.core.MongoTemplateTests.TypeWithNumbers;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.BigDecimalRepresentation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.test.util.Client;
import org.springframework.data.mongodb.test.util.MongoClientExtension;
import org.springframework.data.mongodb.test.util.MongoTestTemplate;
import com.mongodb.client.MongoClient;
/**
* Tests for {@link MongoTemplate} using string representation of {@link BigInteger} values.
*
* @author Christoph Strobl
*/
@ExtendWith(MongoClientExtension.class)
public class BigDecimalToStringConvertingTemplateTests {
public static final String DB_NAME = "mongo-template-tests";
static @Client MongoClient client;
@SuppressWarnings("deprecation") MongoTestTemplate template = new MongoTestTemplate(cfg -> {
cfg.configureDatabaseFactory(it -> {
it.client(client);
it.defaultDb(DB_NAME);
});
cfg.configureConversion(it -> {
it.customConversions(
MongoCustomConversions.create(adapter -> adapter.bigDecimal(BigDecimalRepresentation.STRING)));
});
cfg.configureMappingContext(it -> {
it.autocreateIndex(false);
});
cfg.configureAuditing(it -> {
it.auditingHandler(ctx -> {
return new IsNewAwareAuditingHandler(PersistentEntities.of(ctx));
});
});
});
@AfterEach
public void cleanUp() {
template.flush();
}
@Test // DATAMONGO-602
void testUsingAnInQueryWithBigIntegerId() {
template.remove(new Query(), PersonWithIdPropertyOfTypeBigInteger.class);
PersonWithIdPropertyOfTypeBigInteger p1 = new PersonWithIdPropertyOfTypeBigInteger();
p1.setFirstName("Sven");
p1.setAge(11);
p1.setId(new BigInteger("2666666666666666665069473312490162649510603601"));
template.insert(p1);
PersonWithIdPropertyOfTypeBigInteger p2 = new PersonWithIdPropertyOfTypeBigInteger();
p2.setFirstName("Mary");
p2.setAge(21);
p2.setId(new BigInteger("2666666666666666665069473312490162649510603602"));
template.insert(p2);
PersonWithIdPropertyOfTypeBigInteger p3 = new PersonWithIdPropertyOfTypeBigInteger();
p3.setFirstName("Ann");
p3.setAge(31);
p3.setId(new BigInteger("2666666666666666665069473312490162649510603603"));
template.insert(p3);
PersonWithIdPropertyOfTypeBigInteger p4 = new PersonWithIdPropertyOfTypeBigInteger();
p4.setFirstName("John");
p4.setAge(41);
p4.setId(new BigInteger("2666666666666666665069473312490162649510603604"));
template.insert(p4);
Query q1 = new Query(Criteria.where("age").in(11, 21, 41));
List<PersonWithIdPropertyOfTypeBigInteger> results1 = template.find(q1, PersonWithIdPropertyOfTypeBigInteger.class);
Query q2 = new Query(Criteria.where("firstName").in("Ann", "Mary"));
List<PersonWithIdPropertyOfTypeBigInteger> results2 = template.find(q2, PersonWithIdPropertyOfTypeBigInteger.class);
Query q3 = new Query(Criteria.where("id").in(new BigInteger("2666666666666666665069473312490162649510603601"),
new BigInteger("2666666666666666665069473312490162649510603604")));
List<PersonWithIdPropertyOfTypeBigInteger> results3 = template.find(q3, PersonWithIdPropertyOfTypeBigInteger.class);
assertThat(results1.size()).isEqualTo(3);
assertThat(results2.size()).isEqualTo(2);
assertThat(results3.size()).isEqualTo(2);
}
@Test // DATAMONGO-1404
void updatesBigNumberValueUsingStringComparisonWhenUsingMaxOperator() {
TypeWithNumbers twn = new TypeWithNumbers();
// Note that $max operator uses String comparison for BigDecimal/BigInteger comparison according to BSON sort rules.
// Therefore "80" is considered greater than "700"
twn.bigIntegerVal = new BigInteger("600");
twn.bigDeciamVal = new BigDecimal("700.0");
template.save(twn);
Update update = new Update()//
.max("bigIntegerVal", new BigInteger("70")) //
.max("bigDeciamVal", new BigDecimal("80")) //
;
template.updateFirst(query(where("id").is(twn.id)), update, TypeWithNumbers.class);
TypeWithNumbers loaded = template.find(query(where("id").is(twn.id)), TypeWithNumbers.class).get(0);
assertThat(loaded.bigIntegerVal).isEqualTo(new BigInteger("70"));
assertThat(loaded.bigDeciamVal).isEqualTo(new BigDecimal("80"));
}
@Test // DATAMONGO-1404
void updatesBigNumberValueUsingStringComparisonWhenUsingMinOperator() {
TypeWithNumbers twn = new TypeWithNumbers();
// Note that $max operator uses String comparison for BigDecimal/BigInteger comparison according to BSON sort rules.
// Therefore "80" is considered greater than "700"
twn.bigIntegerVal = new BigInteger("80");
twn.bigDeciamVal = new BigDecimal("90.0");
template.save(twn);
Update update = new Update()//
.min("bigIntegerVal", new BigInteger("700")) //
.min("bigDeciamVal", new BigDecimal("800")) //
;
template.updateFirst(query(where("id").is(twn.id)), update, TypeWithNumbers.class);
TypeWithNumbers loaded = template.find(query(where("id").is(twn.id)), TypeWithNumbers.class).get(0);
assertThat(loaded.bigIntegerVal).isEqualTo(new BigInteger("700"));
assertThat(loaded.bigDeciamVal).isEqualTo(new BigDecimal("800"));
}
}

53
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java

@ -41,6 +41,7 @@ import org.junit.jupiter.api.Test; @@ -41,6 +41,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.converter.Converter;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
@ -874,7 +875,7 @@ public class MongoTemplateTests { @@ -874,7 +875,7 @@ public class MongoTemplateTests {
assertThat(results3.size()).isEqualTo(2);
}
@Test // DATAMONGO-602
@Test // DATAMONGO-602, GH-4920
public void testUsingAnInQueryWithBigIntegerId() throws Exception {
template.remove(new Query(), PersonWithIdPropertyOfTypeBigInteger.class);
@ -883,33 +884,7 @@ public class MongoTemplateTests { @@ -883,33 +884,7 @@ public class MongoTemplateTests {
p1.setFirstName("Sven");
p1.setAge(11);
p1.setId(new BigInteger("2666666666666666665069473312490162649510603601"));
template.insert(p1);
PersonWithIdPropertyOfTypeBigInteger p2 = new PersonWithIdPropertyOfTypeBigInteger();
p2.setFirstName("Mary");
p2.setAge(21);
p2.setId(new BigInteger("2666666666666666665069473312490162649510603602"));
template.insert(p2);
PersonWithIdPropertyOfTypeBigInteger p3 = new PersonWithIdPropertyOfTypeBigInteger();
p3.setFirstName("Ann");
p3.setAge(31);
p3.setId(new BigInteger("2666666666666666665069473312490162649510603603"));
template.insert(p3);
PersonWithIdPropertyOfTypeBigInteger p4 = new PersonWithIdPropertyOfTypeBigInteger();
p4.setFirstName("John");
p4.setAge(41);
p4.setId(new BigInteger("2666666666666666665069473312490162649510603604"));
template.insert(p4);
Query q1 = new Query(Criteria.where("age").in(11, 21, 41));
List<PersonWithIdPropertyOfTypeBigInteger> results1 = template.find(q1, PersonWithIdPropertyOfTypeBigInteger.class);
Query q2 = new Query(Criteria.where("firstName").in("Ann", "Mary"));
List<PersonWithIdPropertyOfTypeBigInteger> results2 = template.find(q2, PersonWithIdPropertyOfTypeBigInteger.class);
Query q3 = new Query(Criteria.where("id").in(new BigInteger("2666666666666666665069473312490162649510603601"),
new BigInteger("2666666666666666665069473312490162649510603604")));
List<PersonWithIdPropertyOfTypeBigInteger> results3 = template.find(q3, PersonWithIdPropertyOfTypeBigInteger.class);
assertThat(results1.size()).isEqualTo(3);
assertThat(results2.size()).isEqualTo(2);
assertThat(results3.size()).isEqualTo(2);
assertThatExceptionOfType(ConversionFailedException.class).isThrownBy(() -> template.insert(p1));
}
@Test
@ -3274,7 +3249,6 @@ public class MongoTemplateTests { @@ -3274,7 +3249,6 @@ public class MongoTemplateTests {
twn.intVal = 400;
twn.longVal = 500L;
// Note that $min operator uses String comparison for BigDecimal/BigInteger comparison according to BSON sort rules.
twn.bigIntegerVal = new BigInteger("600");
twn.bigDeciamVal = new BigDecimal("700.0");
@ -3330,7 +3304,6 @@ public class MongoTemplateTests { @@ -3330,7 +3304,6 @@ public class MongoTemplateTests {
twn.intVal = 400;
twn.longVal = 500L;
// Note that $max operator uses String comparison for BigDecimal/BigInteger comparison according to BSON sort rules.
twn.bigIntegerVal = new BigInteger("600");
twn.bigDeciamVal = new BigDecimal("700.0");
@ -3359,13 +3332,11 @@ public class MongoTemplateTests { @@ -3359,13 +3332,11 @@ public class MongoTemplateTests {
assertThat(loaded.bigDeciamVal).isEqualTo(new BigDecimal("790"));
}
@Test // DATAMONGO-1404
public void updatesBigNumberValueUsingStringComparisonWhenUsingMaxOperator() {
@Test // DATAMONGO-1404, GH-4920
public void updatesBigNumberValueUsingUsingMaxOperator() {
TypeWithNumbers twn = new TypeWithNumbers();
// Note that $max operator uses String comparison for BigDecimal/BigInteger comparison according to BSON sort rules.
// Therefore "80" is considered greater than "700"
twn.bigIntegerVal = new BigInteger("600");
twn.bigDeciamVal = new BigDecimal("700.0");
@ -3379,17 +3350,15 @@ public class MongoTemplateTests { @@ -3379,17 +3350,15 @@ public class MongoTemplateTests {
template.updateFirst(query(where("id").is(twn.id)), update, TypeWithNumbers.class);
TypeWithNumbers loaded = template.find(query(where("id").is(twn.id)), TypeWithNumbers.class).get(0);
assertThat(loaded.bigIntegerVal).isEqualTo(new BigInteger("70"));
assertThat(loaded.bigDeciamVal).isEqualTo(new BigDecimal("80"));
assertThat(loaded.bigIntegerVal).isEqualTo(new BigInteger("600"));
assertThat(loaded.bigDeciamVal).isEqualTo(new BigDecimal("700.0"));
}
@Test // DATAMONGO-1404
public void updatesBigNumberValueUsingStringComparisonWhenUsingMinOperator() {
@Test // DATAMONGO-1404, GH-4920
public void updatesBigNumberValueWhenUsingMinOperator() {
TypeWithNumbers twn = new TypeWithNumbers();
// Note that $max operator uses String comparison for BigDecimal/BigInteger comparison according to BSON sort rules.
// Therefore "80" is considered greater than "700"
twn.bigIntegerVal = new BigInteger("80");
twn.bigDeciamVal = new BigDecimal("90.0");
@ -3403,8 +3372,8 @@ public class MongoTemplateTests { @@ -3403,8 +3372,8 @@ public class MongoTemplateTests {
template.updateFirst(query(where("id").is(twn.id)), update, TypeWithNumbers.class);
TypeWithNumbers loaded = template.find(query(where("id").is(twn.id)), TypeWithNumbers.class).get(0);
assertThat(loaded.bigIntegerVal).isEqualTo(new BigInteger("700"));
assertThat(loaded.bigDeciamVal).isEqualTo(new BigDecimal("800"));
assertThat(loaded.bigIntegerVal).isEqualTo(new BigInteger("80"));
assertThat(loaded.bigDeciamVal).isEqualTo(new BigDecimal("90.0"));
}
@Test // DATAMONGO-1431, DATAMONGO-2323

8
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DbRefMappingMongoConverterUnitTests.java

@ -89,7 +89,9 @@ class DbRefMappingMongoConverterUnitTests { @@ -89,7 +89,9 @@ class DbRefMappingMongoConverterUnitTests {
this.dbRefResolver = spy(new DefaultDbRefResolver(dbFactory));
this.mappingContext = new MongoMappingContext();
this.mappingContext.setSimpleTypeHolder(new MongoCustomConversions(Collections.emptyList()).getSimpleTypeHolder());
this.mappingContext.afterPropertiesSet();
this.converter = new MappingMongoConverter(dbRefResolver, mappingContext);
this.converter.afterPropertiesSet();
}
@Test // DATAMONGO-347
@ -103,7 +105,7 @@ class DbRefMappingMongoConverterUnitTests { @@ -103,7 +105,7 @@ class DbRefMappingMongoConverterUnitTests {
assertThat(dbRef.getCollectionName()).isEqualTo("person");
}
@Test // DATAMONGO-657
@Test // DATAMONGO-657, GH-4920
void convertDocumentWithMapDBRef() {
Document mapValDocument = new Document();
@ -610,7 +612,7 @@ class DbRefMappingMongoConverterUnitTests { @@ -610,7 +612,7 @@ class DbRefMappingMongoConverterUnitTests {
verify(converterSpy, never()).bulkReadRefs(anyList());
}
@Test // DATAMONGO-1194
@Test // DATAMONGO-1194, GH-4920
void shouldBulkFetchMapOfReferences() {
MapDBRefVal val1 = new MapDBRefVal();
@ -642,7 +644,7 @@ class DbRefMappingMongoConverterUnitTests { @@ -642,7 +644,7 @@ class DbRefMappingMongoConverterUnitTests {
verify(converterSpy, never()).readRef(Mockito.any(DBRef.class));
}
@Test // DATAMONGO-1194
@Test // DATAMONGO-1194, GH-4920
void shouldBulkFetchLazyMapOfReferences() {
MapDBRefVal val1 = new MapDBRefVal();

11
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java

@ -384,7 +384,7 @@ class MappingMongoConverterUnitTests { @@ -384,7 +384,7 @@ class MappingMongoConverterUnitTests {
assertThat(nestedMap.get("afield")).isEqualTo(firstLevel);
}
@Test // DATACMNS-42, DATAMONGO-171
@Test // DATACMNS-42, DATAMONGO-171, GH-4920
void writesClassWithBigDecimal() {
BigDecimalContainer container = new BigDecimalContainer();
@ -394,9 +394,8 @@ class MappingMongoConverterUnitTests { @@ -394,9 +394,8 @@ class MappingMongoConverterUnitTests {
org.bson.Document document = new org.bson.Document();
converter.write(container, document);
assertThat(document.get("value")).isInstanceOf(String.class);
assertThat((String) document.get("value")).isEqualTo("2.5");
assertThat(((org.bson.Document) document.get("map")).get("foo")).isInstanceOf(String.class);
assertThat(document.get("value")).isEqualTo(Decimal128.parse("2.5"));
assertThat(((org.bson.Document) document.get("map")).get("foo")).isInstanceOf(Decimal128.class);
}
@Test // DATACMNS-42, DATAMONGO-171
@ -513,7 +512,7 @@ class MappingMongoConverterUnitTests { @@ -513,7 +512,7 @@ class MappingMongoConverterUnitTests {
assertThat(((org.bson.Document) map).keySet()).contains("en_US");
}
@Test
@Test // GH-4920
void writesBigIntegerIdCorrectly() {
ClassWithBigIntegerId foo = new ClassWithBigIntegerId();
@ -522,7 +521,7 @@ class MappingMongoConverterUnitTests { @@ -522,7 +521,7 @@ class MappingMongoConverterUnitTests {
org.bson.Document result = new org.bson.Document();
converter.write(foo, result);
assertThat(result.get("_id")).isInstanceOf(String.class);
assertThat(result.get("_id")).isInstanceOf(Decimal128.class);
}
@Test

21
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java

@ -34,6 +34,7 @@ import java.util.regex.Pattern; @@ -34,6 +34,7 @@ import java.util.regex.Pattern;
import org.bson.BsonRegularExpression;
import org.bson.conversions.Bson;
import org.bson.types.Code;
import org.bson.types.Decimal128;
import org.bson.types.ObjectId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -53,6 +54,7 @@ import org.springframework.data.mongodb.core.aggregation.ConditionalOperators; @@ -53,6 +54,7 @@ import org.springframework.data.mongodb.core.aggregation.ConditionalOperators;
import org.springframework.data.mongodb.core.aggregation.EvaluationOperators;
import org.springframework.data.mongodb.core.aggregation.EvaluationOperators.Expr;
import org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.BigDecimalRepresentation;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
import org.springframework.data.mongodb.core.mapping.DBRef;
@ -126,13 +128,30 @@ public class QueryMapperUnitTests { @@ -126,13 +128,30 @@ public class QueryMapperUnitTests {
}
@Test
void handlesBigIntegerIdsCorrectly() {
@SuppressWarnings("deprecation")
void handlesBigIntegerIdsCorrectly/*in legacy string format*/() {
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
converter.setCustomConversions(MongoCustomConversions.create(adapter -> adapter.bigDecimal(BigDecimalRepresentation.STRING)));
converter.afterPropertiesSet();
QueryMapper mapper = new QueryMapper(converter);
org.bson.Document document = new org.bson.Document("id", new BigInteger("1"));
org.bson.Document result = mapper.getMappedObject(document, context.getPersistentEntity(IdWrapper.class));
assertThat(result).containsEntry("_id", "1");
}
@Test // GH-4920
void handlesBigIntegerIdAsDecimal128Correctly() {
org.bson.Document document = new org.bson.Document("id", new BigInteger("1"));
org.bson.Document result = mapper.getMappedObject(document, context.getPersistentEntity(IdWrapper.class));
assertThat(result).containsEntry("_id", Decimal128.parse("1"));
}
@Test
void handlesObjectIdCapableBigIntegerIdsCorrectly() {

11
src/main/antora/modules/ROOT/pages/mongodb/mapping/custom-conversions.adoc

@ -34,7 +34,7 @@ public class Payment { @@ -34,7 +34,7 @@ public class Payment {
for details.
<2> The desired target type is explicitly defined as `Decimal128` which translates to `NumberDecimal`.
Otherwise, the
`BigDecimal` value would have been truned into a `String`.
`BigDecimal` value would have been turned into a `String`.
<3> `Date` values are handled by the MongoDB driver itself are stored as `ISODate`.
====
@ -110,5 +110,12 @@ class MyMongoConfiguration extends AbstractMongoClientConfiguration { @@ -110,5 +110,12 @@ class MyMongoConfiguration extends AbstractMongoClientConfiguration {
MongoDB in its early days did not have support for large numeric values such as `BigDecimal`.
To persist `BigDecimal` and `BigInteger` values, Spring Data MongoDB converted values their `String` representation.
This approach had several downsides due to lexical instead of numeric comparison for queries, updates, etc.
With MongoDB Server 3.4, `org.bson.types.Decimal128` offers a native representation for `BigDecimal` and `BigInteger`.
You can use the to the native representation by either annotating your properties with `@Field(targetType=DECIMAL128)` or by configuring the big decimal representation in `MongoCustomConversions` through `MongoCustomConversions.create(config -> config.bigDecimal(…))`.
As off Spring Data MongoDB 5.0 the default representation of those types moved to MongoDB native `org.bson.types.Decimal128` as well.
You can still use the to the deprecated `String` variant by configuring the big decimal representation in `MongoCustomConversions` through `MongoCustomConversions.create(config -> config.bigDecimal(BigDecimalRepresentation.STRING))`.
[NOTE]
====
Very large values, though being valid in their java, might exceed the maximum bit length of `org.bson.types.Decimal128` in their store native representation.
====

Loading…
Cancel
Save