POJO自定义
本页内容
概述
在本指南中,您可以了解如何在MongoDB Java驱动程序中定义BSON和POJO之间的自定义数据转换。在我们的指南POJOs中,我们展示了如何指定一个PojoCodecProvider
,它包含提供如何转换一个或多个POJO类及其属性的数据指令的类。
我们展示了如何使用ClassModel和PropertyModel类指定您的数据转换。您还可以从高级配置部分了解更多具体的定制化。
我们还展示了如何使用约定和注解等辅助工具指定常见的序列化操作。
如果您想将多个POJO类序列化为同一集合中的文档,请参阅判别器部分。
如果您必须实现条件序列化或使用枚举、泛型、接口类型或抽象类型,请参阅高级配置部分。
如果您仅使用预定义行为在BSON和POJO之间转换数据,可以使用文档数据格式:POJOs指南中所示的PojoCodecProvider
的“自动”设置。
自定义PojoCodecProvider
本节展示了如何使用PojoCodecProvider
指定您的数据转换逻辑和POJO类。该PojoCodecProvider
是CodecProvider
接口的一个实现,指定了数据转换中使用的编解码器。在执行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
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 nameId field to _id for each PropertyModel . |
DEFAULT_CONVENTIONS | 启用以下约定 - CLASS_AND_PROPERTY_CONVENTION - ANNOTATION_CONVENTION - OBJECT_ID_GENERATORS |
NO_CONVENTIONS | 提供空列表。 |
OBJECT_ID_GENERATORS | 添加一个默认的 IdGenerator ,为每个使用 ObjectId 值的 ClassModel 在 id 属性中添加一个新的 ObjectId 。 |
SET_PRIVATE_FIELDS_CONVENTION | 启用 ClassModel 使用反射设置私有字段,无需setter方法。 |
USE_GETTERS_FOR_SETTERS | 如果不存在setter方法,启用使用getter方法作为 Collection 和 Map 字段的setter。 |
您可以使用以下方法之一指定约定:
要创建自定义约定,创建一个实现 Convention
接口并重写 apply()
方法的类,您可以在其中访问您的 ClassModelBuilder
实例。
注解
您可以将注解应用于POJO类的getter和setter方法。这些注解配置了特定字段、方法或类的 ClassModel
和 PropertyModel
行为。
以下注解来自 org.bson.codecs.pojo.annotations 包
注解名称 | 描述 |
---|---|
BsonCreator | 将公共构造函数或公共静态方法标记为类的实例创建者。您必须使用 BsonProperty 或 BsonId 注解来注释构造函数中的所有参数。 |
BsonDiscriminator | 指定一个类使用判别器。您可以设置自定义的判别器键和值。 |
BsonRepresentation | 指定在值与 POJO 属性不同时用于存储值的 BSON 类型。请参阅本页面上的BsonRepresentation 错误示例。 |
BsonId | 将属性标记为序列化为 _id 属性。 |
BsonIgnore | 将属性标记为忽略。您可以配置是否序列化和/或反序列化属性。 |
BsonProperty | 指定将 POJO 字段转换为 BSON 时使用的自定义文档字段名。您可以在字段中包含判别器以序列化嵌套在字段中的 POJO。 当将 @BsonProperty 应用于私有字段时,您还必须为此字段添加 getter 和 setter 方法以进行序列化和自定义字段名。 |
BsonExtraElements |
以下代码片段展示了名为 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,并在写入操作中将
cls
字段(值为 "AnnotatedProduct")添加到 BSON 文档中在文档中,在 POJO 的
name
字段和值与 BSON 的modelName
字段和值之间进行转换在 POJO 的
serialNumber
字段和值与 BSON 文档的_id
字段和值之间进行转换在转换数据时省略
relatedItems
字段和值使用
Product(String name)
构造函数来实例化 POJO
BsonExtraElements 示例
@BsonExtraElements
注解允许您指定一个字段,从缺少相应 POJO 字段映射的 MongoDB 文档中反序列化数据。当您的应用程序需要处理部分定义的架构中的数据时,这非常有用。您可以使用此注解来访问与您的 POJO 字段不对应的任何字段中的数据。
考虑这样一种情况,即您使用上一示例中的 Product POJO 来存储和检索在线商店的数据。随着您为商店提供更多样化的产品,您会发现需要额外的字段来描述它们。您无需将每个附加字段映射到 POJO,而是可以从一个使用 @BsonExtraElements
注解的单个字段中访问它们,如下面的代码示例所示
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 数据库中的不同数据类型。页面“注解”部分的 Product POJO 代码示例使用 @BsonRepresentation
将数据库文档中的 String
值存储为 ObjectId
值。
然而,使用 @BsonRepresentation
注解在 String
和 ObjectId
以外的数据类型之间转换会导致以下错误消息
Codec must implement RepresentationConfigurable to support BsonRepresentation
例如,以下代码向 Product
POJO 添加了一个类型为 Long
的 purchaseDate
字段。此示例尝试使用 @BsonRepresentation
将数据库中的 Long
值表示为 DateTime
值
public class Product { private String name; private String serialNumber; 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; } 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 对象类型之间的映射。有关如何将自定义 Codec 注册到 CodecRegistry
的说明,请参阅Codecs 指南。
判别器
判别器是一种属性,用于识别特定的文档模式。判别器键用于识别用于识别模式的文档字段。判别器值用于识别文档字段的默认值。
使用判别器来指示 CodecProvider
在反序列化到不同对象类时应使用哪个对象类。在将 POJO 序列化到 MongoDB 集合时,除非在 POJO 属性数据中另有指定,否则相关的编解码器会设置判别器键值字段。
您可以通过以下任一方式在 POJO 中设置和启用判别器
使用
@BsonDiscriminator
注解来指定 POJO 类的判别器在与 POJO 类关联的
ClassModelBuilder
上调用enableDiscriminator(true)
请参阅以下包含 @BsonDiscriminator
注解的示例 POJO 类以及包含判别器字段的示例文档
public class AnonymousUser { // class code } 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
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> { 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 { 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
和包含您的POJO的包中注册您的OptionalPropertyCodecProvider
:
CodecProvider pojoCodecProvider = PojoCodecProvider.builder() .register("org.example.pojos") .register(new OptionalPropertyCodecProvider()) .build();
有关本节中提到的方法和类的更多信息,请参阅以下API文档:
有关泛型和类型参数的更多信息,请参阅Java语言指南:调用和实例化泛型类型。
枚举类型支持
在驱动程序版本4.5及更高版本中,PojoCodecProvider
不再包含用于转换枚举
类型的编解码器。如果需要,请确保注册枚举
类型的编解码器,例如默认编解码器注册表中的编解码器。
有关如何注册其包含的编解码器的更多信息,请参阅默认编解码器注册表的文档。