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*/
@Nullabledefault 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<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {super(dbRefResolver, mappingContext);}@Overridepublic 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的地址,端口號,用戶名,認證數據庫,密碼,數據庫名