update query java mongodb jackson mongo-jackson-mapper

query - Asignación eficiente de POJO a/desde Java Mongo DBObject usando Jackson



query mongodb java (5)

Aunque es similar a Convertir DBObject en un POJO utilizando el controlador Java de MongoDB, mi pregunta es diferente en que estoy específicamente interesado en usar Jackson para mapear.

Tengo un objeto que quiero convertir en una instancia de Mongo DBObject. Quiero usar el marco de Jackson JSON para hacer el trabajo.

Una forma de hacerlo es:

DBObject dbo = (DBObject)JSON.parse(m_objectMapper.writeValueAsString(entity));

Sin embargo, de acuerdo con https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance esta es la peor manera de hacerlo. Por lo tanto, estoy buscando una alternativa. Idealmente, me gustaría poder conectar con el canal de generación JSON y rellenar una instancia de DBObject sobre la marcha. Esto es posible, porque el objetivo en mi caso es una instancia de BasicDBObject , que implementa la interfaz del Mapa. Por lo tanto, debe encajar en la tubería fácilmente.

Ahora, sé que puedo convertir un objeto en un mapa usando la función ObjectMapper.convertValue y luego convertir el mapa de forma BasicDBObject una instancia de BasicDBObject usando el constructor de BasicDBObject tipo BasicDBObject . Pero, quiero saber si puedo eliminar el mapa intermedio y crear el objeto BasicDBObject directamente.

Tenga en cuenta que debido a que un objeto BasicDBObject es esencialmente un mapa, la conversión opuesta, es decir, de un DBObject escalar a un POJO es trivial y debería ser bastante eficiente:

DBObject dbo = getDBO(); Class clazz = getObjectClass(); Object pojo = m_objectMapper.convertValue(dbo, clazz);

Por último, mi POJO no tiene ninguna anotación JSON y me gustaría que siga siendo así.


Aquí hay un ejemplo de un simple serializador (escrito en Scala) de POJO a BsonDocument que podría usarse con la versión 3 del controlador Mongo . El des-serializador sería algo más difícil de escribir.

Cree un objeto BsonObjectGenerator que haría una serialización de transmisión a Mongo Bson directamente:

val generator = new BsonObjectGenerator mapper.writeValue(generator, POJO) generator.result()

Aquí está el código para un serializador:

