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 una 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 usar asistentes como Convenciones y Anotaciones para especificar acciones comunes de serialización.
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 te muestra cómo especificar tu lógica de conversión de datos y clases POJO con un PojoCodecProvider. El PojoCodecProvider es una implementación de la interfaz CodecProvider que especifica los Codecs a utilizar en la conversión de datos. Utiliza esta implementación al realizar la conversión de datos entre BSON y POJOs.
Puedes crear una instancia de PojoCodecProvider usando el método PojoCodecProvider.builder(). También puedes encadenar métodos al generador 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 se pueden especificar los POJOs en un paquete llamado "org.example.pojos" y añade el 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 PropertyModel instancias que describen los campos de propiedad del POJO, si se deben convertir los campos y, opcionalmente, Codecs para convertir los campos.
Un ClassModel contiene los siguientes campos:
Nombre de campo | Descripción |
|---|---|
Nombre | El nombre de la clase POJO que se asociará con |
InstanceCreatorFactory | 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. |
Marca de discriminador | Especifica si se debe serializar el discriminador, apagado por defecto. 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. |
Guardar 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 se 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. Puedes establecer una clave y un valor de discriminador personalizado. |
| Especifica el tipo de BSON utilizado para almacenar el valor cuando difiere de la propiedad POJO. Consulta el Ejemplo de error de representación BSON en esta página. |
| Marca una propiedad para que se serialice como la propiedad _id. |
| Marca una propiedad para ignorar. Puedes configurar si deseas serializar y/o deserializar una propiedad. |
| 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 escrituraConvertir entre el campo y valor POJO
namey el campo y valor BSONmodelNameen el documentoConvertir entre el campo
serialNumberde la POJO y el valor del campo_idde BSON y el valor del documento.Omita el campo y el valor
relatedItemscuando convierta datosUtilice el constructor
Product(String name)al instanciar el POJO
Ejemplo de BsonExtraElements
La anotación @BsonExtraElements te permite especificar un campo para deserializar datos de un documento de MongoDB que carece de una mapeo de campo POJO correspondiente. Esto es útil cuando la aplicación necesita trabajar con datos en un esquema parcialmente definido. Puede utilizar esta anotación para acceder a 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 representación BSON
La anotación @BsonRepresentation permite almacenar un campo de clase POJO como un tipo de datos diferente en tu base de datos MongoDB. El código de ejemplo de Product POJO en la sección de Anotaciones de esta página utiliza @BsonRepresentation para almacenar valores String como valores ObjectId 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 Codec personalizado para convertir los valores de purchaseDate del tipo Long a 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; } }
A continuación, agrega una instancia del LongRepresentableCodec a tu CodecRegistry, que contiene un mapeo entre su Codec y el tipo de objeto Realm Java al que se aplica. Para obtener instrucciones sobre cómo registrar tu Codec personalizado con CodecRegistry, consulta la guía Codificar datos con codecs 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 clase abstracta o interfaz, debes especificar discriminadores en el tipo y en 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 sus clases POJO y 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 por defecto llama al constructor vacío, sin argumentos. Para especificar un constructor diferente, debes realizar lo siguiente en tu POJO:
pase la configuración
ANNOTATION_CONVENTIONa suClassModelBuilderidentifique el constructor usando la anotación
BsonCreator
Para ver un ejemplo de cómo configurar el ANNOTATION_CONVENTION, consulta el consejo ANNOTATION_CONVENTION. Para un ejemplo de la anotación BsonCreator, consulta el código POJO de muestra.
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) );
Puesto que el valor del campo age es mayor que 29, el documento serializado es similar 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 los parámetros de tipo, puedes implementar la interfaz PropertyCodecProvider para especificarlos en tipos genéricos definidos en un POJO. Los siguientes fragmentos de código muestran una implementación de ejemplo del PropertyCodecProvider que añade compatibilidad de serialización a la clase Optional de Guava.
Supongamos que desearas serializar el siguiente POJO con Optional campos:
public class ApplicationUser { private Optional<Address> optionalAddress; private Optional<Subscription> optionalSubscription; // ... }
Se puede usar la siguiente implementación de PropertyCodecProvider para recuperar su propio Codec 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; } } }
Registra tu OptionalPropertyCodecProvider en tu PojoCodecProvider y el paquete que contiene tu 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, consulta la guía del lenguaje Java sobre invocar e instanciar un tipo genérico.
Soporte para tipos enumerados
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.
Agrega el códec al primero en la lista de proveedores, antes del registro de códecs por defecto y antes del 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 el PojoCodecProvider para usar el SET_PRIVATE_FIELDS_CONVENTION, lo que establece un campo privado a través de reflexión si no hay un setter público disponible.
¿Cómo especifico el nombre de la colección para una clase POJO en particular? ¿Hay alguna anotación?
No hay anotaciones. Recomendamos agregar una string estática en tu 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);