2013-04-03 3 views
13

Sebbene simile a Convert DBObject to a POJO using MongoDB Java Driver la mia domanda sia diversa in quanto sono nello specifico interessato a utilizzare Jackson per la mappatura.Mappatura POJO efficiente da/per Java Mongo DBObject utilizzando Jackson

devo un oggetto che voglio convertire a un'istanza Mongo DBOBJECT. Voglio usare il framework Jackson JSON per fare il lavoro.

Un modo per farlo è:

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

Tuttavia, secondo https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance questo è il modo peggiore per andare. Quindi, sto cercando un'alternativa. Idealmente, vorrei essere in grado di agganciare nella pipeline generazione JSON e popolare un'istanza DBObject al volo. Questo è possibile, perché il bersaglio nel mio caso è un esempio BasicDBObject, che implementa l'interfaccia Map. Quindi, dovrebbe adattarsi facilmente alla pipeline.

Ora, so che posso convertire un oggetto mappa utilizzando la funzione ObjectMapper.convertValue e poi ricorsivamente convertire la mappa per un'istanza BasicDBObject utilizzando la mappa del costruttore del tipo BasicDBObject. Ma, voglio sapere se posso eliminare la mappa intermedia e creare direttamente lo BasicDBObject.

nota, che a causa di un BasicDBObject è essenzialmente una mappa, la conversione opposta, vale a dire da uno scalare DBObject ad un POJO è banale e dovrebbe essere abbastanza efficiente:

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

Infine, il mio POJO non hanno alcun Annotazioni JSON e vorrei che continuasse in questo modo

risposta

9

È possibile utilizzare annotazioni Mixin per annotare il POJO e lo BasicDBObject (o DBObject), quindi le annotazioni non rappresentano un problema. Dal momento che BasicDBOject è una mappa, è possibile utilizzare @JsonAnySetter sul metodo put.

m_objectMapper.addMixInAnnotations(YourMixIn.class, BasicDBObject.class); 

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

Questo è tutto ciò che posso venire visto che non ho esperienza con Oggetto MongoDB.

Aggiornamento:MixIn sono fondamentalmente un meccanismo di Jackson per aggiungere annotazioni a una classe senza modificare detta classe. Questa è una misura perfetta quando non si ha il controllo sulla classe che si desidera maresciallo (come quando si tratta di un vaso da esterno) o quando non si vuole ingombrare le vostre classi con annotazione.

Nel tuo caso, hai detto che BasicDBObject implementa l'interfaccia Map, in modo che la classe abbia il metodo put, come definito dall'interfaccia della mappa. Aggiungendo @JsonAnySetter a quel metodo, dici a Jackson che ogni volta che trova una proprietà che non conosce dopo l'introspezione della classe usa il metodo per inserire la proprietà nell'oggetto. La chiave è il nome della proprietà e il valore è, beh, il valore della proprietà.

Tutto questo combinato fa scomparire la mappa intermedia, poiché Jackson si convertirà direttamente nello BasicDBOject perché ora sa come deserializzare quella classe da Json. Con tale configurazione, si può fare:

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

Si noti che non ho ancora testato questo perché io non lavoro con MongoDB, quindi ci potrebbero essere alcuni in sospeso.Tuttavia, ho usato lo stesso meccanismo per casi d'uso simili senza alcun problema. YMMV a seconda delle classi.

+0

Potrebbe elaborare? – mark

+0

@ Mark Ho aggiornato la risposta con spiegazioni su mixins e JsonAnySetter. Qualcos'altro che desideri sapere? –

+0

Questo è molto promettente. Lo controllerò sicuramente. Una domanda: c'è un modo per dire a Jackson di non controllare la presenza della proprietà e usare sempre il metodo put? Non voglio pagare il costo di una riflessione inutile. – mark

1

Si potrebbe essere intereted nel controllo come jongo lo fa. È open source e il codice può essere trovato su github. O potresti anche semplicemente usare la loro biblioteca. Io uso un mix di jongo e plain DBObject s quando ho bisogno di più flessibilità.

Essi affermano che essi sono (quasi) più velocemente utilizzando il driver Java direttamente quindi suppongo loro metodo è efficiente.

Io uso la piccola classe di utilità di supporto di seguito che è ispirata dalla loro base di codice e utilizza un mix di Jongo (il) e Jackson per la conversione tra DBObjects e POJO. Si noti che il metodo getDbObject fa un copia completa del DBOBJECT per renderlo modificabile - se non necessario personalizzare qualsiasi cosa è possibile rimuovere quella parte e migliorare le prestazioni.

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; 
    } 
} 

utilizzo Esempio:

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

Jongo dovrà aspettare, non sono in grado di aggiornare Jackson alla versione 2. Bloccato con 1.9.x, ma controllerò il loro approccio. – mark

+0

@mark A parte il MongoBsonFactory che uso solo come è (e che dipende da 2 o 3 altre classi al massimo), la loro approah si riduce al codice qui sopra - potrebbe essere completamente compatibile con Jackson 1.9 - Non sono sicuro. – assylias

0

Ecco un aggiornamento alla risposta assylias' che non richiede Jongo ed è compatibile con i driver 3.x Mongo. Si occupa anche di grafi di oggetti nidificati, non ho potuto ottenere che per lavorare con LazyWritableDBObject che è stato rimosso nei driver di Mongo 3.x comunque.

L'idea è di dire a Jackson come serializzare un oggetto su un array di byte BSON e quindi deserializzare l'array di byte BSON in BasicDBObject. Sono sicuro che puoi trovare alcune API di basso livello nei driver mongo-java se vuoi spedire i byte BSON direttamente al database. Avrete bisogno di una dipendenza per bson4jackson in ordine per ObjectMapper serializzare BSON quando si chiama 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); 
     } 
    } 
} 
2

Ecco un esempio di un semplice serializzatore (scritto in Scala) da POJO a BsonDocument che potrebbero essere utilizzati con la versione 3 di Mongo driver. Il de-serializer sarebbe un po 'più difficile da scrivere.

Creare un oggetto BsonObjectGenerator, che avrebbe fatto una serializzazione di streaming per Mongo BSON direttamente:

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

Ecco il codice per un serializzatore:

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 [email protected](o) => 
     MongoObject(null, p, o) 
     case [email protected](_, a) => 
     val doc = new BsonDocument 
     a.add(doc) 
     MongoObject(null, p, doc) 
     case [email protected](_, _, 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 [email protected](_) => p 
     case MongoArray(p, a) => unsupported() 
     case MongoObject(_, p, _) => p 
    } 
    } 
} 
0

Capisco che questa è una domanda molto antica, ma se richiesto oggi raccomanderei invece lo built-in POJO support sul driver ufficiale Mongo Java.