20

虽然类似于Convert DBObject to a POJO using MongoDB Java Driver我的问题是不同的,因为我对使用 Jackson 进行映射特别感兴趣。

我有一个要转换为 Mongo DBObject 实例的对象。我想使用 Jackson JSON 框架来完成这项工作。

一种方法是:

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

但是,根据https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance ,这是最糟糕的方法。所以,我正在寻找替代方案。理想情况下,我希望能够连接到 JSON 生成管道并动态填充DBObject实例。这是可能的,因为在我的例子中,目标是一个BasicDBObject实例,它实现了 Map 接口。因此,它应该很容易融入管道。

现在,我知道我可以使用该ObjectMapper.convertValue函数将对象转换为 Map,然后BasicDBObject使用该类型的映射构造函数递归地将映射转换为实例BasicDBObject。但是,我想知道是否可以消除中间映射并BasicDBObject直接创建。

请注意,因为 aBasicDBObject本质上是一个映射,所以相反的转换,即从标量DBObject到 POJO 是微不足道的并且应该非常有效:

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

最后,我的 POJO 没有任何 JSON 注释,我希望它保持这种方式。

4

5 回答 5

11

您可能可以使用 Mixin 注释来注释您的 POJO 和BasicDBObject(or DBObject),因此注释不是问题。既然BasicDBOject是地图,就可以使用@JsonAnySetter放在put方法上。

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

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

这就是我所能想到的,因为我对 MongoDB 对象的经验为零。

更新: MixIn基本上是一种杰克逊机制,可以在不修改所述类的情况下向类添加注释。当您无法控制要编组的类时(例如当它来自外部 jar 时)或者当您不想用注释使类混乱时,这是一个完美的选择。

在您的情况下,您说BasicDBObject实现了Map接口,因此该类具有put由 map 接口定义的方法。通过将@JsonAnySetter添加到该方法,您可以告诉Jackson,每当他在自省后发现一个他不知道的属性时,就可以使用该方法将该属性插入到对象中。键是属性的名称,值是属性的值。

所有这些结合起来使中间映射消失了,因为 Jackson 将直接转换为,BasicDBOject因为它现在知道如何从 Json 反序列化该类。使用该配置,您可以执行以下操作:

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

请注意,我没有对此进行测试,因为我不使用 MongoDB,所以可能会有一些松散的结局。但是,我对类似的用例使用了相同的机制,没有任何问题。YMMV 取决于类。

于 2013-04-03T15:45:51.273 回答
4

这是一个从 POJO 到 BsonDocument 的简单序列化程序(用 Scala 编写)的示例,可以与 Mongo driver 版本 3 一起使用。反序列化器会更难编写。

创建一个BsonObjectGenerator可以直接对 Mongo Bson 进行流式序列化的对象:

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

这是序列化程序的代码:

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
    }
  }
}
于 2016-10-10T12:47:22.723 回答
2

你可能有兴趣检查jongo是如何做到的。它是开源的,代码可以在github上找到。或者您也可以简单地使用他们的库。DBObject当我需要更大的灵活性时,我会混合使用 jongo 和 plain 。

他们声称他们(几乎)与直接使用 Java 驱动程序一样快,所以我认为他们的方法是有效的。

我使用下面的小助手实用程序类,它的灵感来自他们的代码库,并混合使用 Jongo (the MongoBsonFactory) 和 Jackson 在 DBObjects 和 POJOs 之间进行转换。请注意,该getDbObject方法对 DBObject 进行了深层复制以使其可编辑 - 如果您不需要自定义任何内容,则可以删除该部分并提高性能。

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

示例用法:

Pojo pojo = new Pojo(...);
DBObject o = JongoUtils.getDbObject(pojo);
//you can customise it if you want:
o.put("_id", pojo.getId());
于 2013-04-03T20:03:00.420 回答
2

我知道这是一个非常古老的问题,但是如果今天被问到,我会推荐官方 Mongo Java 驱动程序上的内置 POJO 支持。

于 2017-12-13T10:51:17.733 回答
0

这是对 assylias 答案的更新,不需要 Jongo 并且与 Mongo 3.x 驱动程序兼容。它还处理嵌套对象图,无论如何我都无法使用LazyWritableDBObject它在 mongo 3.x 驱动程序中已删除。

这个想法是告诉杰克逊如何将对象序列化为 BSON 字节数组,然后将 BSON 字节数组反序列化为BasicDBObject. 如果您想将 BSON 字节直接发送到数据库,我相信您可以在 mongo-java-drivers 中找到一些低级 API。您需要对bson4jackson的依赖,以便在ObjectMapper调用时序列化 BSON 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);
        }
    }
}
于 2016-05-20T09:33:17.657 回答