webentwicklung-frage-antwort-db.com.de

Effizientes POJO-Mapping zu/von Java Mongo DBObject mit Jackson

Obwohl ähnlich wie Konvertieren von DBObject in ein POJO mit MongoDB Java Driver ist meine Frage insofern anders, als ich speziell daran interessiert ist, Jackson für das Mapping zu verwenden.

Ich habe ein Objekt, das ich in eine Mongo DBObject-Instanz konvertieren möchte. Ich möchte das Jackson JSON-Framework verwenden, um die Arbeit zu erledigen.

Eine Möglichkeit, dies zu tun, ist:

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

Gemäß https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance ist dies jedoch der ungünstigste Weg. Also suche ich nach einer Alternative. Idealerweise möchte ich in der Lage sein, sich in die JSON-Generierungspipeline einzuhaken und eine DBObject-Instanz schnell zu füllen. Dies ist möglich, da das Ziel in meinem Fall eine BasicDBObject-Instanz ist, die die Map-Schnittstelle implementiert. Es sollte also problemlos in die Pipeline passen.

Jetzt weiß ich, dass ich ein Objekt mithilfe der ObjectMapper.convertValue-Funktion in Map konvertieren und dann die Map rekursiv in eine BasicDBObject-Instanz konvertieren kann, indem der Map-Konstruktor des Typs BasicDBObject verwendet wird. Ich möchte jedoch wissen, ob ich die Zwischen-Map entfernen und die BasicDBObject direkt erstellen kann.

Da eine BasicDBObject im Wesentlichen eine Map ist, ist die entgegengesetzte Konvertierung, nämlich von einer skalaren DBObject zu einem POJO, trivial und sollte ziemlich effizient sein:

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

Schließlich hat mein POJO keine JSON-Anmerkungen, und ich möchte, dass es so bleibt.

18
mark

Sie können wahrscheinlich Mixin-Annotationen verwenden, um Ihr POJO und BasicDBObject (oder DBObject) zu kommentieren, sodass Annotationen kein Problem darstellen. Da BasicDBOject eine Map ist, können Sie @JsonAnySetter für die put-Methode verwenden.

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

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

Das ist alles, was ich mir vorstellen kann, da ich keine Erfahrung mit MongoDB Object habe.

Update:MixIn sind im Grunde ein Jackson-Mechanismus, um einer Klasse Anmerkungen hinzuzufügen, ohne die Klasse zu ändern. Dies ist die perfekte Lösung, wenn Sie keine Kontrolle über die Klasse haben, die Sie marshallen möchten (z. B. von einem externen Glas) oder wenn Sie Ihre Klassen nicht mit Anmerkungen überladen möchten.

In Ihrem Fall hier haben Sie gesagt, dass BasicDBObject die Map-Schnittstelle implementiert, sodass diese Klasse über die Methode put verfügt, die von der Map-Schnittstelle definiert wird. Durch Hinzufügen von @JsonAnySetter zu dieser Methode sagen Sie Jackson, dass er jedes Mal, wenn er eine Eigenschaft findet, die er nach Introspektion der Klasse nicht kennt, die Methode zum Einfügen der Eigenschaft in das Objekt verwendet. Der Schlüssel ist der Name der Eigenschaft und der Wert ist der Wert der Eigenschaft.

Dies alles führt dazu, dass die Zwischenkarte wegfällt, da Jackson direkt in BasicDBOject konvertiert wird, da sie jetzt die Klasse von Json deserialisieren kann. Mit dieser Konfiguration können Sie Folgendes tun:

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

Beachten Sie, dass ich dies nicht getestet habe, weil ich nicht mit MongoDB arbeite. Daher kann es zu losen Enden kommen. Ich habe jedoch den gleichen Mechanismus für ähnliche Anwendungsfälle problemlos verwendet. YMMV abhängig von den Klassen.

11
Pascal Gélinas

Hier ist ein Beispiel eines einfachen Serialisierers (in Scala geschrieben) von POJO bis BsonDocument der mit Version 3 des Mongo-Treibers verwendet werden kann . Der De-Serializer wäre etwas schwieriger zu schreiben.

Erstellen Sie ein BsonObjectGenerator-Objekt, das direkt eine Streaming-Serialisierung für Mongo Bson durchführen würde: 

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

Hier ist der Code für einen Serializer:

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
    }
  }
}
3
Artem Oboturov

Sie könnten daran interessiert sein zu überprüfen, wie jongo es tut. Es ist Open Source und der Code befindet sich auf github . Oder Sie können auch einfach ihre Bibliothek nutzen. Ich verwende eine Mischung aus Jongo und einfachen DBObjects, wenn ich mehr Flexibilität brauche.

Sie behaupten, dass sie (fast) so schnell sind wie die direkte Verwendung des Java-Treibers. Ich nehme an, dass ihre Methode effizient ist.

Ich benutze die kleine Helfer-Utility-Klasse, die von ihrer Codebasis inspiriert ist und eine Mischung aus Jongo (die MongoBsonFactory) und Jackson verwendet, um zwischen DBObjects und POJOs zu konvertieren. Beachten Sie, dass die Methode getDbObject eine tiefe Kopie des DBObjects ausführt, um es bearbeitbar zu machen. Wenn Sie nichts anpassen müssen, können Sie diesen Teil entfernen und die Leistung verbessern.

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

Verwendungsbeispiel:

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

Ich verstehe, dass dies eine sehr alte Frage ist, aber wenn ich heute gefragt würde, würde ich stattdessen die eingebaute POJO-Unterstützung des offiziellen Mongo Java-Treibers empfehlen. 

1
Nic Cottrell

Hier ist ein Update der Antwort von assylias, das kein Jongo erfordert und mit den Mongo 3.x-Treibern kompatibel ist. Es behandelt auch verschachtelte Objektdiagramme. Ich konnte das nicht mit LazyWritableDBObject zusammenarbeiten, das in den 3.x-Treibern von Mongo ohnehin entfernt wurde.

Die Idee ist, Jackson mitzuteilen, wie ein Objekt in ein BSON-Byte-Array serialisiert und anschließend das BSON-Byte-Array in BasicDBObject deserialisiert wird. Ich bin mir sicher, dass Sie in den Mongo-Java-Treibern einige Low-Level-APIs finden können, wenn Sie die BSON-Bytes direkt an die Datenbank senden möchten. Sie benötigen eine Abhängigkeit von bson4jackson , damit ObjectMapper BSON serialisiert, wenn Sie writeValues(ByteArrayOutputStream, Object) aufrufen:

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);
        }
    }
}
0
gogstad