class BsonObjectGenerator extends JsonGenerator { sealed trait MongoJsonStreamContext extends JsonStreamContext case class MongoRoot(root: BsonDocument = BsonDocument()) extends MongoJsonStreamContext { _type = JsonStreamContext.TYPE_ROOT override def getCurrentName: String = null override def getParent: MongoJsonStreamContext = null } case class MongoArray(parent: MongoJsonStreamContext, arr: BsonArray = BsonArray()) extends MongoJsonStreamContext { _type = JsonStreamContext.TYPE_ARRAY override def getCurrentName: String = null override def getParent: MongoJsonStreamContext = parent } case class MongoObject(name: String, parent: MongoJsonStreamContext, obj: BsonDocument = BsonDocument()) extends MongoJsonStreamContext { _type = JsonStreamContext.TYPE_OBJECT override def getCurrentName: String = name override def getParent: MongoJsonStreamContext = parent } private val root = MongoRoot() private var node: MongoJsonStreamContext = root private var fieldName: String = _ def result(): BsonDocument = root.root private def unsupported(): Nothing = throw new UnsupportedOperationException override def disable(f: Feature): JsonGenerator = this override def writeStartArray(): Unit = { val array = new BsonArray node match { case MongoRoot(o) => o.append(fieldName, array) fieldName = null case MongoArray(_, a) => a.add(array) case MongoObject(_, _, o) => o.append(fieldName, array) fieldName = null } node = MongoArray(node, array) } private def writeBsonValue(value: BsonValue): Unit = node match { case MongoRoot(o) => o.append(fieldName, value) fieldName = null case MongoArray(_, a) => a.add(value) case MongoObject(_, _, o) => o.append(fieldName, value) fieldName = null } private def writeBsonString(text: String): Unit = { writeBsonValue(BsonString(text)) } override def writeString(text: String): Unit = writeBsonString(text) override def writeString(text: Array[Char], offset: Int, len: Int): Unit = writeBsonString(new String(text, offset, len)) override def writeString(text: SerializableString): Unit = writeBsonString(text.getValue) private def writeBsonFieldName(name: String): Unit = { fieldName = name } override def writeFieldName(name: String): Unit = writeBsonFieldName(name) override def writeFieldName(name: SerializableString): Unit = writeBsonFieldName(name.getValue) override def setCodec(oc: ObjectCodec): JsonGenerator = this override def useDefaultPrettyPrinter(): JsonGenerator = this override def getFeatureMask: Int = 0 private def writeBsonBinary(data: Array[Byte]): Unit = { writeBsonValue(BsonBinary(data)) } override def writeBinary(bv: Base64Variant, data: Array[Byte], offset: Int, len: Int): Unit = { val res = if (offset != 0 || len != data.length) { val subset = new Array[Byte](len) System.arraycopy(data, offset, subset, 0, len) subset } else { data } writeBsonBinary(res) } override def writeBinary(bv: Base64Variant, data: InputStream, dataLength: Int): Int = unsupported() override def isEnabled(f: Feature): Boolean = false override def writeRawUTF8String(text: Array[Byte], offset: Int, length: Int): Unit = writeBsonString(new String(text, offset, length, "UTF-8")) override def writeRaw(text: String): Unit = unsupported() override def writeRaw(text: String, offset: Int, len: Int): Unit = unsupported() override def writeRaw(text: Array[Char], offset: Int, len: Int): Unit = unsupported() override def writeRaw(c: Char): Unit = unsupported() override def flush(): Unit = () override def writeRawValue(text: String): Unit = writeBsonString(text) override def writeRawValue(text: String, offset: Int, len: Int): Unit = writeBsonString(text.substring(offset, offset + len)) override def writeRawValue(text: Array[Char], offset: Int, len: Int): Unit = writeBsonString(new String(text, offset, len)) override def writeBoolean(state: Boolean): Unit = { writeBsonValue(BsonBoolean(state)) } override def writeStartObject(): Unit = { node = node match { case p@MongoRoot(o) => MongoObject(null, p, o) case p@MongoArray(_, a) => val doc = new BsonDocument a.add(doc) MongoObject(null, p, doc) case p@MongoObject(_, _, o) => val doc = new BsonDocument val f = fieldName o.append(f, doc) fieldName = null MongoObject(f, p, doc) } } override def writeObject(pojo: scala.Any): Unit = unsupported() override def enable(f: Feature): JsonGenerator = this override def writeEndArray(): Unit = { node = node match { case MongoRoot(_) => unsupported() case MongoArray(p, a) => p case MongoObject(_, _, _) => unsupported() } } override def writeUTF8String(text: Array[Byte], offset: Int, length: Int): Unit = writeBsonString(new String(text, offset, length, "UTF-8")) override def close(): Unit = () override def writeTree(rootNode: TreeNode): Unit = unsupported() override def setFeatureMask(values: Int): JsonGenerator = this override def isClosed: Boolean = unsupported() override def writeNull(): Unit = { writeBsonValue(BsonNull()) } override def writeNumber(v: Int): Unit = { writeBsonValue(BsonInt32(v)) } override def writeNumber(v: Long): Unit = { writeBsonValue(BsonInt64(v)) } override def writeNumber(v: BigInteger): Unit = unsupported() override def writeNumber(v: Double): Unit = { writeBsonValue(BsonDouble(v)) } override def writeNumber(v: Float): Unit = { writeBsonValue(BsonDouble(v)) } override def writeNumber(v: BigDecimal): Unit = unsupported() override def writeNumber(encodedValue: String): Unit = unsupported() override def version(): Version = unsupported() override def getCodec: ObjectCodec = unsupported() override def getOutputContext: JsonStreamContext = node override def writeEndObject(): Unit = { node = node match { case p@MongoRoot(_) => p case MongoArray(p, a) => unsupported() case MongoObject(_, p, _) => p } } }


Aquí hay una actualización de la respuesta de Assylias que no requiere Jongo y es compatible con los controladores Mongo 3.x. También maneja gráficos de objetos anidados, no pude hacer que funcionara con LazyWritableDBObject que se ha eliminado de todos modos en los controladores mongo 3.x.

La idea es decirle a Jackson cómo serializar un objeto a una matriz de bytes BSON, y luego deserializar la matriz de bytes BSON en BasicDBObject . Estoy seguro de que puede encontrar alguna API de bajo nivel en los controladores mongo-java si desea enviar los bytes BSON directamente a la base de datos. Necesitará una dependencia de bson4jackson para que ObjectMapper writeValues(ByteArrayOutputStream, Object) BSON cuando llame a writeValues(ByteArrayOutputStream, Object) :

import com.fasterxml.jackson.databind.ObjectMapper; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import de.undercouch.bson4jackson.BsonFactory; import de.undercouch.bson4jackson.BsonParser; import org.bson.BSON; import org.bson.BSONObject; import java.io.ByteArrayOutputStream; import java.io.IOException; public class MongoUtils { private static ObjectMapper mapper; static { BsonFactory bsonFactory = new BsonFactory(); bsonFactory.enable(BsonParser.Feature.HONOR_DOCUMENT_LENGTH); mapper = new ObjectMapper(bsonFactory); } public static DBObject getDbObject(Object o) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); mapper.writeValue(baos, o); BSONObject decode = BSON.decode(baos.toByteArray()); return new BasicDBObject(decode.toMap()); } catch (IOException e) { throw new RuntimeException(e); } } }


