web-dev-qa-db-fra.com

Mappage POJO efficace vers/depuis Java Mongo DBObject à l'aide de Jackson

Bien que similaire à Convertir DBObject en POJO à l'aide du pilote Java MongoDB ma question est différente en ce que je suis {spécifiquement intéressé à utiliser Jackson pour le mappage.

J'ai un objet que je veux convertir en une instance de Mongo DBObject. Je souhaite utiliser le framework JSON de Jackson pour faire le travail.

Une façon de le faire est:

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

Cependant, selon https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance , c'est la pire façon de faire. Donc, je cherche une alternative. Dans l'idéal, j'aimerais pouvoir accéder au pipeline de génération JSON et peupler une instance DBObject à la volée. Cela est possible car, dans mon cas, la cible est une instance BasicDBObject, qui implémente l'interface Map. Ainsi, il devrait s'intégrer facilement dans le pipeline.

Maintenant, je sais que je peux convertir un objet en carte à l'aide de la fonction ObjectMapper.convertValue, puis convertir de manière récursive la carte en une instance BasicDBObject à l'aide du constructeur de carte du type BasicDBObject. Mais, je veux savoir si je peux éliminer la carte intermédiaire et créer directement la BasicDBObject.

Notez que, étant donné que BasicDBObject est essentiellement une carte, la conversion opposée, à savoir une variable scalaire DBObject en POJO, est triviale et devrait être relativement efficace:

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

Enfin, mon POJO n’a pas d’annotations JSON et j’aimerais qu’il en soit ainsi.

18
mark

Vous pouvez probablement utiliser les annotations Mixin pour annoter votre POJO et la variable BasicDBObject (ou DBObject). Les annotations ne posent donc pas de problème. Puisque BasicDBOject est une carte, vous pouvez utiliser @JsonAnySetter sur la méthode put.

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

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

C’est tout ce que je peux trouver puisque je n’ai aucune expérience avec MongoDB Object.

Update:MixIn sont fondamentalement un mécanisme de Jackson permettant d’ajouter des annotations à une classe sans modifier ladite classe. Cela convient parfaitement lorsque vous n'avez pas le contrôle sur la classe que vous souhaitez marshaler (par exemple, si elle provient d'un fichier JAR externe) ou lorsque vous ne souhaitez pas encombrer vos classes d'annotation.

Dans votre cas, vous avez dit que BasicDBObject implémente l'interface Map, de sorte que la classe a la méthode put, telle que définie par l'interface de carte. En ajoutant @JsonAnySetter à cette méthode, vous indiquez à Jackson que chaque fois qu'il trouve une propriété qu'il ne connaît pas après l'introspection de la classe, il utilise la méthode pour l'insérer dans l'objet. La clé est le nom de la propriété et la valeur est bien la valeur de la propriété.

Tout cela combiné fait disparaître la carte intermédiaire, puisque Jackson convertira directement en BasicDBOject car il sait maintenant comment désérialiser cette classe de Json. Avec cette configuration, vous pouvez faire:

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

Notez que je n'ai pas testé cela parce que je ne travaille pas avec MongoDB, il pourrait donc y avoir quelques difficultés. Cependant, j'ai utilisé le même mécanisme pour des cas d'utilisation similaires sans aucun problème. YMMV selon les classes.

11
Pascal Gélinas

Voici un exemple de sérialiseur simple (écrit en scala) de POJO à BsonDocument qui pourrait être utilisé avec la version 3 du pilote Mongo . Le dé-sérialiseur serait un peu plus difficile à écrire.

Créez un objet BsonObjectGenerator qui effectuerait une sérialisation en streaming directement vers Mongo Bson: 

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

Voici le code pour un sérialiseur:

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

Vous pourriez être intéressé à vérifier comment jongo le fait. Il est open source et le code peut être trouvé sur github . Ou vous pouvez aussi simplement utiliser leur bibliothèque. J'utilise un mélange de jongo et de DBObjects quand j'ai besoin de plus de souplesse.

Ils prétendent qu'ils sont (presque) aussi rapides que d’utiliser le pilote Java directement, alors je suppose que leur méthode est efficace.

J'utilise la classe utilitaire de petit assistant ci-dessous qui est inspirée de leur base de code et utilise un mélange de Jongo (le MongoBsonFactory) et de Jackson pour effectuer la conversion entre DBObjects et POJO. Notez que la méthode getDbObject crée une copie complète de DBObject pour la rendre modifiable. Si vous n'avez pas besoin de personnaliser quoi que ce soit, vous pouvez supprimer cette partie et améliorer les performances.

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

Exemple d'utilisation:

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

Je comprends que c’est une très vieille question, mais si on me l’avait posée aujourd’hui, je recommanderais plutôt le prise en charge intégrée de POJO sur le pilote officiel Mongo Java. 

1
Nic Cottrell

Voici une mise à jour de la réponse d’assylias qui ne nécessite pas Jongo et qui est compatible avec les pilotes Mongo 3.x. Il gère également les graphiques d'objet imbriqués, je ne pouvais pas le faire fonctionner avec LazyWritableDBObject qui a été supprimé dans les pilotes mongo 3.x de toute façon.

L'idée est de dire à Jackson comment sérialiser un objet sur un tableau d'octets BSON, puis de désérialiser le tableau d'octets BSON en BasicDBObject. Je suis sûr que vous pouvez trouver une API de bas niveau dans les pilotes mongo-Java si vous souhaitez envoyer les octets BSON directement à la base de données. Vous aurez besoin d’une dépendance de bson4jackson pour que ObjectMapper puisse sérialiser BSON lorsque vous appelez 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);
        }
    }
}
0
gogstad