POJO 自定义
在此页面上
Overview
本指南说明如何在 MongoDB Java 驱动程序中定义 BSON 和 POJO 之间的自定义数据转换。在 POJO 指南中,我们展示了如何指定PojoCodecProvider
,其中包含的类说明如何转换一个或多个 POJO 类及其属性的数据。
我们将展示如何使用 ClassModel 和 PropertyModel 类来指定数据转换。您还可以从高级配置部分了解更多具体的定制信息。
我们还将展示如何利用规则和注释等辅助方法指定常用的序列化操作。
如果您想将多个 POJO 类序列化为同一个集合中的文档,请参阅鉴别器部分。
如果您必须实现条件序列化,或使用枚举、泛型、接口类型或抽象类型,请参阅高级配置部分。
如果仅使用预定义行为在 BSON 和 POJO 之间转换数据,则可以使用 PojoCodecProvider
文档数据格式:POJO 指南 中显示的 自动 设置。
自定义 PojoCodecProvider
本部分展示如何使用 PojoCodecProvider
指定数据转换逻辑和 POJO 类。PojoCodecProvider
是 CodecProvider
接口的实现,该接口指定数据转换中使用的编解码器。在执行 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
ClassModel
实例存储特定 POJO 类的数据转换信息。它包含 PropertyModel
实例列表,这些实例描述 POJO 的属性字段、是否转换字段以及可选的转换字段所用的 Codecs
。
ClassModel
包含以下字段:
字段名称 | 说明 |
---|---|
名称 | 与 |
InstanceCreatorFactory | 包含一个新的实例工厂,用于创建 POJO 的新实例。默认情况下,它要求 POJO 有一个空的构造函数。 |
PropertyModels | 包含 |
IdPropertyModelHolder | 指定与文档 |
鉴别器键 | Specifies the name of the discriminator field. Optional. For more information about discriminators, see the Discriminators section. |
鉴别器值 | Specifies the lookup value that represents the POJO class. Optional. For more information about discriminators, see the Discriminators section. |
鉴别器标志 | 指定是否对鉴别器进行序列化,默认为关闭。可选。 |
有关该类的更多信息,请参阅 ClassModel API 文档。
要实例化 ClassModel
,请使用 ClassModel.builder()
方法并指定 POJO 类。构建器使用反射来创建所需的元数据。
ClassModel<Flower> classModel = ClassModel.builder(Flower.class).build();
PropertyModel
PropertyModel
存储有关如何对文档中的特定字段进行序列化和反序列化的信息。
PropertyModel
包含以下信息:
字段名称 | 说明 |
---|---|
名称 | 指定模型中的属性名称。 |
读取名称 | 序列化到 BSON 时用作键的属性名称。 |
写入名称 | 从 BSON 反序列化时用作键的属性名称。 |
类型数据 | 包含描述字段数据类型的 |
编解码器 | 指定用于对字段进行编码或解码的编解码器。可选。 |
序列化检查器 | 利用检查器中指定的标准,确定是否对值进行序列化。 |
属性访问器 | 用来访问来自 POJO 的属性值的方法。 |
useDiscriminator | Specifies whether to use the discriminator. For more information about discriminators, see the Discriminators section. |
要创建 PropertyModel
,请使用 PropertyModelBuilder
,您可以通过调用 PropertyModel.builder()
方法对其进行实例化。
有关此类的更多信息,请参阅 PropertyModel.Builder API 文档。
规则
Convention
接口包含可修改 ClassModel
或 PropertyModel
行为的配置选项。您可以在调用 PojoCodecProvider.Builder.conventions()
或 ClassModelBuilder.conventions()
时指定 Convention
。
注意
构建器按顺序应用Convention
实例,这些实例可以覆盖先前应用的实例中定义的行为。
可以从 Conventions
类中的以下静态字段访问 BSON 库中定义的 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 . |
| Enables the following Conventions: - CLASS_AND_PROPERTY_CONVENTION - ANNOTATION_CONVENTION - OBJECT_ID_GENERATORS |
| 提供一个空列表。 |
| 添加一个默认 |
| 允许 |
| 如果不存在 setter 方法,则允许将 getter 方法用作 |
可以使用以下方法指定规则:
要创建自定义规则,请创建一个实施 Convention
接口的类并重写可访问 ClassModelBuilder
实例的 apply()
方法。
注解
您可以将注解应用于 POJO 类的 getter 和 setter 方法。这些注解为特定字段、方法或类配置 ClassModel
和 PropertyModel
行为。
可从 org.bson.codecs.pojo.annations 包中获得以下注解:
注解名称 | 说明 |
---|---|
| 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 |
| 指定一个类使用鉴别器。您可以设置自定义鉴别器键和值。 |
| 当与 POJO属性不同时,指定用于存储该值的BSON类型。请参阅本页上的 BsonRepresentation 错误示例。 |
| 标记要序列化为 _id 属性的属性。 |
| 标记要忽略的属性。您可以配置是否序列化和/或反序列化属性。 |
| Specifies a custom document field name when converting the POJO
field to BSON. You can include a discriminator to serialize POJOs
nested within the field. When applying @BsonProperty to a private field,
you must also add getter and setter methods for that field to serialize and
customize the field name. |
| Specifies the POJO field on which to deserialize all elements that are
not mapped to a field. The POJO field must be one of the following
types: See an example of a BsonExtraElements Annotation Example. |
以下代码片段展示一个名为 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; public class Product { private String name; private String serialNumber; private List<Product> relatedItems; public Product( String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } // ... }
提示
使用注解时,请记住在 ClassModelBuilder
或 PojoCodecProvider.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
字段和值与 BSONmodelName
字段和值之间进行转换在文档中的 POJO
serialNumber
字段和值与 BSON 文档_id
字段和值之间进行转换转换数据时省略
relatedItems
字段和值实例化 POJO 时使用
Product(String name)
构造函数
BsonExtraElements 示例
@BsonExtraElements
注解允许您指定字段,借以反序列化缺少相应 POJO 字段映射的 MongoDB 文档中的数据。当应用程序需要使用部分定义的模式中的数据时,这非常有用。您可以使用此注解来访问与 POJO 上的字段不对应的任何字段中的数据。
假设您使用前面示例中的产品 POJO 存储和检索在线商店的数据。当您为商店提供更多种类的产品时,您需要更多字段来描述产品。您可以从使用 @BsonExtraElements
注解的单个字段访问产品,无需将每个附加字段映射到 POJO,如以下代码所示:
public class Product { private String name; private String serialNumber; private List<Product> relatedItems; private Document additionalInfo; // ...
假设有人向产品数据中的 dimensions
和 weight
添加了附加字段,使得文档包含以下信息:
{ "name": "MDB0123", "serialNumber": "62e2...", "dimensions": "3x4x5", "weight": "256g" }
使用 Product
POJO 检索到的前述文档包含以下数据:
ProductWithBsonExtraElements [ name=MDB0123, serialNumber=62eb..., relatedItems=null, additionalInfo=Document{{dimensions=3x4x5, weight=256g}} ]
BsonRepresentation 错误示例
@BsonRepresentation
注解允许您将 POJO 类字段作为不同的数据类型存储在 MongoDB 数据库中。本页注解部分中的产品 POJO 代码示例使用 @BsonRepresentation
将String
值作为ObjectId
值存储在数据库文档中。
但是,如果使用 @BsonRepresentation
注解在 String
和 ObjectId
以外的数据类型之间进行转换,则会导致以下错误消息:
Codec must implement RepresentationConfigurable to support BsonRepresentation
例如,以下代码将类型为 Long
的 purchaseDate
字段添加到 Product
POJO。此示例尝试使用 @BsonRepresentation
将 Long
值表示为数据库中 DateTime
值:
public class Product { private String name; private String serialNumber; 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; } public BsonType getRepresentation() { return representation; } 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); } 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); } } 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"); } } 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 类以及包含鉴别器字段的示例文档:
public class AnonymousUser { // class code } 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
抽象类具有子类 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
POJO Codecs
默认调用空的无参数构造函数。若要指定其他构造函数,必须在 POJO 中执行以下操作:
将
ANNOTATION_CONVENTION
设置传递给ClassModelBuilder
使用
BsonCreator
注解标识构造函数
有关设置 ANNOTATION_CONVENTION
的示例,请参阅 ANNOTATION_CONVENTION 示例。有关 BsonCreator
注释的示例,请参阅带有注释代码的 POJO 示例。
序列化自定义
默认情况下,ClassModelBuilder
尝试序列化 POJO 中的所有非空属性。如果属性值为 null
,则默认的 PropertySerialization
实现会跳过该字段。
可以通过以下操作自定义 POJO 序列化行为:
使用
@BsonIgnore
注释,让属性始终跳过序列化。确保使用相应的约定启用注释。创建一个自定义类来覆盖
PropertySerialization
接口的shouldSerialize()
方法。 将您的自定义实施指定为PropertyModelBuilder
,您可以从ClassModelBuilder
访问该实施。
有关如何在 POJO 中使用 @BsonIgnore
注释的更多信息,请参阅本指南中有关注释的部分。
如下示例代码显示的自定义类将实施 PropertySerialization
接口,以覆盖用来确定序列化字段的默认条件:
public class CourteousAgeSerialization implements PropertySerialization<Integer> { 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 { 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; } 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(); } } public Optional<T> decode(final BsonReader reader, final DecoderContext context) { return Optional.of(codec.decode(reader, context)); } 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
类型的编解码器。
有关如何注册默认编解码器包含的编解码器的更多信息,请参阅有关默认编解码器注册表的文档。