文档菜单
文档首页
/ / /
Java 同步驱动器
/ /

POJO自定义

本页内容

  • 概述
  • 自定义PojoCodecProvider
  • ClassModel
  • PropertyModel
  • 约定
  • 注解
  • BsonExtraElements示例
  • BsonRepresentation错误示例
  • 区分器
  • 高级配置
  • 属性中的抽象或接口类型
  • 无参数构造函数的POJO
  • 序列化自定义

在本指南中,您可以了解如何在MongoDB Java驱动程序中定义BSON和POJO之间的自定义数据转换。在我们的指南POJOs中,我们展示了如何指定一个PojoCodecProvider,它包含提供如何转换一个或多个POJO类及其属性的数据指令的类。

我们展示了如何使用ClassModelPropertyModel类指定您的数据转换。您还可以从高级配置部分了解更多具体的定制化。

我们还展示了如何使用约定注解等辅助工具指定常见的序列化操作。

如果您想将多个POJO类序列化为同一集合中的文档,请参阅判别器部分。

如果您必须实现条件序列化或使用枚举、泛型、接口类型或抽象类型,请参阅高级配置部分。

如果您仅使用预定义行为在BSON和POJO之间转换数据,可以使用文档数据格式:POJOs指南中所示的PojoCodecProvider的“自动”设置。

本节展示了如何使用PojoCodecProvider指定您的数据转换逻辑和POJO类。该PojoCodecProviderCodecProvider接口的一个实现,指定了数据转换中使用的编解码器。在执行BSON和POJO之间的数据转换时使用此实现。

您可以使用 PojoCodecProvider 构造函数来创建一个实例。您也可以将方法链接到构建器以注册以下任何内容:

  • 单个 POJO 类

  • 包含 POJO 类的包名

  • 描述特定 POJO 类转换逻辑的 ClassModel 实例

以下示例展示了如何指定名为 "org.example.pojos" 的包中的 POJO,并将 PojoCodecProvider 添加到 CodecRegistry

import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static com.mongodb.MongoClientSettings.getDefaultCodecRegistry;
CodecProvider pojoCodecProvider = PojoCodecProvider.builder().register("org.example.pojos").build();
CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));
// Call withCodecRegistry(pojoCodecRegistry) on an instance of MongoClient, MongoDatabase, or MongoCollection

有关此类的更多信息,请参阅 PojoCodecProvider.Builder API 文档。

ClassModel 实例存储有关特定 POJO 类的数据转换信息。它包含一个 PropertyModel 实例列表,这些实例描述了 POJO 的属性字段,是否转换字段,以及可选的用于转换字段的 Codecs

ClassModel 包含以下字段

字段名称
描述
名称
ClassModel 关联的 POJO 类名。
InstanceCreatorFactory
包含一个新的实例工厂,用于创建 POJO 的新实例。默认情况下,它要求 POJO 有一个空构造函数。
PropertyModels
包含一个 PropertyModel 实例列表,指定如何将数据转换为 POJO 字段中的 BSON,以及从 BSON 转换回数据。
IdPropertyModelHolder
指定与文档 _id 字段对应的 POJO 字段。可选。
区分键
指定区分器字段的名称。可选。
有关区分器的更多信息,请参阅区分器部分。
区分器值
指定代表 POJO 类的查找值。可选。
有关区分器的更多信息,请参阅区分器部分。
区分器标志
指定是否序列化区分器,默认为关闭。可选。

有关此类的更多信息,请参阅ClassModel API 文档。

要实例化 ClassModel,请使用 ClassModel.builder() 方法并指定您的 POJO 类。构建器使用反射来创建所需的元数据。

ClassModel<Flower> classModel = ClassModel.builder(Flower.class).build();

PropertyModel 存储有关如何序列化和反序列化文档中特定字段的信息。

PropertyModel 包含以下信息

字段名称
描述
名称
指定模型中的属性名称。
读取名称
在序列化为 BSON 时用作键的属性的名称。
写入名称
在从 BSON 反序列化时用作键的属性的名称。
类型数据
包含一个 org.bson.codecs.pojo.TypeData 的实例,该实例描述了字段的 数据类型。
编解码器
指定用于编码或解码字段的编解码器。可选。
序列化检查器
确定是否根据检查器中指定的标准序列化值。
属性访问器
用于从 POJO 中访问属性值的方 法。
useDiscriminator
指定是否使用区分器。
有关区分器的更多信息,请参阅区分器部分。

