You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 14:27:52