Morphia 2.3映射异常:Map键返回String而非Locale类型
Morphia 2.3.2中Map<Locale, T>键无法从String映射回Locale的问题解析
问题背景
将Morphia从1.3.2升级到2.3.2后,实体类中定义的Map<Locale, MessageTemplate>字段出现类型映射异常:保存到Mongo时键会被转为String,但查询返回时无法自动映射回Locale类型,仍保留为String。临时修复代码可解决问题,但配置的LocaleCodec并未生效,需明确原因及正确解决方式。
相关代码片段
实体类字段
private Map<Locale, MessageTemplate> messageTemplates; public Map<Locale, MessageTemplate> getMessageTemplates() { if(messageTemplates == null){ this.messageTemplates = new HashMap<>(); } return messageTemplates; } public void setMessageTemplates(Map<Locale, MessageTemplate> messageTemplates) { this.messageTemplates = messageTemplates; }
Mongo集合中的数据
{ "_id" : "55c8a25e-137c-44f8-8e75-18617aafd374", "className" : "com.swipejobs.desktopnotifications.model.NotificationTypeDetails", "messageTemplates" : { "en" : { "subject" : "", "body" : "Your verification PIN is ${pincode}. Please do not reply to this message.", "emailSubject" : "", "emailBody" : "" } } }
当前MongoClient配置
import com.swipejobs.dataaccess.codecs.ZoneIdCodec; import com.swipejobs.dataaccess.codecs.ZonedDateTimeCodec; import dev.morphia.Datastore; import dev.morphia.mapping.codec.LocaleCodec; CodecRegistry codecRegistry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs(new ZonedDateTimeCodec(), new ZoneIdCodec(), new LocaleCodec()), MongoClientSettings.getDefaultCodecRegistry(), CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build()) ); ConnectionString connectionString = new ConnectionString(uri); MongoClientSettings settings = MongoClientSettings.builder() .codecRegistry(codecRegistry) .applyConnectionString(connectionString) .build(); MongoClient mongoClient = MongoClients.create(settings); MapperOptions options = MapperOptions.builder() .discriminator(DiscriminatorFunction.className()) .discriminatorKey("className") .collectionNaming(NamingStrategy.identity()) .build(); mDataStore = Morphia.createDatastore(mMongoClient, mDbName, options); mDataStore.ensureIndexes(); datastore.getMapper().map(NotificationTypeDetails.class);
临时修复代码
private Map<Locale, MessageTemplate> fixMap(Map<Locale, MessageTemplate> map){ return map.entrySet().stream() .map(this::fixEntry) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } private Map.Entry<Locale, MessageTemplate> fixEntry(Map.Entry<Locale, MessageTemplate> entry) { Locale key = fixKey(entry.getKey()); MessageTemplate value = entry.getValue(); return Map.of(key, value).entrySet().stream().findFirst().get(); } private Locale fixKey(Object key){ if (key.getClass().equals(Locale.class)) return (Locale) key; if (key.getClass().equals(String.class)) return new Locale((String) key); return Locale.ENGLISH; }
问题原因
你配置的LocaleCodec是MongoDB底层的Codec,仅负责处理作为BSON值的Locale类型(比如直接定义的private Locale locale;字段)。但MongoDB的BSON文档键只能是String类型,Map键的类型转换由Morphia的TypeConverter负责,而非Mongo的Codec。因此LocaleCodec无法作用于Map键的转换,导致查询时String键无法自动转为Locale。
Morphia 2.x默认未为Locale类型的Map键注册对应转换器,需手动配置。
正确解决方案
方案1:注册Locale的TypeConverter到Morphia Mapper
先自定义Locale转换器,再注册到Morphia的Mapper中:
// 自定义Locale转换器,处理String与Locale的互转 public class LocaleConverter extends TypeConverter { public LocaleConverter() { super(Locale.class); } @Override public Object decode(Class<?> targetClass, Object fromObject, MappingContext mappingContext) { if (fromObject instanceof String) { return new Locale((String) fromObject); } return super.decode(targetClass, fromObject, mappingContext); } @Override public Object encode(Object value, MappingContext mappingContext) { if (value instanceof Locale) { return ((Locale) value).getLanguage(); } return super.encode(value, mappingContext); } } // 在初始化Datastore后注册转换器 datastore.getMapper().getConverters().addConverter(new LocaleConverter());
方案2:使用@MapKeyConverter注解指定转换器
直接在Map字段上添加注解,绑定自定义转换器:
import dev.morphia.annotations.MapKeyConverter; @MapKeyConverter(LocaleConverter.class) private Map<Locale, MessageTemplate> messageTemplates;
两种方案均可让Morphia自动完成Map键的String与Locale互转,无需临时修复代码。
验证
配置完成后重新查询文档,messageTemplates的键会自动转为Locale类型,无需手动处理。
内容的提问来源于stack exchange,提问作者Greg Newitt
相关产品推荐
相关产品推荐

