diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoId.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoId.java
new file mode 100644
index 000000000..97749f05f
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/MongoId.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+import org.springframework.data.annotation.Id;
+
+/**
+ * {@link MongoId} represents a MongoDB specific {@link Id} annotation that allows tweaking {@literal id} conversion. By
+ * default {@link Object Class<Object>} will be used as the {@literal id's} target type. This means that the
+ * actual property value is used. No conversion attempts to any other type is made.
+ * In contrast to {@link Id @Id}, {@link String} {@literal id's} are stored as the such even when the actual value
+ * represents a valid {@link org.bson.types.ObjectId#isValid(String) ObjectId hex String}. To trigger {@link String} to
+ * {@link org.bson.types.ObjectId} conversion use {@link MongoId#targetType() @MongoId(ObjectId.class)}.
+ *
+ * @author Christoph Strobl
+ * @since 2.2
+ */
+@Id
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
+public @interface MongoId {
+
+ /**
+ * @return the preferred id type.
+ * @see #targetType()
+ */
+ @AliasFor("targetType")
+ Class> value() default Object.class;
+
+ /**
+ * Get the preferred {@literal _id} type to be used. Defaulted to {@link Object Class<Object>} which used the
+ * property's type. If defined different, the given value is attempted to be converted into the desired target type
+ * via {@link org.springframework.data.mongodb.core.convert.MongoConverter#convertId(Object, Class)}.
+ *
+ * @return the preferred {@literal id} type. {@link Object Class<Object>} by default.
+ */
+ @AliasFor("value")
+ Class> targetType() default Object.class;
+
+}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
index 9b8ce5563..024ef0b4b 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java
@@ -20,6 +20,7 @@ import java.util.Map.Entry;
import org.bson.Document;
import org.bson.conversions.Bson;
+import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@@ -516,7 +517,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (idProperty != null && !dbObjectAccessor.hasValue(idProperty)) {
- Object value = idMapper.convertId(accessor.getProperty(idProperty));
+ Object value = idMapper.convertId(accessor.getProperty(idProperty), idProperty.getIdType());
if (value != null) {
dbObjectAccessor.put(idProperty, value);
@@ -981,7 +982,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
throw new MappingException("Cannot create a reference to an object with a NULL id.");
}
- return dbRefResolver.createDbRef(property == null ? null : property.getDBRef(), entity, idMapper.convertId(id));
+ return dbRefResolver.createDbRef(property == null ? null : property.getDBRef(), entity, idMapper.convertId(id, idProperty != null ? idProperty.getIdType() : ObjectId.class));
}
throw new MappingException("No id property found on class " + entity.getType());
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java
index c007193b3..38fe3fbc6 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MongoConverter.java
@@ -18,6 +18,8 @@ package org.springframework.data.mongodb.core.convert;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
+import org.bson.types.ObjectId;
+import org.springframework.core.convert.ConversionException;
import org.springframework.data.convert.EntityConverter;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.convert.TypeMapper;
@@ -83,7 +85,18 @@ public interface MongoConverter
if (sourceDocument.containsKey("$ref") && sourceDocument.containsKey("$id")) {
- sourceDocument = dbRefResolver.fetch(new DBRef(sourceDocument.getString("$ref"), sourceDocument.get("$id")));
+ Object id = sourceDocument.get("$id");
+ String collection = sourceDocument.getString("$ref");
+
+ MongoPersistentEntity> entity = getMappingContext().getPersistentEntity(targetType);
+ if (entity.getIdType() != null) {
+ id = convertId(id, entity.getIdType());
+ }
+
+ DBRef ref = sourceDocument.containsKey("$db") ? new DBRef(sourceDocument.getString("$db"), collection, id)
+ : new DBRef(collection, id);
+
+ sourceDocument = dbRefResolver.fetch(ref);
if (sourceDocument == null) {
return null;
}
@@ -102,4 +115,36 @@ public interface MongoConverter
}
return getConversionService().convert(source, targetType);
}
+
+ /**
+ * Converts the given raw id value into either {@link ObjectId} or {@link String}.
+ *
+ * @param id
+ * @return {@literal null} if source {@literal id} is already {@literal null}.
+ * @since 2.2
+ */
+ @Nullable
+ default Object convertId(@Nullable Object id, Class> targetType) {
+
+ if (id == null) {
+ return null;
+ }
+
+ if (ClassUtils.isAssignable(ObjectId.class, targetType)) {
+
+ if (id instanceof String) {
+
+ if (ObjectId.isValid(id.toString())) {
+ return new ObjectId(id.toString());
+ }
+ }
+ }
+
+ try {
+ return getConversionService().canConvert(id.getClass(), targetType)
+ ? getConversionService().convert(id, targetType) : convertToMongoType(id, null);
+ } catch (ConversionException o_O) {
+ return convertToMongoType(id, null);
+ }
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
index 1b89e8a95..00687fa9d 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
@@ -28,7 +28,6 @@ import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
-import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.domain.Example;
@@ -50,6 +49,7 @@ import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
@@ -322,11 +322,11 @@ public class QueryMapper {
String inKey = valueDbo.containsField("$in") ? "$in" : "$nin";
List