要创建 PropertyModel,请使用 PropertyModelBuilder,您可以通过调用 PropertyModel.builder() 方法来实例化它。

有关此类的更多信息,请参阅PropertyModel.Builder API 文档。

《约定》接口包含修改《ClassModel》或《PropertyModel》行为的配置选项。您可以在调用《PojoCodecProvider.Builder.conventions()`》或《ClassModelBuilder.conventions()`》时指定《约定》。

注意

构建器按照顺序应用《约定》实例,这可以覆盖先前应用的行为。

您可以从《Conventions》类中以下静态字段访问在 BSON 库中定义的《约定》实例

字段名称
描述
ANNOTATION_CONVENTION
启用《org.bson.codecs.pojo.annotations》包中定义的注解。有关更多信息,请参阅《注解》部分。
CLASS_AND_PROPERTY_CONVENTION
为《ClassModel》和《PropertyModel》实例设置以下默认值
Discriminator key to _t
Discriminator value to the ClassModel simple type name
Id field to _id for each PropertyModel.
DEFAULT_CONVENTIONS
启用以下约定
- CLASS_AND_PROPERTY_CONVENTION
- ANNOTATION_CONVENTION
- OBJECT_ID_GENERATORS
NO_CONVENTIONS
提供空列表。
OBJECT_ID_GENERATORS
添加一个默认的 IdGenerator,为每个使用 ObjectId 值的 ClassModelid 属性中添加一个新的 ObjectId
SET_PRIVATE_FIELDS_CONVENTION
启用 ClassModel 使用反射设置私有字段,无需setter方法。
USE_GETTERS_FOR_SETTERS
如果不存在setter方法,启用使用getter方法作为 CollectionMap 字段的setter。

您可以使用以下方法之一指定约定:

要创建自定义约定,创建一个实现 Convention 接口并重写 apply() 方法的类,您可以在其中访问您的 ClassModelBuilder 实例。

您可以将注解应用于POJO类的getter和setter方法。这些注解配置了特定字段、方法或类的 ClassModelPropertyModel 行为。

以下注解来自 org.bson.codecs.pojo.annotations

注解名称
描述
BsonCreator
将公共构造函数或公共静态方法标记为类的实例创建者。您必须使用 BsonPropertyBsonId 注解来注释构造函数中的所有参数。
BsonDiscriminator
指定一个类使用判别器。您可以设置自定义的判别器键和值。
BsonRepresentation
指定在值与 POJO 属性不同时用于存储值的 BSON 类型。请参阅本页面上的BsonRepresentation 错误示例
BsonId
将属性标记为序列化为 _id 属性。
BsonIgnore
将属性标记为忽略。您可以配置是否序列化和/或反序列化属性。
BsonProperty
指定将 POJO 字段转换为 BSON 时使用的自定义文档字段名。您可以在字段中包含判别器以序列化嵌套在字段中的 POJO。

当将 @BsonProperty 应用于私有字段时,您还必须为此字段添加 getter 和 setter 方法以进行序列化和自定义字段名
BsonExtraElements
指定用于反序列化所有未映射到字段的元素的 POJO 字段。POJO 字段必须是以下类型之一
- Map<String, Object>

以下代码片段展示了名为 Product 的示例 POJO,它使用了前面几个注解。

import org.bson.BsonType;
import org.bson.codecs.pojo.annotations.BsonCreator;
import org.bson.codecs.pojo.annotations.BsonDiscriminator;
import org.bson.codecs.pojo.annotations.BsonId;
import org.bson.codecs.pojo.annotations.BsonIgnore;
import org.bson.codecs.pojo.annotations.BsonProperty;
import org.bson.codecs.pojo.annotations.BsonRepresentation;
@BsonDiscriminator(value="AnnotatedProduct", key="_cls")
public class Product {
@BsonProperty("modelName")
private String name;
@BsonId()
@BsonRepresentation(BsonType.OBJECT_ID)
private String serialNumber;
@BsonIgnore
private List<Product> relatedItems;
@BsonCreator
public Product(@BsonProperty("modelName") String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
// ...
}

提示

使用注解时,请记住在您的 ClassModelBuilderPojoCodecProvider.Builder 中指定 Conventions.ANNOTATION_CONVENTION。例如

ClassModel<Product> classModel = ClassModel.builder(Product.class).
conventions(Arrays.asList(Conventions.ANNOTATION_CONVENTION)).build();

示例 POJO 中的注解指定以下行为

  • 使用指定的判别键和值引用 POJO,并在写入操作中将 cls 字段(值为 "AnnotatedProduct")添加到 BSON 文档中

  • 在文档中,在 POJO 的 name 字段和值与 BSON 的 modelName 字段和值之间进行转换

  • 在 POJO 的 serialNumber 字段和值与 BSON 文档的 _id 字段和值之间进行转换

  • 在转换数据时省略 relatedItems 字段和值

  • 使用 Product(String name) 构造函数来实例化 POJO

@BsonExtraElements 注解允许您指定一个字段,从缺少相应 POJO 字段映射的 MongoDB 文档中反序列化数据。当您的应用程序需要处理部分定义的架构中的数据时,这非常有用。您可以使用此注解来访问与您的 POJO 字段不对应的任何字段中的数据。

考虑这样一种情况,即您使用上一示例中的 Product POJO 来存储和检索在线商店的数据。随着您为商店提供更多样化的产品,您会发现需要额外的字段来描述它们。您无需将每个附加字段映射到 POJO,而是可以从一个使用 @BsonExtraElements 注解的单个字段中访问它们,如下面的代码示例所示

public class Product {
@BsonProperty("modelName")
private String name;
@BsonId()
@BsonRepresentation(BsonType.OBJECT_ID)
private String serialNumber;
@BsonIgnore
private List<Product> relatedItems;
@BsonExtraElements
private Document additionalInfo;
// ...

假设有人向产品数据中添加了 dimensionsweight 附加字段,使得文档包含以下信息

{
"name": "MDB0123",
"serialNumber": "62e2...",
"dimensions": "3x4x5",
"weight": "256g"
}

使用 Product POJO 检索的先前文档包含以下数据

ProductWithBsonExtraElements [
name=MDB0123,
serialNumber=62eb...,
relatedItems=null,
additionalInfo=Document{{dimensions=3x4x5, weight=256g}}
]

《@BsonRepresentation》注解允许您将 POJO 类的字段存储为 MongoDB 数据库中的不同数据类型。页面“注解”部分的 Product POJO 代码示例使用 @BsonRepresentation 将数据库文档中的 String 值存储为 ObjectId 值。

然而,使用 @BsonRepresentation 注解在 StringObjectId 以外的数据类型之间转换会导致以下错误消息

Codec must implement RepresentationConfigurable to support BsonRepresentation

例如,以下代码向 Product POJO 添加了一个类型为 LongpurchaseDate 字段。此示例尝试使用 @BsonRepresentation 将数据库中的 Long 值表示为 DateTime

public class Product {
@BsonProperty("modelName")
private String name;
@BsonId()
@BsonRepresentation(BsonType.OBJECT_ID)
private String serialNumber;
@BsonRepresentation(BsonType.DATE_TIME)
private Long purchaseDate;
// ...
}

上述代码会导致错误。相反,您可以创建一个自定义 Codec 来将 purchaseDate 值从 Long 类型转换为 DateTime 类型

public class LongRepresentableCodec implements Codec<Long>, RepresentationConfigurable<Long> {
private final BsonType representation;
/**
* Constructs a LongRepresentableCodec with a Int64 representation.
*/
public LongRepresentableCodec() {
representation = BsonType.INT64;
}
private LongRepresentableCodec(final BsonType representation) {
this.representation = representation;
}
@Override
public BsonType getRepresentation() {
return representation;
}
@Override
public Codec<Long> withRepresentation(final BsonType representation) {
if (representation != BsonType.INT64 && representation != BsonType.DATE_TIME) {
throw new CodecConfigurationException(representation
+ " is not a supported representation for LongRepresentableCodec");
}
return new LongRepresentableCodec(representation);
}
@Override
public void encode(final BsonWriter writer, final Long value, final EncoderContext encoderContext) {
switch (representation) {
case INT64:
writer.writeInt64(value);
break;
case DATE_TIME:
writer.writeDateTime(value);
break;
default:
throw new BsonInvalidOperationException("Cannot encode a Long to a "
+ representation);
}
}
@Override
public Long decode(final BsonReader reader, final DecoderContext decoderContext) {
switch (representation) {
case INT64:
return reader.readInt64();
case DATE_TIME:
return reader.readDateTime();
default:
throw new CodecConfigurationException("Cannot decode " + representation
+ " to a Long");
}
}
@Override
public Class<Long> getEncoderClass() {
return Long.class;
}
}

然后,将 LongRepresentableCodec 的一个实例添加到您的 CodecRegistry 中,该实例包含您的 Codec 与它应用的 Java 对象类型之间的映射。有关如何将自定义 Codec 注册到 CodecRegistry 的说明,请参阅Codecs 指南。

判别器是一种属性,用于识别特定的文档模式。判别器键用于识别用于识别模式的文档字段。判别器值用于识别文档字段的默认值。

使用判别器来指示 CodecProvider 在反序列化到不同对象类时应使用哪个对象类。在将 POJO 序列化到 MongoDB 集合时,除非在 POJO 属性数据中另有指定,否则相关的编解码器会设置判别器键值字段。

您可以通过以下任一方式在 POJO 中设置和启用判别器

  • 使用 @BsonDiscriminator 注解来指定 POJO 类的判别器

  • 在与 POJO 类关联的 ClassModelBuilder 上调用 enableDiscriminator(true)

请参阅以下包含 @BsonDiscriminator 注解的示例 POJO 类以及包含判别器字段的示例文档

@BsonDiscriminator(value="AnonymousUser", key="_cls")
public class AnonymousUser {
// class code
}
@BsonDiscriminator(value="RegisteredUser", key="_cls")
public class RegisteredUser {
// class code
}

以下是在单个 MongoDB 集合中从先前的 POJOs 创建的示例文档

{ "_cls": "AnonymousUser", "_id": ObjectId("<Object ID>"), ... }
{ "_cls": "RegisteredUser", "_id": ObjectId("<Object ID>"), ... }

要序列化包含抽象类或接口类型属性的 POJO,必须在类型及其所有子类型或实现上指定判别器。

假设您定义了一个 POJO,其中一个字段引用了抽象类 User,如下所示

public class UserRecordPojo {
private User user;
// ...
}

如果User抽象类有子类FreeUser和SubscriberUser,您可以将您的POJO和抽象类按以下方式添加到您的CodecRegistry中:

ClassModel<UserRecordPojo> userRecordPojo = ClassModel.builder(UserRecordPojo.class).enableDiscriminator(true).build();
ClassModel<User> userModel = ClassModel.builder(User.class).enableDiscriminator(true).build();
ClassModel<FreeUser> freeUserModel = ClassModel.builder(FreeUser.class).enableDiscriminator(true).build();
ClassModel<SubscriberUser> subscriberUserModel = ClassModel.builder(SubscriberUser.class).enableDiscriminator(true).build();
PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder().register(userRecordPojo, userModel, freeUserModel, subscriberUserModel).build();
CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));

有关指定区分符的更多信息,请参阅本指南中关于区分符的部分。

POJO Codecs 默认调用空参数构造函数。要指定不同的构造函数,您必须在您的POJO中执行以下操作:

  • ANNOTATION_CONVENTION设置传递给ClassModelBuilder

  • 使用BsonCreator注解标识构造函数

有关设置ANNOTATION_CONVENTION的示例,请参阅ANNOTATION_CONVENTION示例。有关BsonCreator注解的示例,请参阅带有注解代码的POJO示例

默认情况下,ClassModelBuilder会尝试序列化POJO中的所有非null属性。如果一个属性的值为null,默认的PropertySerialization实现将跳过该字段。

您可以通过以下方式之一来自定义您的POJO序列化行为:

  • 使用@BsonIgnore注解始终跳过属性的序列化。请确保使用适当的约定启用注解。

  • 创建一个自定义类,该类覆盖了PropertySerialization接口中的shouldSerialize()方法。将您的自定义实现指定给PropertyModelBuilder,您可以从ClassModelBuilder访问它。

有关如何在POJO中使用@BsonIgnore注解的更多信息,请参阅本指南中关于注解的部分。

以下示例代码展示了如何通过实现PropertySerialization接口来自定义类,从而覆盖确定是否序列化字段的默认条件。

public class CourteousAgeSerialization implements PropertySerialization<Integer> {
@Override
public boolean shouldSerialize(Integer value) {
return (value < 30);
}
}

前面的类指定任何大于29的整数都不会被序列化,因此不会包含在MongoDB文档中。假设您将此自定义序列化行为应用于以下示例POJO:

public class BirthdayInvitation {
private String name;
private Integer age;
private LocalDateTime eventDateTime;
// ...
}

您可以通过以下代码将CourteousAgeSerialization实例添加到与age字段相关联的ClassModel属性中的PropertyModelBuilder,以指定自定义序列化:

ClassModelBuilder<BirthdayInvitation> classModel = ClassModel.builder(BirthdayInvitation.class);
((PropertyModelBuilder<Integer>) classModel.getProperty("age"))
.propertySerialization(new CourteousAgeSerialization());
PojoCodecProvider pojoCodecProvider = PojoCodecProvider.builder().register(classModel.build()).build();
CodecRegistry pojoCodecRegistry = fromRegistries(getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));

如果将包含大于29的值的POJO插入到age字段中,序列化的文档将省略该值。POJO声明和生成的文档如下所示:

// constructor with parameters for name, age, and eventDateTime, respectively
BirthdayInvitation invitation = new BirthdayInvitation(
"Galadriel",
7582,
LocalDateTime.of(2021, Month.JANUARY, 18, 30, 0)
);

由于age字段值大于29,序列化的文档如下所示:

{ "_id" : ObjectId("..."), "eventDateTime" : ..., "name" : "Galadriel" }

如果类包含泛型属性且满足以下条件,则可以使用POJO Codec进行序列化:

  • 仅包含有界具体类型参数

  • 如果它或其任何字段是类层次结构的一部分,则顶级POJO不包含任何类型参数

ClassModelBuilder会检查并保存具体类型参数以解决类型擦除问题。由于JVM移除了类型参数信息,因此它无法序列化包含泛型属性且没有具体类型参数的类。

要保存类型参数,您可以实现PropertyCodecProvider接口来指定POJO中定义的泛型类型的参数。以下代码片段显示了一个PropertyCodecProvider的示例实现,该实现为Guava Optional类添加了序列化兼容性。

假设您想使用Optional字段序列化以下POJO:

public class ApplicationUser {
private Optional<Address> optionalAddress;
private Optional<Subscription> optionalSubscription;
// ...
}

您可以使用以下PropertyCodecProvider的实现来检索您的自定义Codec。此实现使用TypeWithTypeParameters接口来访问类型信息。

public class OptionalPropertyCodecProvider implements PropertyCodecProvider {
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public <T> Codec<T> get(final TypeWithTypeParameters<T> type, final PropertyCodecRegistry registry) {
// Check the main type and number of generic parameters
if (Optional.class.isAssignableFrom(type.getType()) && type.getTypeParameters().size() == 1) {
// Get the codec for the concrete type of the Optional, as its declared in the POJO.
Codec<?> valueCodec = registry.get(type.getTypeParameters().get(0));
return new OptionalCodec(type.getType(), valueCodec);
} else {
return null;
}
}
private static final class OptionalCodec<T> implements Codec<Optional<T>> {
private final Class<Optional<T>> encoderClass;
private final Codec<T> codec;
private OptionalCodec(final Class<Optional<T>> encoderClass, final Codec<T> codec) {
this.encoderClass = encoderClass;
this.codec = codec;
}
@Override
public void encode(final BsonWriter writer, final Optional<T> optionalValue, final EncoderContext encoderContext) {
if (optionalValue != null && optionalValue.isPresent()) {
codec.encode(writer, optionalValue.get(), encoderContext);
} else {
writer.writeNull();
}
}
@Override
public Optional<T> decode(final BsonReader reader, final DecoderContext context) {
return Optional.of(codec.decode(reader, context));
}
@Override
public Class<Optional<T>> getEncoderClass() {
return encoderClass;
}
}
}

按照以下方式在您的PojoCodecProvider和包含您的POJO的包中注册您的OptionalPropertyCodecProvider

CodecProvider pojoCodecProvider = PojoCodecProvider.builder()
.register("org.example.pojos")
.register(new OptionalPropertyCodecProvider())
.build();

有关本节中提到的方法和类的更多信息,请参阅以下API文档:

有关泛型和类型参数的更多信息,请参阅Java语言指南:调用和实例化泛型类型。

在驱动程序版本4.5及更高版本中,PojoCodecProvider不再包含用于转换枚举类型的编解码器。如果需要,请确保注册枚举类型的编解码器,例如默认编解码器注册表中的编解码器。

有关如何注册其包含的编解码器的更多信息,请参阅默认编解码器注册表的文档。

返回

记录