对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。
Docs 菜单

使用类型编解码器对数据进行编码

在本指南中,您可以了解关于 编解码器 和支持类,它们在 Scala 驱动程序中处理 Scala 对象与 BSON 数据之间的编码和解码。Codec 抽象允许您将任何 Scala 类型映射到对应的 BSON 类型。使用此将域对象直接映射到 BSON,或从 BSON 直接映射到域对象,而不依赖于 DocumentBsonDocument 等基于中间映射的对象。

Codec 接口包含用于将 Scala 对象编码和解码为 BSON 数据的抽象方法。实现这些方法以定义 BSON 与 Codec 实现的 Scala 类型之间的转换逻辑。

要实现 Codec 接口,请定义该接口的 encode()decode()getEncoderClass() 方法。要查看实现这些方法的代码示例,请参阅“基本自定义编解码示例”部分。

encode() 方法将 Scala 类型的实例编码为 BSON,以便驱动程序将其存储在 MongoDB 中。此方法需要以下参数:

Parameter Type
说明

writer

实现 BsonWriter 的类的实例,该接口公开用于编写 BSON 文档的方法。使用此实例通过为 BSON 值类型使用适当的写入方法来写入 BSON 值。

value

该方法编码的数据。value 类型必须与分配给 Codec 实现的类型参数匹配。

encoderContext

关于该方法编码为 BSON 的 Scala 对象的元数据,包括是否将当前值存储在 MongoDB 集合中。

encode() 方法不返回值。

decode() 方法使用 BSON 数据解码 Scala 类型的实例。此方法需要以下参数:

Parameter Type
说明

bsonReader

实现 BsonReader 的类的实例,该接口公开用于读取 BSON 文档的方法。

decoderContext

关于 BSON 数据的元数据,该方法将其解码为 Scala 对象。

getEncoderClass() 方法返回 Codec 中定义的 Scala 类型的实例。此方法提供了 Java 虚拟机 (JVM) 在运行时消除的类型信息。

以下代码示例使用 PowerStatus 密封特征和 PowerStatusCodec 类来显示如何实现自定义 Codec

PowerStatus 密封特征使用大小写对象 OnOff 来表示电气开关的状态:

sealed trait PowerStatus
object PowerStatus {
case object On extends PowerStatus
case object Off extends PowerStatus
}

PowerStatusCodec 类实现 Codec 接口,以将 PowerStatus 值编码为对应的 BSON 布尔值。encode() 方法将 PowerStatus 值编码为 BSON 布尔值,decode() 方法将 BSON 布尔值解码为 PowerStatus 值。

class PowerStatusCodec extends Codec[PowerStatus] {
override def encode(writer: BsonWriter, value: PowerStatus,
encoderContext: EncoderContext): Unit = {
if (value != null) {
writer.writeBoolean(value == PowerStatus.On)
}
}
override def decode(reader: BsonReader,
decoderContext: DecoderContext): PowerStatus = {
if (reader.readBoolean()) PowerStatus.On else PowerStatus.Off
}
override def getEncoderClass: Class[PowerStatus] = classOf[PowerStatus]
}

要使用 PowerStatusCodec 类,必须将该类的实例添加到 CodecRegistry 接口,该接口将 Codec 映射到对应的 Scala 类型。仅当 CodecRegistry 包含该类型的 Codec 时,驱动程序才能编码和解码类型。要了解如何在 CodecRegistry 接口中包含自定义 Codec,请参阅本页的 CodecRegistry 部分。

有关此部分中的类和接口的更多信息,请参阅以下 API 文档:

CodecRegistryCodec 实例的不可变集合。要构建 CodecRegistry 实例,请使用以下 CodecRegistries 类静态工厂方法之一。每种方法都从不同的 Codec 实例源构建注册表:

方法
说明

fromCodecs()

从传递给方法的 Codec 实例构建注册表

fromProviders()

从传递给该方法的 CodecProvider 实例提供的 Codec 实例构建注册表

