Personalização POJO
Nesta página
- Visão geral
- Personalizar um PojoCodecProvider
- ClassModel
- PropertyModel
- Convenções
- Anotações
- Exemplo de BSONExtraElements
- Exemplo de erro BsonRepresentation
- Discriminadores
- Configuração avançada
- Tipos abstratos ou de interface em propriedades
- POJOs que não têm construtores sem argumentos
- Personalização de serialização
Visão geral
Neste guia, você pode aprender a definir conversões de dados personalizadas entre BSON e POJOs no driver Java do MongoDB. Em nosso guia sobre POJOs, mostramos como especificar um PojoCodecProvider
que contém classes que fornecem instruções sobre como converter dados para uma ou mais classes POJO e suas propriedades.
Mostramos como especificar sua conversão de dados utilizando as classes ClassModel e PropertyModel. Você também pode aprender sobre personalização mais específica na seção Configuração avançada.
Também mostramos como usar assistentes como Convenções e Anotações para especificar ações comuns de serialização.
Consulte a seção Discriminadores se quiser serializar várias classes POJO para documentos na mesma coleção.
Se você precisar implementar serialização condicional ou usar enumerações, genéricos, tipos de interface ou tipos abstratos, consulte a seção sobre configuração avançada.
Se você estiver usando apenas o comportamento predefinido para converter dados entre BSON e POJOs, você pode usar a configuração automática para o PojoCodecProvider
mostrado no guia Formatos de dados do documento: POJOs.
Personalizar um PojoCodecProvider
Esta seção mostra como especificar a lógica de conversão de dados e as classes POJO com um PojoCodecProvider
. O PojoCodecProvider
é uma implementação da interface do CodecProvider
que especifica os codecs para utilizar na conversão de dados. Use essa implementação ao executar a conversão de dados entre BSON e POJOs.
Você pode criar uma instância PojoCodecProvider
utilizando o método PojoCodecProvider.builder()
. Além disso, pode encadear métodos para o construtor para registrar um dos itens a seguir:
Classes POJO individuais
Nomes de pacotes que contêm classes POJO
Instâncias de
ClassModel
que descrevem a lógica de conversão para uma classe POJO
O exemplo a seguir mostra como você pode especificar os POJOs em um pacote chamado "org.example.pojos" e adicione o PojoCodecProvider
a um 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
Para obter mais informações sobre essa classe, consulte a documentação da API do PojoCodecProvider.Builder.
ClassModel
Uma instância ClassModel
armazena informações de conversão de dados sobre uma classe POJO específica. Ele contém uma lista de instâncias PropertyModel
que descrevem os campos de propriedade do POJO, se deseja converter campos e, opcionalmente, Codecs
para converter os campos.
Um ClassModel
contém os seguintes campos:
Nome do campo | Descrição |
---|---|
Nome | O nome da classe POJO para associar ao ClassModel . |
InstanceCreatorFactory | Contém uma nova fábrica de instâncias que cria novas instâncias do POJO. Por padrão, isso requer que o POJO tenha um construtor vazio. |
PropertyModels | Contém uma lista de instâncias PropertyModel que especificam como converter dados de e para BSON para um campo no POJO. |
IdPropertyModelHolder | Especifica o campo POJO que corresponde ao campo _id do documento. Opcional. |
Chave do discriminador | Specifies the name of the discriminator field. Optional. For more information about discriminators, see the Discriminators section. |
Valor do discriminador | Specifies the lookup value that represents the POJO class. Optional. For more information about discriminators, see the Discriminators section. |
Sinalizador do discriminador | Especifica se deseja serializar o discriminador, desativado por padrão. Opcional. |
Para obter mais informações sobre esta classe, consulte a Documentação da API ClassModel.
Para instanciar um ClassModel
, utilize o método ClassModel.builder()
e especifique sua classe POJO. O construtor usa reflexão para criar os metadados necessários.
ClassModel<Flower> classModel = ClassModel.builder(Flower.class).build();
PropertyModel
Um PropertyModel
armazena informações sobre como serializar e desserializar um campo específico em um documento.
O PropertyModel
contém as seguintes informações:
Nome do campo | Descrição |
---|---|
Nome | Especifica o nome da propriedade no modelo. |
Ler nome | Nome da propriedade a ser usada como chave ao serializar para BSON. |
Gravar nome | Nome da propriedade a ser usada como chave ao desserializar do BSON. |
Digitar dados | Contém uma instância org.bson.codecs.pojo.TypeData que descreve o tipo de dados do campo. |
Codec | Especifica um codec a ser usado para codificar ou decodificar o campo. Opcional. |
Verificador de serialização | Determina se deve serializar um valor usando os critérios especificados no verificador. |
Acessador de propriedade | Método usado para acessar o valor da propriedade a partir do POJO. |
useDiscriminator | Specifies whether to use the discriminator. For more information about discriminators, see the Discriminators section. |
Para criar um PropertyModel
utilize um PropertyModelBuilder
que você pode instanciar chamando o método PropertyModel.builder()
.
Para obter mais informações sobre esta classe, consulte PropertyModel.Builder Documentação da API.
Convenções
A interface Convention
contém opções de configuração que modificam o comportamento de ClassModel
ou PropertyModel
. Você pode especificar um Convention
em uma chamada para PojoCodecProvider.Builder.conventions()
ou para ClassModelBuilder.conventions()
.
Observação
Os construtores aplicam instâncias Convention
em ordem, o que pode substituir o comportamento definido em uma instância aplicada anteriormente.
Você pode acessar as instâncias Convention
definidas na biblioteca BSON a partir dos seguintes campos estáticos na classe Conventions
:
Nome do campo | Descrição |
---|---|
ANNOTATION_CONVENTION | Habilita as anotações definidas no pacote org.bson.codecs.pojo.annotations para seu POJO. Para obter mais informações, consulte a seção Anotações. |
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 | Fornece uma lista vazia. |
OBJECT_ID_GENERATORS | Adiciona um IdGenerator padrão que adiciona um novo ObjectId para cada ClassModel que utiliza valores de ObjectId na propriedade id . |
SET_PRIVATE_FIELDS_CONVENTION | Permite que o ClassModel defina campos privados usando reflexão sem exigir um método de preparação. |
USE_GETTERS_FOR_SETTERS | Habilita o uso de métodos de obtenção e preparação para campos Collection e Map se não existir nenhum método de preparação. |
Você pode especificar Convenções usando um dos seguintes métodos:
Para criar uma convenção personalizada, crie uma classe que implemente a interface do Convention
e substitua o método do apply()
do qual você pode acessar sua instância ClassModelBuilder
.
Anotações
Você pode aplicar anotações aos métodos de obtenção e preparação de uma classe POJO. Estas anotações configuram o comportamento ClassModel
e PropertyModel
para um campo, método ou classe específico.
As seguintes anotações estão disponíveis no pacote org.bson.codecs.pojo.annotations:
Nome da Anotação | Descrição |
---|---|
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 | Especifica que uma classe usa um discriminador. Você pode definir uma chave e um valor de discriminador personalizado. |
BsonRepresentation | Especifica o tipo BSON utilizado para armazenar o valor quando diferente da propriedade POJO. Veja um exemplo de Exemplo de erro de representação BSON nesta página. |
BsonId | Marca uma propriedade a ser serializada como a propriedade _id. |
BsonIgnore | Marca uma propriedade a ser ignorada. Você pode configurar se deseja serializar e/ou desserializar uma propriedade. |
BsonProperty | 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. |
BsonExtraElements | 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. |
O seguinte trecho de código exibe uma amostra POJO chamada Product
que usa várias das anotações anteriores.
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; } // ... }
Dica
Ao utilizar anotações, lembre-se de especificar o Conventions.ANNOTATION_CONVENTION
em seu ClassModelBuilder
ou PojoCodecProvider.Builder
. Por exemplo:
ClassModel<Product> classModel = ClassModel.builder(Product.class). conventions(Arrays.asList(Conventions.ANNOTATION_CONVENTION)).build();
As anotações no exemplo POJO especificam o seguinte comportamento:
Faça referência ao POJO com a chave e o valor do discriminador especificado, adicionando o campo
cls
com o valor de "AnnotatedProduct" ao documento BSON em operações de gravaçãoConverter entre o campo e o valor POJO
name
e o campo e o valor BSONmodelName
no documentoConverta entre o campo e valor POJO
serialNumber
e o campo e valor BSON_id
no documentoOmita o campo e valor
relatedItems
ao converter dadosUtilize o construtor
Product(String name)
ao instanciar o POJO
Exemplo de BSONExtraElements
A anotação @BsonExtraElements
permite especificar um campo para desserializar dados de um documento MongoDB que não tem um mapeamento de campo POJO correspondente. Isso é útil quando seu aplicativo precisa trabalhar com dados em um esquema parcialmente definido. Você pode usar essa anotação para acessar dados de qualquer campo que não corresponda aos campos no seu POJO.
Considere uma situação em que você armazena e recupera dados de uma loja virtual usando o POJO do produto do exemplo anterior. À medida que você oferece uma variedade maior de produtos na loja, descobre que precisa de campos adicionais para descrevê-los. Em vez de mapear cada campo adicional ao POJO, você pode acessá-los a partir de um único campo anotado com @BsonExtraElements
, conforme mostrado no exemplo de código a seguir:
public class Product { private String name; private String serialNumber; private List<Product> relatedItems; private Document additionalInfo; // ...
Suponha que alguém tenha adicionado campos para dimensions
e weight
aos dados do produto, de modo que os documentos contenham as seguintes informações:
{ "name": "MDB0123", "serialNumber": "62e2...", "dimensions": "3x4x5", "weight": "256g" }
O documento anterior recuperado usando o POJO do Product
contém os seguintes dados:
ProductWithBsonExtraElements [ name=MDB0123, serialNumber=62eb..., relatedItems=null, additionalInfo=Document{{dimensions=3x4x5, weight=256g}} ]
Exemplo de erro BsonRepresentation
A anotação @BsonRepresentation
permite que você armazene um campo de classe POJO como um tipo de dados diferente em seu banco de dados MongoDB. O exemplo de código POJO do produto na seção Anotações desta página usa @BsonRepresentation
para armazenar valores String
como valores ObjectId
nos documentos do banco de dados.
No entanto, o uso da anotação @BsonRepresentation
para converter entre tipos de dados diferentes de String
e ObjectId
causa a seguinte mensagem de erro:
Codec must implement RepresentationConfigurable to support BsonRepresentation
Por exemplo, o código a seguir adiciona um campo purchaseDate
do tipo Long
ao Product
PoJo. Este exemplo tenta usar @BsonRepresentation
para representar valores Long
como valores DateTime
no banco de dados:
public class Product { private String name; private String serialNumber; private Long purchaseDate; // ... }
O código anterior resulta em um erro. Em vez disso, você pode criar um codec personalizado para converter os valores purchaseDate
do tipo Long
para 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; } }
Em seguida, adicione uma instância do LongRepresentableCodec
no seu CodecRegistry
que contém um mapeamento entre seu Codec e o tipo de objeto Java ao qual ele se aplica. Para obter instruções sobre como registrar seu codec personalizado com o CodecRegistry
, consulte o guia de codecs.
Discriminadores
Um discriminador é uma propriedade que identifica um esquema de documento específico. A chave do discriminador identifica um campo do documento a ser usado para identificar o esquema. O valor do discriminador identifica o valor padrão do campo do documento.
Use discriminadores para instruir ao CodecProvider
qual classe de objeto usar ao desserializar para diferentes classes de objetos da mesma collection. Ao serializar o POJO para uma collection do MongoDB, o codec associado define o campo de valor-chave do discriminador, a menos que especificado de outra forma nos dados da propriedade POJO.
Você pode definir e habilitar um discriminador em um POJO executando um dos seguintes procedimentos:
Use a anotação
@BsonDiscriminator
para especificar o discriminador para a classe POJOChamar
enableDiscriminator(true)
noClassModelBuilder
associado à classe POJO
Veja os seguintes exemplos de classes POJO que contêm anotações @BsonDiscriminator
e exemplos de documentos que contêm os campos discriminadores:
public class AnonymousUser { // class code } public class RegisteredUser { // class code }
Veja a seguir exemplos de documentos criados a partir dos POJOs anteriores em uma única collection do MongoDB:
{ "_cls": "AnonymousUser", "_id": ObjectId("<Object ID>"), ... } { "_cls": "RegisteredUser", "_id": ObjectId("<Object ID>"), ... }
Configuração avançada
Tipos abstratos ou de interface em propriedades
Para serializar um POJO que inclui as propriedades classe abstrata ou tipo de interface, você deve especificar discriminadores no tipo e em todos os seus subtipos ou implementações.
Suponha que você definiu um POJO que referenciou uma classe abstrata User
em um de seus campos da seguinte maneira:
public class UserRecordPojo { private User user; // ... }
Se a classe abstrata User
tiver subclasses FreeUser
e SubscriberUser
, você poderá adicionar o POJO e as classes abstratas à CodecRegistry
da seguinte forma:
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));
Para obter mais informações sobre como especificar discriminadores, consulte a seção Discriminadores no presente guia.
POJOs que não têm construtores sem argumentos
O padrão do POJO Codecs
é chamar o construtor vazio e sem argumentos. Para especificar um construtor diferente, você deve executar o seguinte em seu POJO:
passe a configuração
ANNOTATION_CONVENTION
para o seuClassModelBuilder
identifique o construtor utilizando a anotação
BsonCreator
Para ver um exemplo de configuração do ANNOTATION_CONVENTION
, consulte o exemplo ANNOTATION_CONVENTION. Para ver um exemplo da anotação BsonCreator
, consulte o exemplo de código POJO com anotação.
Personalização de serialização
Por padrão, o ClassModelBuilder
tenta serializar todas as propriedades não nulas em seu POJO. Se um valor de propriedade for null
, a implementação padrão do PropertySerialization
pulará esse campo.
Você pode personalizar seu comportamento de serialização de POJO executando um dos seguintes procedimentos:
Utilize a anotação
@BsonIgnore
para uma propriedade para sempre ignorar a serialização. Certifique-se de habilitar as anotações usando as convençõesapropriadas.Crie uma classe personalizada que substitua o método
shouldSerialize()
da interfacePropertySerialization
. Especifique sua implementação personalizada paraPropertyModelBuilder
que você pode acessar emClassModelBuilder
.
Para obter mais informações sobre como usar a anotação @BsonIgnore
em um POJO, consulte a seção deste guia sobre Anotações.
O código de exemplo a seguir mostra uma classe personalizada que implementa a interface PropertySerialization
para substituir as condições padrão pelas quais se determina a serialização de um campo:
public class CourteousAgeSerialization implements PropertySerialization<Integer> { public boolean shouldSerialize(Integer value) { return (value < 30); } }
A classe anterior especifica que qualquer número inteiro maior que 29 não é serializado e, portanto, não está incluído no documento MongoDB. Suponha que você tenha aplicado este comportamento de serialização personalizada à seguinte amostra POJO:
public class BirthdayInvitation { private String name; private Integer age; private LocalDateTime eventDateTime; // ... }
Você pode especificar a serialização personalizada adicionando a instância CourteousAgeSerialization
ao PropertyModelBuilder
da propriedade ClassModel
associada ao campo age
usando o seguinte código:
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));
Se você inserir um POJO que contém um valor maior que 29 no campo age
, o documento serializado o omitirá. A declaração POJO e o documento resultante se assemelham ao seguinte:
// constructor with parameters for name, age, and eventDateTime, respectively BirthdayInvitation invitation = new BirthdayInvitation( "Galadriel", 7582, LocalDateTime.of(2021, Month.JANUARY, 18, 30, 0) );
Como o valor do campo age
é maior que 29, o documento serializado é semelhante ao seguinte:
{ "_id" : ObjectId("..."), "eventDateTime" : ..., "name" : "Galadriel" }
Suporte a genéricos
Você pode usar o POJO Codec
para serializar classes que contêm propriedades genéricas se elas atenderem aos seguintes critérios:
Contém apenas parâmetros de tipos concretos delimitados
Se ele ou qualquer um dos seus campos fizer parte de uma hierarquia de classe, o POJO de nível superior não conterá nenhum parâmetro de tipo
O ClassModelBuilder
inspeciona e salva parâmetros de tipo concreto para trabalhar ao redor do apagamento do tipo. Não é possível serializar classes que contêm propriedades genéricas sem parâmetros de tipo concreto, pois a JVM remove as informações do parâmetro de tipo.
Para salvar parâmetros de tipo, você pode implementar a interface do PropertyCodecProvider
para especificá-los como tipos genéricos definidos em um POJO. Os seguintes trechos de código mostram um exemplo de implementação do PropertyCodecProvider
que adiciona compatibilidade de serialização à classe Guava Optional
.
Suponha que você queira serializar o seguinte POJO com campos Optional
:
public class ApplicationUser { private Optional<Address> optionalAddress; private Optional<Subscription> optionalSubscription; // ... }
Você pode utilizar a seguinte implementação do PropertyCodecProvider
para recuperar seu codec personalizado. Essa implementação usa a interface doTypeWithTypeParameters
para acessar as informações do tipo.
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; } } }
Registre seu OptionalPropertyCodecProvider
no seu PojoCodecProvider
e no pacote que contém seu POJO como segue:
CodecProvider pojoCodecProvider = PojoCodecProvider.builder() .register("org.example.pojos") .register(new OptionalPropertyCodecProvider()) .build();
Para obter mais informações sobre os métodos e as classes mencionadas nesta seção, consulte a seguinte documentação da API:
Para obter mais informações sobre genéricos e parâmetros de tipo, consulte o guia da linguagem Java sobre como invocar e instanciar um tipo genérico.
Suporte ao tipo Enum
Nas versões 4.5 e posterior do driver, o PojoCodecProvider
não inclui mais um codec para converter tipos enum
. Certifique-se de registrar um codec para tiposenum
se precisar de um, como o do registro de codec padrão.
Consulte a documentação sobre o registro de codec padrão para obter mais informações sobre como registrar os codecs que ele inclui.