Entiendo que esta es una pregunta muy antigua, pero si la hiciera hoy, recomendaría el soporte POJO incorporado en el controlador oficial de Mongo Java.


Podrías estar interesado en comprobar cómo lo hace Jongo. Es de código abierto y el código se puede encontrar en github . O también puedes simplemente usar su biblioteca. Utilizo una combinación de jongo y de DBObject lisos cuando necesito más flexibilidad.

Afirman que son (casi) tan rápidos como usar el controlador Java directamente, así que supongo que su método es eficiente.

Utilizo la pequeña clase de utilidad de ayuda que se encuentra debajo de la base de código y utiliza una combinación de Jongo ( MongoBsonFactory ) y Jackson para convertir entre DBObjects y POJOs. Tenga en cuenta que el método getDbObject hace una copia profunda del DBObject para hacerlo editable: si no necesita personalizar nada, puede eliminar esa parte y mejorar el rendimiento.

import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.introspect.VisibilityChecker; import com.mongodb.BasicDBObject; import com.mongodb.DBEncoder; import com.mongodb.DBObject; import com.mongodb.DefaultDBEncoder; import com.mongodb.LazyWriteableDBObject; import java.io.ByteArrayOutputStream; import java.io.IOException; import org.bson.LazyBSONCallback; import org.bson.io.BasicOutputBuffer; import org.bson.io.OutputBuffer; import org.jongo.marshall.jackson.bson4jackson.MongoBsonFactory; public class JongoUtils { private final static ObjectMapper mapper = new ObjectMapper(MongoBsonFactory.createFactory()); static { mapper.setVisibilityChecker(VisibilityChecker.Std.defaultInstance().withFieldVisibility( JsonAutoDetect.Visibility.ANY)); } public static DBObject getDbObject(Object o) throws IOException { ObjectWriter writer = mapper.writer(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); writer.writeValue(baos, o); DBObject dbo = new LazyWriteableDBObject(baos.toByteArray(), new LazyBSONCallback()); //turn it into a proper DBObject otherwise it can''t be edited. DBObject result = new BasicDBObject(); result.putAll(dbo); return result; } public static <T> T getPojo(DBObject o, Class<T> clazz) throws IOException { ObjectReader reader = mapper.reader(clazz); DBEncoder dbEncoder = DefaultDBEncoder.FACTORY.create(); OutputBuffer buffer = new BasicOutputBuffer(); dbEncoder.writeObject(buffer, o); T pojo = reader.readValue(buffer.toByteArray()); return pojo; } }

Uso de la muestra:

Pojo pojo = new Pojo(...); DBObject o = JongoUtils.getDbObject(pojo); //you can customise it if you want: o.put("_id", pojo.getId());


Probablemente pueda usar las anotaciones de Mixin para anotar su POJO y el objeto BasicDBObject (o DBObject ), por lo que las anotaciones no son un problema. Ya que BasicDBOject es un mapa, puede usar @JsonAnySetter en el método put.

m_objectMapper.addMixInAnnotations(YourMixIn.class, BasicDBObject.class); public interface YourMixIn.class { @JsonAnySetter void put(String key, Object value); }

Esto es todo lo que puedo hacer ya que no tengo experiencia con MongoDB Object.

Actualización: MixIn es básicamente un mecanismo de Jackson para agregar anotaciones a una clase sin modificar dicha clase. Este es un ajuste perfecto cuando no tiene control sobre la clase que desea ordenar (como cuando proviene de un contenedor externo) o cuando no quiere saturar sus clases con anotaciones.

En su caso aquí, usted dijo que BasicDBObject implementa la interfaz del Map , por lo que esa clase tiene el método put , como lo define la interfaz del mapa. Al agregar @JsonAnySetter a ese método, le dice a Jackson que siempre que encuentre una propiedad que no sepa después de la introspección de la clase, use el método para insertar la propiedad en el objeto. La clave es el nombre de la propiedad y el valor es, bueno, el valor de la propiedad.

Todo esto combinado hace que el mapa intermedio desaparezca, ya que Jackson se convertirá directamente a BasicDBOject porque ahora sabe cómo deserializar esa clase de Json. Con esa configuración, puedes hacer:

DBObject dbo = m_objectMapper.convertValue(pojo, BasicDBObject.class);

Tenga en cuenta que no he probado esto porque no trabajo con MongoDB, por lo que puede haber algunos cabos sueltos. Sin embargo, he usado el mismo mecanismo para casos de uso similares sin ningún problema. YMMV en función de las clases.