fromRegistries()

通过组合传递给该方法的其他 CodecRegistry 实例来构建注册表

以下示例使用两个 Codec 实现:

  • IntegerCodec: BSON 包中的 Codec,可将 Java Integer 值编码为 BSON 32位整数值。

  • PowerStatusCodec:将 PowerStatus 值解码为 BSON 布尔值的示例 Codec

以下示例显示如何使用 fromCodecs() 方法构建 CodecRegistry 实例,以将这些实现分配给注册表:

val codecRegistry = CodecRegistries.fromCodecs(
new IntegerCodec(), new PowerStatusCodec()
)

以下示例从 CodecRegistry 集合中检索上一示例中的 Codec 实例:

val powerStatusCodec: Codec[PowerStatus] =
codecRegistry.get(classOf[PowerStatus])
val integerCodec: Codec[Integer] =
codecRegistry.get(classOf[Integer])

注意

如果对未注册的类的 Codec 实例调用 get() 方法,驱动程序将抛出 CodecConfigurationException

默认编解码器注册表是一组 CodecProvider 类,用于在常用 Scala 和 MongoDB 类型之间进行编码。除非您指定自定义编解码器注册表,否则驱动程序会自动使用默认编解码器注册表。要了解有关 CodecProvider 接口的更多信息,请参阅 CodecProvider 部分。

如果您必须覆盖一个或多个 Codec 类的行为,但又想保留其他类的默认编解码器注册表中的行为,则可以按优先顺序指定所有注册表。例如,要使用自定义 MyEnumCodec 覆盖特定类型的 Codec 的默认提供商行为,请在默认编解码器注册表之前将其添加到注册表中。以下示例展示了此模式:

val newRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(new MyEnumCodec()),
MongoClientSettings.getDefaultCodecRegistry()
)

有关此部分中的类和接口的更多信息,请参阅以下 API 文档:

CodecProvider 接口包含创建 Codec 实例并将其分配给 CodecRegistry 实例的抽象方法。与 CodecRegistry 接口类似,CodecProvider 接口定义了返回 Codec 实例的 get() 方法。BSON 库使用这些 Codec 实例在 Scala 和 BSON 数据类型之间进行编码。

在将类添加到代码中时,如果其字段需要对应的 Codec 对象,请使用 CodecProvider。当每个字段都需要其自己的 Codec 实例时,必须在为该类实例化 Codec 实例之前,先实例化每个字段的 Codec 对象。使用 get() 方法中的 CodecRegistry 参数,将 Codec 依赖的任何 Codec 实例传递到构造函数中。

以下示例展示如何实现 CodecProvider 接口。驱动程序使用已实现的 MonolightCodecProvider 接口为 Monolight 类创建 MonolightCodec 实例。

class MonolightCodecProvider extends CodecProvider {
override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = {
if (clazz == classOf[Monolight]) {
new MonolightCodec(registry).asInstanceOf[Codec[T]]
} else {
null
}
}
}

有关 CodecProvider 接口的完整实现(包括自定义类),请参阅本指南的完整自定义编解码器示例一节。

有关此部分中的类和接口的更多信息,请参阅以下 API 文档:

在本节中,您可以学习如何实现 CodecCodecProvider 接口来定义自定义 Scala 类的编码和解码逻辑。本节还展示了如何指定和使用自定义实现来执行插入和检索操作。

以下代码片段显示了示例自定义类 Monolight 及其字段:

case class Monolight(
powerStatus: PowerStatus = PowerStatus.Off,
colorTemperature: Int = 0
)

Monolight 类包含以下字段,每个字段都需要 Codec 接口实现:

  • powerStatus 描述灯是开启还是关闭。PowerStatusCodec 类将 PowerStatus 值编码为 BSON 布尔值。

  • colorTemperature 描述灯光的颜色并包含 Int 值。BSON 库中包含的 IntegerCodec 类将 colorTemperature 值编码为 BSON 32位整数。

