MongoDB Driver:Case Object编解码器问题及ADT属性持久化故障
解决MongoDB持久化带ADT(密封特质+Case Object)的Case类问题
看来你在尝试持久化包含Status这种ADT属性的Operation case类时踩了坑——官方默认的Codec只支持case类,case object完全不生效,自己写的自定义Codec也没起作用对吧?我来给你几个靠谱的解决办法:
方案一:正确实现并注册自定义Codec
你的自定义Codec思路是对的,但大概率是没把它正确注册到MongoDB驱动的CodecRegistry里,导致驱动还是用默认逻辑处理Status类型。先完善你的Codec实现,再正确注册:
1. 完善StatusCodec
import org.bson.codecs.{Codec, DecoderContext, EncoderContext} import org.bson.{BsonReader, BsonWriter} class StatusCodec extends Codec[Status] { // 编码:把case object转为对应的字符串 override def encode(writer: BsonWriter, value: Status, encoderContext: EncoderContext): Unit = { value match { case Ok => writer.writeString("Ok") case Fail => writer.writeString("Fail") } } // 解码:从字符串转回对应的case object override def decode(reader: BsonReader, decoderContext: DecoderContext): Status = { reader.readString() match { case "Ok" => Ok case "Fail" => Fail case unknown => throw new IllegalArgumentException(s"无法识别的状态值:$unknown") } } override def getEncoderClass: Class[Status] = classOf[Status] }
2. 注册Codec到CodecRegistry
关键是要把自定义的StatusCodec和Operation的自动生成Codec结合起来,并且保证自定义Codec的优先级更高:
import org.mongodb.scala.MongoClient import org.mongodb.scala.bson.codecs.Macros._ import org.bson.codecs.configuration.CodecRegistries.{fromCodecs, fromProviders, fromRegistries} // 创建自定义Codec实例 val statusCodec = new StatusCodec() // 生成Operation的自动Codec Provider val operationCodecProvider = provide[Operation] // 组合CodecRegistry:自定义Codec在前,默认Registry在后 val codecRegistry = fromRegistries( fromCodecs(statusCodec), fromProviders(operationCodecProvider), MongoClient.DEFAULT_CODEC_REGISTRY ) // 使用带自定义Registry的数据库和集合 val mongoClient = MongoClient() val db = mongoClient.getDatabase("your_db_name").withCodecRegistry(codecRegistry) val opsCollection = db.getCollection[Operation]("operations")
这样驱动在处理Operation的status字段时,就会优先用你的StatusCodec来序列化/反序列化case object了。
方案二:用Scala枚举替代ADT(简单场景首选)
如果你的Status只有几个固定的单例状态,完全可以用Scala的枚举来替代密封特质+case object,MongoDB驱动对枚举有原生支持,不需要写任何自定义Codec:
// 定义枚举 object Status extends Enumeration { type Status = Value val Ok, Fail = Value } // 调整Operation类的属性类型 case class Operation(_id: String, status: Status.Status)
这样驱动会自动把枚举值序列化为对应的字符串,读取时也能自动转回来,省心又高效。
为什么之前的自定义Codec失效?
大概率是这两个原因:
- 没有把
StatusCodec注册到CodecRegistry里,驱动还是用默认的编码器处理Status类型,而默认编码器无法识别case object; - CodecRegistry的顺序不对,默认Registry的优先级比自定义Codec高,导致驱动没用到你的实现。
内容的提问来源于stack exchange,提问作者Oleksandr
相关产品推荐
相关产品推荐

