Overview
En esta guía, aprenderá a definir conversiones de datos personalizadas entre BSON y POJO en el controlador Java de MongoDB. En nuestra guía sobre POJOs, mostramos cómo especificar un PojoCodecProvider que contiene clases que proporcionan instrucciones sobre cómo convertir datos para una o más clases POJO y sus propiedades.
Le mostramos cómo especificar la conversión de datos utilizando el ClasesClassModel y PropertyModel. También puede obtener información sobre personalización más específica en la sección Configuración avanzada.
También mostramos cómo utilizar ayudantes como Convenciones y Anotaciones para especificar acciones de serialización comunes.
Consulte la sección sobre discriminadores si desea serializar múltiples clases POJO en documentos de la misma colección.
Si necesitas implementar serialización condicional, usar enums, genéricos, tipos de interfaz o tipos abstractos, consulta la sección Configuración avanzada.
Si está utilizando solo el comportamiento predefinido para convertir datos entre BSON y POJO, puede usar la configuración automática para que se PojoCodecProvider muestra en la guía Formatos de datos de documentos: POJO.
Personalizar un PojoCodecProvider
Esta sección muestra cómo especificar la lógica de conversión de datos y las clases POJO con un PojoCodecProvider. El PojoCodecProvider es una implementación de la interfaz CodecProvider que especifica los códecs que se usarán en la conversión de datos. Utilice esta implementación al realizar conversiones de datos entre BSON y POJO.
Puedes crear una instancia PojoCodecProvider con el método PojoCodecProvider.builder(). También puedes encadenar métodos al constructor para registrar cualquiera de los siguientes:
Clases individuales de POJO
Nombres de paquetes que contienen clases POJO
Instancias de
ClassModelque describen la lógica de conversión para una clase POJO específica
El siguiente ejemplo muestra cómo puede especificar los POJO en un paquete llamado "org.example.pojos" y agregar PojoCodecProvider a un 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 obtener más información sobre esta clase, consulte PojoCodecProvider.Builder Documentación de la API.
Modelo de clase
Una instancia ClassModel almacena información de conversión de datos sobre una clase POJO específica. Contiene una lista de instancias PropertyModel que describen los campos de propiedad del POJO, si se deben convertir los campos y, opcionalmente, Codecs para convertirlos.
Un ClassModel contiene los siguientes campos:
Nombre de campo | Descripción |
|---|---|
Nombre | El nombre de la clase POJO que se asociará con |
Fábrica de creadores de instancias | Contiene una nueva fábrica de instancias que crea nuevas instancias del POJO. De forma predeterminada, requiere que el POJO tenga un constructor vacío. |
Modelos de propiedad | Contiene una lista de |
IdPropertyModelHolder | Especifica el campo POJO que corresponde al campo |
Clave discriminadora | Specifies the name of the discriminator field. Optional. For more information about discriminators, see the Discriminators section. |
Valor discriminador | Specifies the lookup value that represents the POJO class. Optional. For more information about discriminators, see the Discriminators section. |
Bandera discriminadora | Especifica si se debe serializar el discriminador (desactivado de forma predeterminada). Opcional. |
Para obtener más información sobre esta clase, consulte la documentación de la API de ClassModel.
Para instanciar un ClassModel, use el método ClassModel.builder() y especifique su clase POJO. El generador usa la reflexión para crear los metadatos necesarios.
ClassModel<Flower> classModel = ClassModel.builder(Flower.class).build();
Modelo de propiedad
Un PropertyModel almacena información sobre cómo serializar y deserializar un campo específico en un documento.
El PropertyModel contiene la siguiente información:
Nombre de campo | Descripción |
|---|---|
Nombre | Especifica el nombre de la propiedad en el modelo. |
Leer nombre | Nombre de la propiedad a utilizar como clave al serializar en BSON. |
Escribe el nombre | Nombre de la propiedad que se utilizará como clave al deserializar desde BSON. |
Tipo de datos | Contiene una instancia de |
Códec | Especifica el códec que se utilizará para codificar o decodificar el campo. Opcional. |
Comprobador de serialización | Determina si se debe serializar un valor utilizando los criterios especificados en el verificador. |
Accesor de propiedad | Método utilizado para acceder al valor de la propiedad desde el POJO. |
useDiscriminator | Specifies whether to use the discriminator. For more information about discriminators, see the Discriminators section. |
Para crear un PropertyModel utilice un PropertyModelBuilder que puede instanciar llamando al método PropertyModel.builder().
Para obtener más información sobre esta clase, consulte la documentación de la API PropertyModel.Builder.
Convenciones
La interfaz Convention contiene opciones de configuración que modifican el comportamiento de un ClassModel o PropertyModel. Puedes especificar un Convention en una llamada a PojoCodecProvider.Builder.conventions() o a ClassModelBuilder.conventions().
Nota
Los constructores aplican Convention instancias en orden que pueden anular el comportamiento definido en una aplicada anteriormente.
Puede acceder a las instancias Convention definidas en la biblioteca BSON desde los siguientes campos estáticos en la clase Conventions:
Nombre de campo | Descripción |
|---|---|
| Habilita las anotaciones definidas en el |
| 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 |
| Proporciona una lista vacía. |
| Agrega un |
| Permite que |
| Permite el uso de métodos getter como setters para los campos |
Puede especificar convenciones utilizando uno de los siguientes métodos:
Para crear una convención personalizada, cree una clase que implemente la interfaz Convention y anule el método apply() desde el cual puede acceder a su instancia ClassModelBuilder.
Anotaciones
Puedes aplicar anotaciones a los métodos getter y setter de una clase POJO. Estas anotaciones configuran el comportamiento ClassModel y PropertyModel para un campo, método o clase específicos.
Las siguientes anotaciones están disponibles en el paquete org.bson.codecs.pojo.annotations:
Nombre de la anotación | Descripción |
|---|---|
| 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 |
| Especifica que una clase utiliza un discriminador. Puede establecer una clave y un valor de discriminador personalizados. |
| Especifica el tipo BSON utilizado para almacenar el valor cuando es diferente de la propiedad POJO. Consulte el ejemplo de error de BsonRepresentation en esta página. |
| Marca una propiedad para serializar como la propiedad _id. |
| Marca una propiedad para ignorarla. Puede configurar si desea serializarla o deserializarla. |
| 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 you apply @BsonProperty to a private field,
you must also add getter and setter methods for that field to
serialize and customize the field name. If you use
SET_PRIVATE_FIELDS_CONVENTION, the setter is optional but
the getter is still required. You must also include
ANNOTATION_CONVENTION when you use @BsonProperty with
SET_PRIVATE_FIELDS_CONVENTION. |
| 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 the BsonExtraElements Annotation Example on this page. |
El siguiente fragmento de código muestra un POJO de muestra llamado Product que utiliza varias de las anotaciones 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; } // ... }
Tip
Al usar anotaciones, recuerde especificar el Conventions.ANNOTATION_CONVENTION en su ClassModelBuilder o PojoCodecProvider.Builder. Por ejemplo:
ClassModel<Product> classModel = ClassModel.builder(Product.class). conventions(Arrays.asList(Conventions.ANNOTATION_CONVENTION)).build();
Las anotaciones en el POJO de ejemplo especifican el siguiente comportamiento:
Haga referencia al POJO con la clave y el valor del discriminador especificados, agregando el campo
clscon el valor de "AnnotatedProduct" al documento BSON en las operaciones de escrituraConvierte entre el campo y valor POJO
namey el campo y valor BSONmodelNameen el documentoConvierte entre el campo POJO
serialNumbery el valor del campo del documento BSON_idy el valor en el documentoOmita el campo y el valor
relatedItemsal convertir datosUtilice el constructor
Product(String name)al crear una instancia del POJO
Ejemplo de BsonExtraElements
La anotación @BsonExtraElements permite especificar un campo para deserializar datos de un documento MongoDB que no tiene una asignación de campo POJO correspondiente. Esto resulta útil cuando la aplicación necesita trabajar con datos en un esquema parcialmente definido. Puede usar esta anotación para acceder a los datos de cualquier campo que no corresponda a los campos de su POJO.
Considere una situación en la que almacena y recupera datos para una tienda en línea utilizando el POJO de producto del ejemplo anterior. A medida que ofrece una mayor variedad de productos para la tienda, descubre que necesita campos adicionales para describirlos. En lugar de asignar cada campo adicional al POJO, puede acceder a ellos desde un solo campo anotado con,@BsonExtraElements como se muestra en el siguiente ejemplo de código:
public class Product { private String name; private String serialNumber; private List<Product> relatedItems; private Document additionalInfo; // ...
Supongamos que alguien agregó campos adicionales para dimensions y weight a los datos del producto de modo que los documentos contuvieran la siguiente información:
{ "name": "MDB0123", "serialNumber": "62e2...", "dimensions": "3x4x5", "weight": "256g" }
El documento anterior recuperado mediante el POJO Product contiene los siguientes datos:
ProductWithBsonExtraElements [ name=MDB0123, serialNumber=62eb..., relatedItems=null, additionalInfo=Document{{dimensions=3x4x5, weight=256g}} ]
Ejemplo de error de BsonRepresentation
La @BsonRepresentation anotación permite almacenar un campo de clase POJO como un tipo de dato diferente en la base de datos MongoDB. El ejemplo de código POJO de producto, en la sección Anotaciones de esta página, utiliza @BsonRepresentation para almacenar String valores como ObjectId valores en los documentos de la base de datos.
Sin embargo, el uso de la anotación @BsonRepresentation para convertir entre tipos de datos distintos de String y ObjectId genera el siguiente mensaje de error:
Codec must implement RepresentationConfigurable to support BsonRepresentation
Por ejemplo, el siguiente código agrega un campo purchaseDate de tipo Long al POJO Product. Este ejemplo intenta usar @BsonRepresentation para representar valores Long como valores DateTime en la base de datos:
public class Product { private String name; private String serialNumber; private Long purchaseDate; // ... }
El código anterior genera un error. En su lugar, puede crear un códec personalizado para convertir los valores purchaseDate del tipo Long al tipo 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; } }
Luego, agregue una instancia de a LongRepresentableCodec CodecRegistrysu, que contiene una asignación entre su códec y el tipo de objeto Java al que se aplica. Para obtener instrucciones sobre cómo registrar su códec personalizado CodecRegistry con, consulte la guía "Codificar datos con códecs de tipo".
Discriminadores
Un discriminador es una propiedad que identifica un esquema de documento específico. La clave del discriminador identifica un campo de documento que se utiliza para identificar el esquema. El valor del discriminador identifica el valor predeterminado del campo de documento.
Utilice discriminadores para indicar a CodecProvider qué clase de objeto usar al deserializar a diferentes clases de objeto de la misma colección. Al serializar el POJO a una colección de MongoDB, el códec asociado establece el campo clave-valor del discriminador, a menos que se especifique lo contrario en los datos de propiedad del POJO.
Puede configurar y habilitar un discriminador en un POJO realizando una de las siguientes acciones:
Utilice la anotación
@BsonDiscriminatorpara especificar el discriminador para la clase POJOLlamar a
enableDiscriminator(true)en elClassModelBuilderasociado con la clase POJO
Vea las siguientes clases POJO de ejemplo que contienen anotaciones @BsonDiscriminator y documentos de ejemplo que contienen los campos discriminadores:
public class AnonymousUser { // class code } public class RegisteredUser { // class code }
A continuación se muestran documentos de muestra creados a partir de los POJO anteriores en una única colección de MongoDB:
{ "_cls": "AnonymousUser", "_id": ObjectId("<Object ID>"), ... } { "_cls": "RegisteredUser", "_id": ObjectId("<Object ID>"), ... }
Configuración avanzada
Tipos abstractos o de interfaz en propiedades
Para serializar un POJO que incluye propiedades de tipo de interfaz o clase abstracta, debe especificar discriminadores en el tipo y todos sus subtipos o implementaciones.
Supongamos que definió un POJO que hace referencia a una clase abstracta User en uno de sus campos de la siguiente manera:
public class UserRecordPojo { private User user; // ... }
Si la clase abstracta User tiene subclases FreeUser y SubscriberUser, puede agregar su POJO y clases abstractas a su CodecRegistry de la siguiente manera:
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 obtener más información sobre cómo especificar discriminadores, consulte la sección de esta guía sobre Discriminadores.
POJOs sin constructores sin argumentos
El POJO Codecs llama por defecto al constructor vacío, sin argumentos. Para especificar un constructor diferente, debe realizar lo siguiente en su POJO:
pasa la configuración
ANNOTATION_CONVENTIONa tuClassModelBuilderIdentificar el constructor usando la anotación
BsonCreator
Para ver un ejemplo de cómo ANNOTATION_CONVENTION configurar, consulte la sugerencia ANNOTATION_CONVENTION. Para ver un ejemplo de la BsonCreator anotación, consulte el código POJO de ejemplo.
Personalización de la serialización
De forma predeterminada, ClassModelBuilder intenta serializar todas las propiedades no nulas del POJO. Si el valor de una propiedad es null, la implementación predeterminada de PropertySerialization omite ese campo.
Puede personalizar el comportamiento de serialización de POJO realizando una de las siguientes acciones:
Use la
@BsonIgnoreanotación para una propiedad para omitir siempre la serialización. Asegúrese de habilitar las anotaciones con las convenciones adecuadas.Cree una clase personalizada que sobrescriba el método
shouldSerialize()de la interfazPropertySerialization. Especifique su implementación personalizada enPropertyModelBuilder, a la que puede acceder desdeClassModelBuilder.
Para obtener más información sobre cómo utilizar la @BsonIgnore anotación en un POJO, consulte la sección de esta guía sobre Anotaciones.
El siguiente código de muestra muestra una clase personalizada que implementa la interfaz PropertySerialization para anular las condiciones predeterminadas para determinar si se debe serializar un campo:
public class CourteousAgeSerialization implements PropertySerialization<Integer> { public boolean shouldSerialize(Integer value) { return (value < 30); } }
La clase anterior especifica que cualquier entero mayor que 29 no se serializa y, por lo tanto, no se incluye en el documento de MongoDB. Supongamos que aplicaste este comportamiento de serialización personalizada al siguiente POJO de muestra:
public class BirthdayInvitation { private String name; private Integer age; private LocalDateTime eventDateTime; // ... }
Puede especificar la serialización personalizada agregando la instancia CourteousAgeSerialization a la PropertyModelBuilder de la propiedad ClassModel asociada con el campo age usando el siguiente 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));
Si inserta un POJO que contiene un valor mayor que 29 en el campo age, el documento serializado lo omite. La declaración del POJO y el documento resultante son similares a los siguientes:
// constructor with parameters for name, age, and eventDateTime, respectively BirthdayInvitation invitation = new BirthdayInvitation( "Galadriel", 7582, LocalDateTime.of(2021, Month.JANUARY, 18, 30, 0) );
Dado que el valor del campo age es mayor que 29, el documento serializado se parece al siguiente:
{ "_id" : ObjectId("..."), "eventDateTime" : ..., "name" : "Galadriel" }
Soporte para genéricos
Puede utilizar el POJO Codec para serializar clases que contengan propiedades genéricas si cumplen con los siguientes criterios:
Contienen únicamente parámetros de tipo concreto delimitados
Si este o alguno de sus campos son parte de una jerarquía de clases, el POJO de nivel superior no contiene ningún parámetro de tipo.
El ClassModelBuilder inspecciona y guarda los parámetros de tipo concreto para evitar el borrado de tipos. No puede serializar clases que contengan propiedades genéricas sin parámetros de tipo concreto, ya que la JVM elimina la información de estos.
Para guardar parámetros de tipo, puede implementar la interfaz PropertyCodecProvider para especificarlos para tipos genéricos definidos en un POJO. Los siguientes fragmentos de código muestran un ejemplo de implementación de PropertyCodecProvider que añade compatibilidad de serialización a la clase Optional de Guava.
Supongamos que desea serializar el siguiente POJO con Optional campos:
public class ApplicationUser { private Optional<Address> optionalAddress; private Optional<Subscription> optionalSubscription; // ... }
Puede usar la siguiente implementación de PropertyCodecProvider para recuperar su códec personalizado. Esta implementación utiliza la interfaz TypeWithTypeParameters para acceder a la información de 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 su OptionalPropertyCodecProvider en su PojoCodecProvider y el paquete que contiene su POJO de la siguiente manera:
CodecProvider pojoCodecProvider = PojoCodecProvider.builder() .register("org.example.pojos") .register(new OptionalPropertyCodecProvider()) .build();
Para obtener más información sobre los métodos y clases mencionados en esta sección, consulte la siguiente documentación de API:
Para obtener más información sobre genéricos y parámetros de tipo, consulte la guía del lenguaje Java sobre cómo invocar e instanciar un tipo genérico.
Compatibilidad con tipos de enumeración
En las versiones 4.5 y posteriores del controlador, el PojoCodecProvider ya no incluye un códec para convertir los tipos enum. Asegúrese de registrar un códec para los tipos enum si lo necesita, como el que se encuentra en el registro de códecs predeterminado.
Consulte la documentación del registro de códecs predeterminado para obtener más información sobre cómo registrar los códecs que incluye.
Preguntas frecuentes
Esta sección responde preguntas que pueden surgir al definir la lógica de conversión de datos.
¿Puedo controlar la LocalDateserialización de?
Sí, el controlador Java v3.7 agrega soporte nativo para JSR-310 Instant, LocalDate y LocalDateTime.
¿Puedo serializar un java.util.Date como una cadena en formato aaaa-mm-dd?
Sí, puedes compilar tu propio códec para esta clase y añadirlo al registro.
Agregue el códec al primero en la lista de proveedores, antes del registro de códec predeterminado y antes de PojoCodecProvider:
CodecRegistry registry = CodecRegistries.fromRegistries( CodecRegistries.fromCodecs( new MyDateAsStringCodec()), MongoClientSettings.getDefaultCodecRegistry(), fromProviders(pojoCodecProvider));
¿Puedo hacer que los POJO lean/escriban directamente en el campo y no utilizar los captadores/establecedores en absoluto?
Puede configurar PojoCodecProvider para utilizar SET_PRIVATE_FIELDS_CONVENTION, que establece un campo privado a través de la reflexión si no hay ningún establecedor público disponible.
¿Cómo especifico el nombre de la colección para una clase POJO específica? ¿Hay alguna anotación?
No hay anotación. Recomendamos agregar una cadena estática a la clase, como se muestra en el siguiente código:
public class Person { public static final String COLLECTION_NAME = "people"; }
El siguiente fragmento especifica el nombre de la colección para una clase POJO particular:
database.getCollection(Person.COLLECTION_NAME, Person.class);