以下代码示例展示了如何为 Monolight 类实现 Codec 接口。构造函数使用 CodecRegistry 实例来检索编码和解码 Monolight 字段所需的 Codec 实例。

class MonolightCodec(registry: CodecRegistry) extends Codec[Monolight] {
private val powerStatusCodec: Codec[PowerStatus] =
registry.get(classOf[PowerStatus])
private val integerCodec: Codec[Integer] =
registry.get(classOf[Integer])
override def encode(writer: BsonWriter, value: Monolight,
encoderContext: EncoderContext): Unit = {
writer.writeStartDocument()
writer.writeName("powerStatus")
powerStatusCodec.encode(writer, value.powerStatus, encoderContext)
writer.writeName("colorTemperature")
integerCodec.encode(writer, value.colorTemperature, encoderContext)
writer.writeEndDocument()
}
override def decode(reader: BsonReader,
decoderContext: DecoderContext): Monolight = {
var powerStatus: PowerStatus = PowerStatus.Off
var colorTemperature: Int = 0
reader.readStartDocument()
while (reader.readBsonType() != BsonType.END_OF_DOCUMENT) {
reader.readName() match {
case "powerStatus" =>
powerStatus = powerStatusCodec.decode(reader, decoderContext)
case "colorTemperature" =>
colorTemperature = integerCodec.decode(reader, decoderContext)
case "_id" =>
reader.readObjectId()
case _ =>
reader.skipValue()
}
}
reader.readEndDocument()
Monolight(powerStatus, colorTemperature)
}
override def getEncoderClass: Class[Monolight] = classOf[Monolight]
}

以下代码示例展示了如何为 Monolight 类中的字段构建 Codec 实例并实现自定义 CodecProvider

class MonolightCodecProvider extends CodecProvider {
override def get[T](clazz: Class[T], registry: CodecRegistry): Codec[T] = {
if (clazz == classOf[Monolight]) {
new MonolightCodec(registry).asInstanceOf[Codec[T]]
} else {
null
}
}
}

当驱动程序请求 Monolight 类的 Codec 时,get() 方法返回一个新的 MonolightCodec。该方法将 CodecRegistry 传递给 MonolightCodec 构造函数,以便 MonolightCodec 可以检索其字段的 Codec 实例,例如 PowerStatusCodecIntegerCodec。如果驱动程序请求任何其他类的 Codec,则该方法返回 null

定义转换逻辑后,您可以执行以下操作:

  • Monolight 类实例的数据存储到 MongoDB 中

  • 从 MongoDB 检索数据到 Monolight 类的实例中

以下示例通过将 MonolightCodecProvider 类传递给 withCodecRegistry() 方法,将其赋值给 MongoCollection 实例。然后,该示例通过调用 insertOne() 方法,将新的 Monolight 实例插入到集合中,然后调用 find() 方法返回存储的 Monolight 实例。输出显示检索到的 Monolight 实例,这证实了自定义编解码器成功编码和解码了数据。

object MonolightCodecExample {
def main(args: Array[String]): Unit = {
val uri = "<connection string URI>"
val mongoClient = MongoClient(uri)
val codecRegistry = CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(
new IntegerCodec(), new PowerStatusCodec()
),
CodecRegistries.fromProviders(new MonolightCodecProvider()),
MongoClientSettings.getDefaultCodecRegistry()
)
val database = mongoClient.getDatabase("codecs_example_products")
val collection: MongoCollection[Monolight] = database
.getCollection[Monolight]("monolights")
.withCodecRegistry(codecRegistry)
val myMonolight = Monolight(PowerStatus.On, 5200)
Await.result(
collection.insertOne(myMonolight).toFuture(),
Duration.Inf
)
val lights = Await.result(
collection.find().toFuture(),
Duration.Inf
)
println(lights)
mongoClient.close()
}
}
List(Monolight(On,5200))

有关本节中提到的方法和类的详情,请参阅以下 API 文档: