Docs 菜单
Docs 主页
/ / /
java sync
/ /

POJO 自定义

在此页面上

  • Overview
  • 自定义 PojoCodecProvider
  • ClassModel
  • PropertyModel
  • 规则
  • 注解
  • BsonExtraElements 示例
  • BsonRepresentation 错误示例
  • 鉴别器
  • 高级配置
  • 属性中的抽象或接口类型
  • 不带无参数构造函数的 POJO
  • 序列化自定义

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

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

我们还将展示如何利用规则注释等辅助方法指定常用的序列化操作。

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

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

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

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

您可以使用 PojoCodecProvider.builder() 方法创建一个 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 字段。可选。
鉴别器键
Specifies the name of the discriminator field. Optional.
For more information on discriminators, see the Discriminators section.
鉴别器值
Specifies the lookup value that represents the POJO class. Optional.
For more information on discriminators, see the Discriminators section.
鉴别器标志
指定是否对鉴别器进行序列化,默认为关闭。可选。

有关该类的更多信息,请参阅 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
Specifies whether to use the discriminator.
For more information on discriminators, see the Discriminators section.

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

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

Convention 接口包含可修改 ClassModelPropertyModel 行为的配置选项。您可以在调用 PojoCodecProvider.Builder.conventions()ClassModelBuilder.conventions() 时指定 Convention

注意

构建器按照顺序应用 Convention 实例,这可能会覆盖先前应用的实例中定义的行为。

可以从 Conventions 类中的以下静态字段访问 BSON 库中定义的 Convention 实例:

字段名称
说明
ANNOTATION_CONVENTION
为 POJO 启用 org.bson.codecs.pojo.annotations 包中定义的注解。有关详细信息,请参阅注解部分。
CLASS_AND_PROPERTY_CONVENTION
Sets the following default values for the ClassModel and PropertyModel instances:
- Discriminator key to _t
- Discriminator value to the ClassModel simple type name
- Id field to _id for each PropertyModel.
DEFAULT_CONVENTIONS
Enables the following Conventions:
- CLASS_AND_PROPERTY_CONVENTION
- ANNOTATION_CONVENTION
- OBJECT_ID_GENERATORS
NO_CONVENTIONS
提供一个空列表。
OBJECT_ID_GENERATORS
添加一个默认 IdGenerator,它为每个在 id 属性中使用 ObjectId 值的 ClassModel 添加一个新的 ObjectId
SET_PRIVATE_FIELDS_CONVENTION
允许 ClassModel 利用反射设置私有字段,而无需使用 setter 方法。
USE_GETTERS_FOR_SETTERS
如果不存在 setter 方法,则允许将 getter 方法用作 CollectionMap 字段的 setter。

可以使用以下方法指定规则:

要创建自定义规则,请创建一个实施 Convention 接口的类并重写可访问 ClassModelBuilder 实例的 apply() 方法。

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

可从 org.bson.codecs.pojo.annations 包中获得以下注解:

注解名称
说明
BsonCreator
Marks a public constructor or a public static method as the creator for new instances of the class. You must annotate all parameters in the constructor with either the BsonProperty or BsonId annotations.
BsonDiscriminator
指定一个类使用鉴别器。您可以设置自定义鉴别器键和值。
BsonRepresentation

指定当与 POJO 属性不同时用于存储该值的 BSON 类型。

提示

另请参阅:

BsonId
标记要序列化为 _id 属性的属性。
BsonIgnore
标记要忽略的属性。您可以配置是否序列化和/或反序列化属性。
BsonProperty

指定将 POJO 字段转换为 BSON 时的自定义文档字段名称。您可以纳入一个鉴别器来序列化嵌套在该字段中的 POJO。

重要

当将@BsonProperty应用于私有字段时,您还必须为该字段添加 getter 和 setter 方法,以序列化和自定义字段名称。

BsonExtraElements

指定一个 POLO 字段,要在该字段上反序列化所有未映射到字段的元素。POJO 字段必须是以下类型:

提示

另请参阅:

以下代码片段展示一个名为 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,并在执行写入操作时将值为 "AnnotatedProduct" 的 cls 字段添加到该 BSON 文档

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

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

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

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

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

假设您使用前面示例中的产品 POJO 存储和检索在线商店的数据。当您为商店提供更多种类的产品时,您需要更多字段来描述产品。您可以从使用 @BsonExtraElements 注解的单个字段访问产品,无需将每个附加字段映射到 POJO,如以下代码所示:

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 数据库中。本页注解部分中的产品 POJO 代码示例使用 @BsonRepresentationString 值作为ObjectId 值存储在数据库文档中。

但是,如果使用 @BsonRepresentation 注解在 StringObjectId 以外的数据类型之间进行转换,则会导致以下错误消息:

Codec must implement RepresentationConfigurable to support BsonRepresentation

例如,以下代码将类型为 LongpurchaseDate 字段添加到 Product POJO。此示例尝试使用 @BsonRepresentationLong 值表示为数据库中 DateTime 值:

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

上述代码会导致错误。相反,您可以创建一个自定义编解码器,将 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 对象类型之间的映射。有关使用 CodecRegistry 注册自定义编解码器的说明,请参阅编解码器指南。

鉴别器是标识特定文档架构的属性。鉴别器键标识用于标识架构的文档字段。鉴别器值标识文档字段的默认值。

使用鉴别器指示 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 集合中从前述 POJO 创建的示例文档:

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

要序列化包含抽象类或接口类型属性的 POJO,须指定针对该类型及其所有子类型或实施的鉴别器。

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

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

如果 User 抽象类具有子类 FreeUserSubscriberUser,您可以将 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,则默认的 PropertySerialization 实现会跳过该字段。

可以通过以下操作自定义 POJO 序列化行为:

  • 使用 @BsonIgnore 注释,让属性始终跳过序列化。确保使用相应的约定启用注释。

  • 创建自定义类来覆盖 PropertySerialization 接口的 shouldSerialize() 方法。将自定义实现指定为可从 ClassModelBuilder 访问的 PropertyModelBuilder

有关如何在 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));

如果插入的 POJO 在 age 字段中包含大于 29 的值,则序列化文档将忽略它。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 检查并保存具体类型参数,以解决类型擦除问题。它无法序列化包含泛型属性而没有具体类型参数的类,因为 Java 虚拟机会删除类型参数信息。

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

假设您想序列化以下带有 Optional 字段的 POJO:

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

您可以使用以下 PropertyCodecProvider 实施来检索自定义编解码器。此实施使用 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 中注册 OptionalPropertyCodecProvider 以及包含 POJO 的软件包,如下所示:

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

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

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

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

请参阅有关默认编解码器注册表的文档,进一步了解如何注册该注册表包含的编解码器。

后退

文档数据格式:记录