MongoDB推荐使用ObjectId作为主键,但国内的开发都知道,事情往往不如人所愿,当我们真的出现了“_id”主键的类型为String时,且还必须想用mongoTemplate.findOne或findList时,直接使用该方法会导致查询结果为空。
因为mongoTemplate会在查询的时候将主键转换为ObjectId。
实际上这一步转换是MappingMongoConverter中做的,源码如下:
/**
* Converts the given raw id value into either {@link ObjectId} or {@link String}.
*
* @param id can be {@literal null}.
* @param targetType must not be {@literal null}.
* @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 || ClassUtils.isAssignableValue(targetType, id)) {
return id;
}
if (ClassUtils.isAssignable(ObjectId.class, targetType)) {
if (id instanceof String) {
if (ObjectId.isValid(id.toString())) {
return new ObjectId(id.toString());
}
// avoid ConversionException as convertToMongoType will return String anyways.
return id;
}
}
try {
return getConversionService().canConvert(id.getClass(), targetType)
? getConversionService().convert(id, targetType)
: convertToMongoType(id, (TypeInformation) null);
} catch (ConversionException o_O) {
return convertToMongoType(id,(TypeInformation) null);
}
}
注意示例版本为spring-data-mongodb:3.3.5,一些更古早的版本,如2.1.9,需要重写其他部分,如果有人需要请留言,我会抽时间补充,但鉴于之前的版本有比较重大的漏洞,所以推荐升级版本。
if (ObjectId.isValid(id.toString())) { return new ObjectId(id.toString()); }
这段就是mongoTemplate将String转换为ObjectId的步骤。
所以我们要做的就是重写这部分,并且替换掉原有的:
1、新建一个Converter
public class MyMongoConverter extends MappingMongoConverter {
public MyMongoConverter(DbRefResolver dbRefResolver, MappingContext, MongoPersistentProperty> mappingContext) {
super(dbRefResolver, mappingContext);
}
@Override
public Object convertId(@Nullable Object id, Class targetType) {
if (id == null || ClassUtils.isAssignableValue(targetType, id)) {
return id;
}
if (ClassUtils.isAssignable(ObjectId.class, targetType)) {
if (id instanceof String) {
return id;
}
}
try {
return getConversionService().canConvert(id.getClass(), targetType)
? getConversionService().convert(id, targetType)
: convertToMongoType(id, (TypeInformation) null);
} catch (ConversionException o_O) {
return convertToMongoType(id,(TypeInformation) null);
}
}
}
2、在构建MongoTemplate时用我们自己写的替换
MongoTemolate mongoTemplate = new MongoTemplate(mongoDbFactory(), getDefaultMongoConverter(mongoDbFactory()))
protected static MongoConverter getDefaultMongoConverter(MongoDatabaseFactory factory) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MongoCustomConversions conversions = new MongoCustomConversions(Collections.emptyList());
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
mappingContext.afterPropertiesSet();
MyMongoConverter converter = new MyMongoConverter(dbRefResolver, mappingContext);
converter.setCustomConversions(conversions);
converter.afterPropertiesSet();
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
return converter;
}
/**
* @return {@link MongoDatabaseFactory}
*/
protected MongoDatabaseFactory mongoDbFactory() {
ServerAddress serverAddress = new ServerAddress(host, port);
MongoClientSettings.Builder setting = MongoClientSettings.builder()
.applyToClusterSettings(builder -> builder.hosts(Collections.singletonList(serverAddress)))
.retryWrites(false);
if (ObjectUtil.isNotEmpty(username)) {
MongoCredential credential = MongoCredential.createCredential(username, authenticationDatabase, password.toCharArray());
setting.credential(credential);
}
MongoClient mongoClient = MongoClients.create(setting.build());
return new SimpleMongoClientDatabaseFactory(mongoClient, database);
}
host,port,username,authenticationDatabase,password,database
分别为MongoDB的地址,端口号,用户名,认证数据库,密码,数据库名