Overview
En esta guía, podrás aprender cómo definir conversiones de datos personalizadas entre BSON y POJO usando el driver 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.
Mostramos cómo especificar tu conversión de datos utilizando el ClassModel and PropertyModel clase. También puedes aprender sobre una personalización más específica en la sección sobre Configuración avanzada.
También mostramos cómo utilizar ayudantes como Convenciones y Anotaciones para especificar acciones de serialización comunes.
Consulta la sección sobre Discriminadores si deseas serializar varias clases POJO en documentos en 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 sólo se utiliza el comportamiento predefinido para convertir datos entre BSON y POJOs, se puede utilizar la configuración automática para el PojoCodecProvider que se muestra en la guía Formatos de datos de documentos: POJOs.
Personaliza 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 POJO individuales
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, consulta el PojoCodecProvider.Builder Documentación de la API.
ClassModel
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. Por defecto, requiere que el POJO tenga un constructor vacío. |
ModelosPropiedad | Contiene una lista de |
IdPropertyModelHolder | Especifica el campo POJO que corresponde al campo |
Clave discriminatoria | 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, consulta la ClassModel Documentación de API.
Para crear una instancia de un ClassModel, utiliza el método ClassModel.builder() y especifica tu clase POJO. El constructor utiliza la reflexión para crear los metadatos requeridos.
ClassModel<Flower> classModel = ClassModel.builder(Flower.class).build();
PropertyModel
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 que se utilizará como clave al serializar a BSON. |
Escribe el nombre | Nombre de la propiedad que se va a usar como clave al deserializar desde BSON. |
Tipo de datos | Contiene una instancia de |
Códec | Especifica un códec que se debe utilizar para codificar o decodificar el campo. opcional. |
Comprobador de serialización | Determina si se debe serializar un valor usando 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 PropertyModel.Builder Documentación de la API.
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 desarrolladores aplican instancias Convention en un orden que puede sobrescribir el comportamiento definido en una aplicada anteriormente.
Puede acceder a las instancias Convention definidas en la librería BSON desde los siguientes campos estáticos en la clase Conventions:
Nombre de campo | Descripción |
|---|---|
| Activa las anotaciones definidas en el paquete |
| 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 a |
| Permite el uso de métodos getter como setters para los campos |
Puede especificar Conventions 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 de un campo, método o clase específico.
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 que se serialice 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
Cuando utilices anotaciones, recuerda especificar el Conventions.ANNOTATION_CONVENTION en tu ClassModelBuilder o PojoCodecProvider.Builder. Por ejemplo:
ClassModel<Product> classModel = ClassModel.builder(Product.class). conventions(Arrays.asList(Conventions.ANNOTATION_CONVENTION)).build();
Las anotaciones en el ejemplo POJO especifican el siguiente comportamiento:
Referencia el POJO con la clave y valor de discriminador especificados, añade el campo
clscon el valor de "AnnotatedProduct" al documento BSON durante 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 instanciar el 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.
Considera una situación en la que almacenas y recuperas datos para una tienda en línea utilizando el POJO de producto del ejemplo anterior. Cuando ofreces una mayor variedad de productos para el almacenar, descubres que necesitas campos adicionales para describirlos. En lugar de mapear todos los campos adicionales al POJO, puedes 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 añade campos adicionales para dimensions y weight a los datos del producto, de modo que los documentos incluyan la siguiente información:
{ "name": "MDB0123", "serialNumber": "62e2...", "dimensions": "3x4x5", "weight": "256g" }
El documento anterior recuperado utilizando 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, utilizar la anotación @BsonRepresentation para convertir entre tipos de datos distintos de String y ObjectId provoca 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 documentos específico. La clave discriminatoria identifica un campo de documento que se utilizará para identificar el esquema. El valor discriminatorio identifica el valor por defecto del campo del documento.
Utilice discriminadores para indicar al CodecProvider qué clase de objeto usar al deserializar a diferentes clases de objetos de la misma colección. Cuando se serializa el POJO a una colección de MongoDB, el codec asociado establece el campo clave-valor del discriminador, a menos que se especifique lo contrario en los datos de propiedad del POJO.
Se puede configurar y habilitar un discriminador en un POJO realizando una de las siguientes acciones:
Usa la anotación
@BsonDiscriminatorpara especificar el discriminador para la clase POJOLlame a
enableDiscriminator(true)en elClassModelBuilderasociado con la clase POJO
Consulta los siguientes ejemplos de clases POJO que contienen @BsonDiscriminator anotaciones y ejemplos de documentos que contienen los campos discriminadores:
public class AnonymousUser { // class code } public class RegisteredUser { // class code }
A continuación se muestra 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.
Supón que definiste un POJO que referenciaba una clase abstracta User en uno de sus campos así:
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, consulta 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:
pase la configuración
ANNOTATION_CONVENTIONa suClassModelBuilderidentifique 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
Por defecto, ClassModelBuilder intenta serializar todas las propiedades no nulas en tu POJO. Si un valor de propiedad es null, la implementación por defecto de PropertySerialization omite ese campo.
Puedes personalizar el comportamiento de serialización de tu POJO realizando una de las siguientes acciones:
Utiliza la anotación
@BsonIgnoreen una propiedad para omitir siempre la serialización. Asegúrate de habilitar las anotaciones utilizando las Convencionesadecuadas.Crea una clase personalizada que sobrescriba el método
shouldSerialize()de la interfazPropertySerialization. Especifique su implementación personalizada en elPropertyModelBuilder, que puede acceder desde elClassModelBuilder.
Para obtener más información sobre cómo utilizar la anotación @BsonIgnore en un POJO, consulta la sección de esta guía sobre Anotaciones.
El siguiente código de ejemplo muestra una clase personalizada que implementa la interfaz PropertySerialization para sobrescribir las condiciones por defecto que determinan 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; // ... }
Puedes especificar la serialización personalizada añadiendo la instancia de CourteousAgeSerialization al PropertyModelBuilder desde la propiedad ClassModel asociada con el campo age utilizando 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 insertas un POJO que contiene un valor mayor que 29 en el campo age, el documento serializado lo omite. La declaración de POJO y el documento resultante se asemejan a lo siguiente:
// 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 de genéricos
Puede utilizar el POJO Codec para serializar clases que contengan propiedades genéricas si cumplen con los siguientes criterios:
Contener solo parámetros de tipo concretos acotados
Si este o cualquiera de sus campos forman 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 la eliminación de tipos. No puede serializar clases que contengan propiedades genéricas sin parámetros de tipo concretos, ya que la JVM remueve la información del parámetro de tipo.
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 la 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 del controlador 4.5 y posteriores, el PojoCodecProvider ya no incluye un códec para convertir tipos de enum. Asegúrate de registrar un códec para los tipos enum si lo necesitas, como el que se encuentra en el registro de códecs por defecto.
Consulta la documentación sobre el registro de códecs por defecto 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 serialización de LocalDate?
Sí, el controlador de Java v3.7 agrega soporte nativo para JSR-310 Instant, LocalDate y LocalDateTime.
¿Puedo serializar un java.util.Date como un string 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 POJOs lean/guarden directamente en el campo y no utilicen los getters/setters 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 en particular? ¿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 snippet especifica el nombre de la colección para una clase POJO en particular:
database.getCollection(Person.COLLECTION_NAME, Person.class);