Browse Source

Require explicit BigInteger/BigDecimal conversion settings.

See: #5037
Original Pull Request: #5051
pull/5056/head
Christoph Strobl 3 months ago
parent
commit
8b807890ac
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 5
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java
  2. 36
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoCustomConversions.java
  3. 2
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateCollationTests.java
  4. 134
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateTests.java
  5. 139
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java
  6. 22
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/QueryMapperUnitTests.java
  7. 6
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoConverterConfigurer.java
  8. 1
      src/main/antora/modules/ROOT/nav.adoc
  9. 60
      src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-4.x-to-5.x.adoc
  10. 11
      src/main/antora/modules/ROOT/pages/mongodb/mapping/custom-conversions.adoc

5
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverters.java

@ -132,9 +132,7 @@ abstract class MongoConverters { @@ -132,9 +132,7 @@ abstract class MongoConverters {
List<Object> converters = new ArrayList<>(4);
converters.add(BigDecimalToStringConverter.INSTANCE);
converters.add(StringToBigDecimalConverter.INSTANCE);
converters.add(BigIntegerToStringConverter.INSTANCE);
converters.add(StringToBigIntegerConverter.INSTANCE);
return converters;
}
@ -169,6 +167,9 @@ abstract class MongoConverters { @@ -169,6 +167,9 @@ abstract class MongoConverters {
converters.add(ListToVectorConverter.INSTANCE);
converters.add(BinaryVectorToMongoVectorConverter.INSTANCE);
converters.add(StringToBigDecimalConverter.INSTANCE);
converters.add(StringToBigIntegerConverter.INSTANCE);
converters.add(reading(BsonUndefined.class, Object.class, it -> null));
converters.add(reading(String.class, URI.class, URI::create).andWriting(URI::toString));

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

@ -31,8 +31,9 @@ import java.util.Locale; @@ -31,8 +31,9 @@ import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
@ -63,6 +64,7 @@ import org.springframework.util.Assert; @@ -63,6 +64,7 @@ import org.springframework.util.Assert;
*/
public class MongoCustomConversions extends org.springframework.data.convert.CustomConversions {
private static final Log LOGGER = LogFactory.getLog(MongoCustomConversions.class);
private static final List<Object> STORE_CONVERTERS;
static {
@ -153,7 +155,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus @@ -153,7 +155,7 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
LocalDateTime.class);
private boolean useNativeDriverJavaTimeCodecs = false;
private BigDecimalRepresentation bigDecimals = BigDecimalRepresentation.DECIMAL128;
private BigDecimalRepresentation @Nullable [] bigDecimals;
private final List<Object> customConverters = new ArrayList<>();
private final PropertyValueConversions internalValueConversion = PropertyValueConversions.simple(it -> {});
@ -310,14 +312,14 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus @@ -310,14 +312,14 @@ 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#DECIMAL128}.
*
* @param representation the representation to use.
* @param representations ordered list of representations to use (first one is default)
* @return this.
* @since 4.5
*/
public MongoConverterConfigurationAdapter bigDecimal(BigDecimalRepresentation representation) {
public MongoConverterConfigurationAdapter bigDecimal(BigDecimalRepresentation... representations) {
Assert.notNull(representation, "BigDecimalDataType must not be null");
this.bigDecimals = representation;
Assert.notEmpty(representations, "BigDecimalDataType must not be null");
this.bigDecimals = representations;
return this;
}
@ -372,12 +374,16 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus @@ -372,12 +374,16 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
List<Object> storeConverters = new ArrayList<>(STORE_CONVERTERS.size() + 10);
if (bigDecimals == BigDecimalRepresentation.STRING) {
storeConverters.addAll(MongoConverters.getBigNumberStringConverters());
}
if (bigDecimals == BigDecimalRepresentation.DECIMAL128) {
storeConverters.addAll(MongoConverters.getBigNumberDecimal128Converters());
if (bigDecimals != null) {
for (BigDecimalRepresentation representation : bigDecimals) {
switch (representation) {
case STRING -> storeConverters.addAll(MongoConverters.getBigNumberStringConverters());
case DECIMAL128 -> storeConverters.addAll(MongoConverters.getBigNumberDecimal128Converters());
}
}
} else if (LOGGER.isInfoEnabled()) {
LOGGER.info(
"No BigDecimal/BigInteger representation set. Choose [DECIMAL128] and/or [String] to store values in desired format.");
}
if (useNativeDriverJavaTimeCodecs) {
@ -395,9 +401,9 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus @@ -395,9 +401,9 @@ public class MongoCustomConversions extends org.springframework.data.convert.Cus
// Avoid default registrations
return !JAVA_DRIVER_TIME_SIMPLE_TYPES.contains(convertiblePair.getSourceType())
|| !Date.class.isAssignableFrom(convertiblePair.getTargetType());
}, this.propertyValueConversions);
return !JAVA_DRIVER_TIME_SIMPLE_TYPES.contains(convertiblePair.getSourceType())
|| !Date.class.isAssignableFrom(convertiblePair.getTargetType());
}, this.propertyValueConversions);
}

2
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/MongoTemplateCollationTests.java

@ -30,6 +30,8 @@ import org.junit.jupiter.api.extension.ExtendWith; @@ -30,6 +30,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.BigDecimalRepresentation;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.query.Collation.Alternate;
import org.springframework.data.mongodb.core.query.Collation.ComparisonLevel;

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

@ -15,10 +15,14 @@ @@ -15,10 +15,14 @@
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
import static org.springframework.data.mongodb.core.query.Update.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.fail;
import static org.springframework.data.mongodb.core.query.Criteria.expr;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
import static org.springframework.data.mongodb.core.query.Update.update;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
@ -28,17 +32,29 @@ import java.time.Instant; @@ -28,17 +32,29 @@ import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.bson.codecs.configuration.CodecConfigurationException;
import org.bson.types.ObjectId;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.convert.ConversionFailedException;
@ -69,6 +85,7 @@ import org.springframework.data.mongodb.core.aggregation.StringOperators; @@ -69,6 +85,7 @@ import org.springframework.data.mongodb.core.aggregation.StringOperators;
import org.springframework.data.mongodb.core.convert.LazyLoadingProxy;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.BigDecimalRepresentation;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
@ -144,6 +161,12 @@ public class MongoTemplateTests { @@ -144,6 +161,12 @@ public class MongoTemplateTests {
it.defaultDb(DB_NAME);
});
cfg.configureConversion(it -> {
it.customConverters(adapter -> {
adapter.bigDecimal(BigDecimalRepresentation.DECIMAL128);
});
});
cfg.configureMappingContext(it -> {
it.autocreateIndex(false);
it.initialEntitySet(AuditablePerson.class);
@ -170,7 +193,10 @@ public class MongoTemplateTests { @@ -170,7 +193,10 @@ public class MongoTemplateTests {
});
cfg.configureConversion(it -> {
it.customConverters(DateToDateTimeConverter.INSTANCE, DateTimeToDateConverter.INSTANCE);
it.customConverters(adapter -> {
adapter.registerConverters(List.of(DateToDateTimeConverter.INSTANCE, DateTimeToDateConverter.INSTANCE))
.bigDecimal(BigDecimalRepresentation.DECIMAL128);
});
});
cfg.configureMappingContext(it -> {
@ -732,7 +758,7 @@ public class MongoTemplateTests { @@ -732,7 +758,7 @@ public class MongoTemplateTests {
.containsExactlyInAnyOrder(person1.getName(), person2.getName());
assertThat(template.findDistinct(new BasicQuery("{'address.state' : 'PA'}"), "name",
template.getCollectionName(MyPerson.class), MyPerson.class, String.class))
.containsExactlyInAnyOrder(person1.getName(), person2.getName());
.containsExactlyInAnyOrder(person1.getName(), person2.getName());
}
@Test // DATAMONGO-1761
@ -876,7 +902,7 @@ public class MongoTemplateTests { @@ -876,7 +902,7 @@ public class MongoTemplateTests {
}
@Test // DATAMONGO-602, GH-4920
public void testUsingAnInQueryWithBigIntegerId() throws Exception {
public void testUsingAnInQueryWithBigIntegerId() {
template.remove(new Query(), PersonWithIdPropertyOfTypeBigInteger.class);
@ -887,6 +913,34 @@ public class MongoTemplateTests { @@ -887,6 +913,34 @@ public class MongoTemplateTests {
assertThatExceptionOfType(ConversionFailedException.class).isThrownBy(() -> template.insert(p1));
}
@Test // GH-5037
public void errorsIfNoBigNumberFormatDefined() {
template = new MongoTestTemplate(cfg -> {
cfg.configureDatabaseFactory(it -> {
it.client(client);
it.defaultDb(DB_NAME);
});
cfg.configureConversion(it -> {
it.customConverters(adapter -> {
// no numeric conversion
});
});
});
template.remove(new Query(), PersonWithIdPropertyOfTypeBigInteger.class);
PersonWithIdPropertyOfTypeBigInteger p1 = new PersonWithIdPropertyOfTypeBigInteger();
p1.setFirstName("Sven");
p1.setAge(11);
p1.setId(new BigInteger("2666666666666666665"));
assertThatExceptionOfType(CodecConfigurationException.class).isThrownBy(() -> template.insert(p1));
}
@Test
public void testUsingAnInQueryWithPrimitiveIntId() throws Exception {
@ -2561,9 +2615,7 @@ public class MongoTemplateTests { @@ -2561,9 +2615,7 @@ public class MongoTemplateTests {
walter.address = new Address("spring", "data");
template.save(walter);
PersonPWA result = template.query(MyPerson.class)
.as(PersonPWA.class)
.matching(where("id").is(walter.id))
PersonPWA result = template.query(MyPerson.class).as(PersonPWA.class).matching(where("id").is(walter.id))
.firstValue();
assertThat(result.getAddress().getCity()).isEqualTo("data");
@ -2571,6 +2623,7 @@ public class MongoTemplateTests { @@ -2571,6 +2623,7 @@ public class MongoTemplateTests {
interface PersonPWA {
String getName();
AdressProjection getAddress();
}
@ -2823,7 +2876,7 @@ public class MongoTemplateTests { @@ -2823,7 +2876,7 @@ public class MongoTemplateTests {
assertThat(template.getDb().getCollection("sample").countDocuments(
new org.bson.Document("field", new org.bson.Document("$in", Arrays.asList("spring", "mongodb")))))
.isEqualTo(0L);
.isEqualTo(0L);
assertThat(template.getDb().getCollection("sample").countDocuments(new org.bson.Document("field", "data")))
.isEqualTo(1L);
}
@ -3935,7 +3988,8 @@ public class MongoTemplateTests { @@ -3935,7 +3988,8 @@ public class MongoTemplateTests {
template.save(source);
org.bson.Document raw = template.execute(WithFieldNameContainingDots.class, collection -> collection.find(new org.bson.Document("_id", source.id)).first());
org.bson.Document raw = template.execute(WithFieldNameContainingDots.class,
collection -> collection.find(new org.bson.Document("_id", source.id)).first());
assertThat(raw).containsEntry("field.name.with.dots", "v1");
}
@ -3954,13 +4008,17 @@ public class MongoTemplateTests { @@ -3954,13 +4008,17 @@ public class MongoTemplateTests {
template.save(source);
template.save(source2);
WithFieldNameContainingDots loaded = template.query(WithFieldNameContainingDots.class) // with property -> fieldname mapping
.matching(expr(ComparisonOperators.valueOf(ObjectOperators.getValueOf("value")).equalToValue("v1"))).firstValue();
WithFieldNameContainingDots loaded = template.query(WithFieldNameContainingDots.class) // with property -> fieldname
// mapping
.matching(expr(ComparisonOperators.valueOf(ObjectOperators.getValueOf("value")).equalToValue("v1")))
.firstValue();
assertThat(loaded).isEqualTo(source);
loaded = template.query(WithFieldNameContainingDots.class) // using raw fieldname
.matching(expr(ComparisonOperators.valueOf(ObjectOperators.getValueOf("field.name.with.dots")).equalToValue("v1"))).firstValue();
.matching(
expr(ComparisonOperators.valueOf(ObjectOperators.getValueOf("field.name.with.dots")).equalToValue("v1")))
.firstValue();
assertThat(loaded).isEqualTo(source);
}
@ -3975,20 +4033,20 @@ public class MongoTemplateTests { @@ -3975,20 +4033,20 @@ public class MongoTemplateTests {
template.save(source);
template.update(WithFieldNameContainingDots.class)
.matching(where("id").is(source.id))
.apply(AggregationUpdate.newUpdate(ReplaceWithOperation.replaceWithValue(ObjectOperators.setValueTo("value", "changed"))))
.first();
template.update(WithFieldNameContainingDots.class).matching(where("id").is(source.id)).apply(AggregationUpdate
.newUpdate(ReplaceWithOperation.replaceWithValue(ObjectOperators.setValueTo("value", "changed")))).first();
org.bson.Document raw = template.execute(WithFieldNameContainingDots.class, collection -> collection.find(new org.bson.Document("_id", source.id)).first());
org.bson.Document raw = template.execute(WithFieldNameContainingDots.class,
collection -> collection.find(new org.bson.Document("_id", source.id)).first());
assertThat(raw).containsEntry("field.name.with.dots", "changed");
template.update(WithFieldNameContainingDots.class)
.matching(where("id").is(source.id))
.apply(AggregationUpdate.newUpdate(ReplaceWithOperation.replaceWithValue(ObjectOperators.setValueTo("field.name.with.dots", "changed-again"))))
template.update(WithFieldNameContainingDots.class).matching(where("id").is(source.id))
.apply(AggregationUpdate.newUpdate(
ReplaceWithOperation.replaceWithValue(ObjectOperators.setValueTo("field.name.with.dots", "changed-again"))))
.first();
raw = template.execute(WithFieldNameContainingDots.class, collection -> collection.find(new org.bson.Document("_id", source.id)).first());
raw = template.execute(WithFieldNameContainingDots.class,
collection -> collection.find(new org.bson.Document("_id", source.id)).first());
assertThat(raw).containsEntry("field.name.with.dots", "changed-again");
}
@ -4013,9 +4071,8 @@ public class MongoTemplateTests { @@ -4013,9 +4071,8 @@ public class MongoTemplateTests {
org.bson.Document raw = template.execute(WithFieldNameContainingDots.class,
collection -> collection.find(new org.bson.Document("_id", source.id)).first());
assertThat(raw.get("mapValue", org.bson.Document.class))
.containsEntry("k1", "v1")
.containsEntry("map.key.with.dot", "v2");
assertThat(raw.get("mapValue", org.bson.Document.class)).containsEntry("k1", "v1").containsEntry("map.key.with.dot",
"v2");
}
@Test // GH-4464
@ -4031,16 +4088,13 @@ public class MongoTemplateTests { @@ -4031,16 +4088,13 @@ public class MongoTemplateTests {
MongoTemplate template = new MongoTemplate(new SimpleMongoClientDatabaseFactory(client, DB_NAME), converter);
Map<String, String> sourceMap = Map.of("k1", "v1", "sourceMap.key.with.dot", "v2");
template.execute(WithFieldNameContainingDots.class,
collection -> {
collection.insertOne(new org.bson.Document("_id", "id-1").append("mapValue", sourceMap));
return null;
}
);
template.execute(WithFieldNameContainingDots.class, collection -> {
collection.insertOne(new org.bson.Document("_id", "id-1").append("mapValue", sourceMap));
return null;
});
WithFieldNameContainingDots loaded = template.query(WithFieldNameContainingDots.class)
.matching(where("id").is("id-1"))
.firstValue();
.matching(where("id").is("id-1")).firstValue();
assertThat(loaded.mapValue).isEqualTo(sourceMap);
}
@ -5015,8 +5069,7 @@ public class MongoTemplateTests { @@ -5015,8 +5069,7 @@ public class MongoTemplateTests {
String id;
@Field(value = "field.name.with.dots", nameType = Type.KEY)
String value;
@Field(value = "field.name.with.dots", nameType = Type.KEY) String value;
Map<String, String> mapValue;
@ -5034,7 +5087,8 @@ public class MongoTemplateTests { @@ -5034,7 +5087,8 @@ public class MongoTemplateTests {
return false;
}
WithFieldNameContainingDots withFieldNameContainingDots = (WithFieldNameContainingDots) o;
return Objects.equals(id, withFieldNameContainingDots.id) && Objects.equals(value, withFieldNameContainingDots.value)
return Objects.equals(id, withFieldNameContainingDots.id)
&& Objects.equals(value, withFieldNameContainingDots.value)
&& Objects.equals(mapValue, withFieldNameContainingDots.mapValue);
}

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

@ -15,10 +15,22 @@ @@ -15,10 +15,22 @@
*/
package org.springframework.data.mongodb.core.convert;
import static java.time.ZoneId.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.DocumentTestUtils.*;
import static org.springframework.data.mongodb.test.util.Assertions.*;
import static java.time.ZoneId.systemDefault;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.data.mongodb.core.DocumentTestUtils.assertTypeHint;
import static org.springframework.data.mongodb.core.DocumentTestUtils.getAsDocument;
import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
import static org.springframework.data.mongodb.test.util.Assertions.assertThatExceptionOfType;
import static org.springframework.data.mongodb.test.util.Assertions.assertThatThrownBy;
import static org.springframework.data.mongodb.test.util.Assertions.fail;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -27,7 +39,24 @@ import java.nio.ByteBuffer; @@ -27,7 +39,24 @@ import java.nio.ByteBuffer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
@ -53,7 +82,6 @@ import org.junit.jupiter.params.provider.ValueSource; @@ -53,7 +82,6 @@ import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.factory.annotation.Autowired;
@ -62,6 +90,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -62,6 +90,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
@ -90,6 +119,8 @@ import org.springframework.data.mongodb.core.DocumentTestUtils; @@ -90,6 +119,8 @@ import org.springframework.data.mongodb.core.DocumentTestUtils;
import org.springframework.data.mongodb.core.convert.DocumentAccessorUnitTests.NestedType;
import org.springframework.data.mongodb.core.convert.DocumentAccessorUnitTests.ProjectingType;
import org.springframework.data.mongodb.core.convert.MappingMongoConverterUnitTests.ClassWithMapUsingEnumAsKey.FooBarEnum;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.BigDecimalRepresentation;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
import org.springframework.data.mongodb.core.geo.Sphere;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
@ -135,8 +166,8 @@ class MappingMongoConverterUnitTests { @@ -135,8 +166,8 @@ class MappingMongoConverterUnitTests {
@BeforeEach
void beforeEach() {
MongoCustomConversions conversions = new MongoCustomConversions(
Arrays.asList(new ByteBufferToDoubleHolderConverter()));
MongoCustomConversions conversions = new MongoCustomConversions(new MongoConverterConfigurationAdapter()
.registerConverter(new ByteBufferToDoubleHolderConverter()).bigDecimal(BigDecimalRepresentation.DECIMAL128));
mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(context);
@ -396,6 +427,31 @@ class MappingMongoConverterUnitTests { @@ -396,6 +427,31 @@ class MappingMongoConverterUnitTests {
assertThat(document.get("value")).isEqualTo(Decimal128.parse("2.5"));
assertThat(((org.bson.Document) document.get("map")).get("foo")).isInstanceOf(Decimal128.class);
} // MappingMongoConverterUnitTests
@Test // DATACMNS-42, DATAMONGO-171, GH-4920
void writesClassWithBigDecimalFails() {
MongoCustomConversions conversions = new MongoCustomConversions(new MongoConverterConfigurationAdapter());
mappingContext = new MongoMappingContext();
mappingContext.setApplicationContext(context);
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
mappingContext.afterPropertiesSet();
mappingContext.getPersistentEntity(Address.class);
converter = new MappingMongoConverter(resolver, mappingContext);
BigDecimalContainer container = new BigDecimalContainer();
container.value = BigDecimal.valueOf(2.5d);
container.map = Collections.singletonMap("foo", container.value);
org.bson.Document document = new org.bson.Document();
converter.write(container, document);
assertThat(document.get("value")).isInstanceOf(BigDecimal.class);
assertThat(((org.bson.Document) document.get("map")).get("foo")).isInstanceOf(BigDecimal.class);
}
@Test // DATACMNS-42, DATAMONGO-171
@ -2149,6 +2205,61 @@ class MappingMongoConverterUnitTests { @@ -2149,6 +2205,61 @@ class MappingMongoConverterUnitTests {
assertThat(target.get("bigDecimal")).isEqualTo(new Decimal128(source.bigDecimal));
}
@Test // GH-5037
@SuppressWarnings("deprecation")
void mapsBigIntegerToDecimal128WhenAnnotatedWithFieldTargetTypeWhenDefaultConversionIsSetToString() {
converter = createConverter(BigDecimalRepresentation.STRING, BigDecimalRepresentation.DECIMAL128);
WithExplicitTargetTypes source = new WithExplicitTargetTypes();
source.bigDecimal = BigDecimal.valueOf(3.14159D);
org.bson.Document target = new org.bson.Document();
converter.write(source, target);
assertThat(target.get("bigDecimal")).isEqualTo(new Decimal128(source.bigDecimal));
}
@Test // GH-5037
@SuppressWarnings("deprecation")
void mapsBigIntegerToStringWhenNotAnnotatedWithFieldTargetTypeAndDefaultConversionIsSetToString() {
converter = createConverter(BigDecimalRepresentation.STRING, BigDecimalRepresentation.DECIMAL128);
BigDecimalContainer source = new BigDecimalContainer();
source.value = BigDecimal.valueOf(3.14159D);
org.bson.Document target = new org.bson.Document();
converter.write(source, target);
assertThat(target.get("value")).isInstanceOf(String.class);
}
@Test // GH-5037
void mapsBigIntegerToStringWhenAnnotatedWithFieldTargetTypeEvenWhenDefaultConverterIsSetToDecimal128() {
converter = createConverter(BigDecimalRepresentation.DECIMAL128);
WithExplicitTargetTypes source = new WithExplicitTargetTypes();
source.bigIntegerAsString = BigInteger.TWO;
org.bson.Document target = new org.bson.Document();
converter.write(source, target);
assertThat(target.get("bigIntegerAsString")).isEqualTo(source.bigIntegerAsString.toString());
}
@Test // GH-5037
void explicitBigNumberConversionErrorsIfConverterNotRegistered() {
converter = createConverter(BigDecimalRepresentation.STRING);
WithExplicitTargetTypes source = new WithExplicitTargetTypes();
source.bigInteger = BigInteger.TWO;
org.bson.Document target = new org.bson.Document();
assertThatExceptionOfType(ConversionFailedException.class).isThrownBy(() -> converter.write(source, target));
}
@Test // DATAMONGO-2328
void mapsDateToLongWhenAnnotatedWithFieldTargetType() {
@ -3171,7 +3282,6 @@ class MappingMongoConverterUnitTests { @@ -3171,7 +3282,6 @@ class MappingMongoConverterUnitTests {
return nativeValue.getString("bar");
}
@Override
public org.bson.@Nullable Document write(@Nullable String domainValue, MongoConversionContext context) {
return new org.bson.Document("bar", domainValue);
@ -3408,7 +3518,7 @@ class MappingMongoConverterUnitTests { @@ -3408,7 +3518,7 @@ class MappingMongoConverterUnitTests {
}
private MappingMongoConverter createConverter(
MongoCustomConversions.BigDecimalRepresentation bigDecimalRepresentation) {
MongoCustomConversions.BigDecimalRepresentation... bigDecimalRepresentation) {
MongoCustomConversions conversions = MongoCustomConversions.create(
it -> it.registerConverter(new ByteBufferToDoubleHolderConverter()).bigDecimal(bigDecimalRepresentation));
@ -4081,7 +4191,11 @@ class MappingMongoConverterUnitTests { @@ -4081,7 +4191,11 @@ class MappingMongoConverterUnitTests {
@Field(targetType = FieldType.DECIMAL128) //
BigDecimal bigDecimal;
@Field(targetType = FieldType.DECIMAL128) BigInteger bigInteger;
@Field(targetType = FieldType.DECIMAL128) //
BigInteger bigInteger;
@Field(targetType = FieldType.STRING) //
BigInteger bigIntegerAsString;
@Field(targetType = FieldType.INT64) //
Date dateAsLong;
@ -4211,7 +4325,6 @@ class MappingMongoConverterUnitTests { @@ -4211,7 +4325,6 @@ class MappingMongoConverterUnitTests {
@WritingConverter
static class TypeImplementingMapToDocumentConverter implements Converter<TypeImplementingMap, org.bson.Document> {
@Override
public org.bson.@Nullable Document convert(TypeImplementingMap source) {
return new org.bson.Document("1st", source.val1).append("2nd", source.val2);
@ -4413,7 +4526,6 @@ class MappingMongoConverterUnitTests { @@ -4413,7 +4526,6 @@ class MappingMongoConverterUnitTests {
return value.getString("bar");
}
@Override
public org.bson.@Nullable Document write(@Nullable String value, MongoConversionContext context) {
return new org.bson.Document("bar", value);
@ -4427,7 +4539,6 @@ class MappingMongoConverterUnitTests { @@ -4427,7 +4539,6 @@ class MappingMongoConverterUnitTests {
return value.getString("foo");
}
@Override
public org.bson.@Nullable Document write(@Nullable String value, MongoConversionContext context) {
return new org.bson.Document("foo", value);

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

@ -55,6 +55,7 @@ import org.springframework.data.mongodb.core.aggregation.EvaluationOperators; @@ -55,6 +55,7 @@ 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.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.geo.GeoJsonPolygon;
import org.springframework.data.mongodb.core.mapping.DBRef;
@ -97,7 +98,7 @@ public class QueryMapperUnitTests { @@ -97,7 +98,7 @@ public class QueryMapperUnitTests {
@BeforeEach
void beforeEach() {
MongoCustomConversions conversions = new MongoCustomConversions();
MongoCustomConversions conversions = new MongoCustomConversions(new MongoConverterConfigurationAdapter().bigDecimal(BigDecimalRepresentation.DECIMAL128));
this.context = new MongoMappingContext();
this.context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
@ -152,6 +153,25 @@ public class QueryMapperUnitTests { @@ -152,6 +153,25 @@ public class QueryMapperUnitTests {
assertThat(result).containsEntry("_id", Decimal128.parse("1"));
}
@Test // GH-5037
void leavesBigIntegerAsIsIfNotConfigured() {
MongoCustomConversions conversions = new MongoCustomConversions();
context = new MongoMappingContext();
context.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, context);
converter.setCustomConversions(conversions);
converter.afterPropertiesSet();
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", new BigInteger("1"));
}
@Test
void handlesObjectIdCapableBigIntegerIdsCorrectly() {

6
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/MongoConverterConfigurer.java

@ -16,10 +16,12 @@ @@ -16,10 +16,12 @@
package org.springframework.data.mongodb.test.util;
import java.util.Arrays;
import java.util.function.Consumer;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
/**
* Utility to configure {@link MongoCustomConversions}.
@ -37,4 +39,8 @@ public class MongoConverterConfigurer { @@ -37,4 +39,8 @@ public class MongoConverterConfigurer {
public void customConverters(Converter<?, ?>... converters) {
customConversions(new MongoCustomConversions(Arrays.asList(converters)));
}
public void customConverters(Consumer<MongoConverterConfigurationAdapter> configurer) {
customConversions(MongoCustomConversions.create(configurer));
}
}

1
src/main/antora/modules/ROOT/nav.adoc

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
** xref:migration-guides.adoc[]
*** xref:migration-guide/migration-guide-2.x-to-3.x.adoc[]
*** xref:migration-guide/migration-guide-3.x-to-4.x.adoc[]
*** xref:migration-guide/migration-guide-4.x-to-5.x.adoc[]
* xref:mongodb.adoc[]
** xref:preface.adoc[]

60
src/main/antora/modules/ROOT/pages/migration-guide/migration-guide-4.x-to-5.x.adoc

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
[[mongodb.migration.4.x-5.x]]
= Migration Guide from 4.x to 5.x
Spring Data MongoDB 4.x requires the MongoDB Java Driver 5.5.x +
To learn more about driver versions please visit the https://www.mongodb.com/docs/drivers/java/sync/current/upgrade/[MongoDB Documentation].
== UUID Representation Changes
Spring Data no longer defaults UUID settings via its configuration support classes, factory beans, nor XML namespace. +
In order to persist UUID values the `UuidRepresentation` hast to be set explicitly.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@Configuration
static class Config extends AbstractMongoClientConfiguration {
@Override
protected void configureClientSettings(MongoClientSettings.Builder builder) {
builder.uuidRepresentation(UuidRepresentation.STANDARD);
}
// ...
}
----
XML::
+
[source,xml,indent=0,subs="verbatim,quotes",role="secondary"]
----
<mongo:mongo-client>
<mongo:client-settings uuid-representation="STANDARD"/>
</mongo:mongo-client>
----
======
== BigInteger/BigDecimal Conversion Changes
Spring Data no longer defaults BigInteger/BigDecimal conversion via its configuration support classes.
In order to persist those values the default `BigDecimalRepresentation` hast to be set explicitly.
[source,java]
----
@Configuration
static class Config extends AbstractMongoClientConfiguration {
@Override
protected void configureConverters(MongoConverterConfigurationAdapter configAdapter) {
configAdapter.bigDecimal(BigDecimalRepresentation.DECIMAL128);
}
// ...
}
----
Users upgrading from prior versions may choose `BigDecimalRepresentation.STRING` as default.
Those using`@Field(targetType = FieldType.DECIMAL128)` need to define a combination of representations `configAdapter.bigDecimal(BigDecimalRepresentation.STRING, BigDecimalRepresentation.DECIMAL128)` to set defaulting to String while having the `DECIMAL128` converter being registered for usage with explicit target type configuration.

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

@ -4,7 +4,7 @@ include::{commons}@data-commons::page$custom-conversions.adoc[] @@ -4,7 +4,7 @@ include::{commons}@data-commons::page$custom-conversions.adoc[]
== Type based Converter
The most trivial way of influencing the mapping result is by specifying the desired native MongoDB target type via the `@Field` annotation.
This allows to work with non MongoDB types like `BigDecimal` in the domain model while persisting values in native `org.bson.types.Decimal128` format.
This allows to work with non MongoDB types like `BigDecimal` in the domain model while persisting values in eg. `String` format.
.Explicit target type mapping
====
@ -33,8 +33,7 @@ public class Payment { @@ -33,8 +33,7 @@ public class Payment {
<1> String _id_ values that represent a valid `ObjectId` are converted automatically. See xref:mongodb/template-crud-operations.adoc#mongo-template.id-handling[How the `_id` Field is Handled in the Mapping Layer]
for details.
<2> The desired target type is explicitly defined as `String`.
Otherwise, the
`BigDecimal` value would have been turned into a `Decimal128`.
Otherwise.
<3> `Date` values are handled by the MongoDB driver itself are stored as `ISODate`.
====
@ -113,8 +112,10 @@ To persist `BigDecimal` and `BigInteger` values, Spring Data MongoDB converted v @@ -113,8 +112,10 @@ To persist `BigDecimal` and `BigInteger` values, Spring Data MongoDB converted v
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`.
As of Spring Data MongoDB 5.0. the default representation of those types moved to MongoDB native `org.bson.types.Decimal128`.
You can still use the to the previous `String` variant by configuring the big decimal representation in `MongoCustomConversions` through `MongoCustomConversions.create(config -> config.bigDecimal(BigDecimalRepresentation.STRING))`.
As of Spring Data MongoDB 5.0. there no longer is a default representation of those types and conversion needs to be configured explicitly.
You can register multiple formats, 1st being default, and still retain the previous behaviour by configuring the `BigDecimalRepresentation` in `MongoCustomConversions` through `MongoCustomConversions.create(config -> config.bigDecimal(BigDecimalRepresentation.STRING, BigDecimalRepresentation.DECIMAL128))`.
This allows you to make use of the explicit storage type format via `@Field(targetType = DECIMAL128)` while keeping default conversion set to String.
Choosing none of the provided representations is valid as long as those values are no persisted.
[NOTE]
====

Loading…
Cancel
Save