简体   繁体   English

使用Jackson进行Java Mongo DBObject的高效POJO映射

[英]Efficient POJO mapping to/from Java Mongo DBObject using Jackson

Although similar to Convert DBObject to a POJO using MongoDB Java Driver my question is different in that I am specifically interested in using Jackson for mapping. 虽然类似于使用MongoDB Java驱动程序将DBObject转换为POJO,但我的问题不同,因为我特别感兴趣使用Jackson进行映射。

I have an object which I want to convert to a Mongo DBObject instance. 我有一个对象,我想转换为Mongo DBObject实例。 I want to use the Jackson JSON framework to do the job. 我想使用Jackson JSON框架来完成这项工作。

One way to do so is: 一种方法是:

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

However, according to https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance this is the worst way to go. 但是,根据https://github.com/FasterXML/jackson-docs/wiki/Presentation:-Jackson-Performance,这是最糟糕的方式。 So, I am looking for an alternative. 所以,我正在寻找替代方案。 Ideally, I would like to be able to hook into the JSON generation pipeline and populate a DBObject instance on the fly. 理想情况下,我希望能够挂钩到JSON生成管道并动态填充DBObject实例。 This is possible, because the target in my case is a BasicDBObject instance, which implements the Map interface. 这是可能的,因为在我的情况下,目标是一个BasicDBObject实例,它实现了Map接口。 So, it should fit into the pipeline easily. 因此,它应该很容易适应管道。

Now, I know I can convert an object to Map using the ObjectMapper.convertValue function and then recursively convert the map to a BasicDBObject instance using the map constructor of the BasicDBObject type. 现在,我知道我可以使用ObjectMapper.convertValue函数将对象转换为Map,然后使用BasicDBObject类型的映射构造函数递归地将映射转换为BasicDBObject实例。 But, I want to know if I can eliminate the intermediate map and create the BasicDBObject directly. 但是,我想知道我是否可以消除中间映射并直接创建BasicDBObject

Note, that because a BasicDBObject is essentially a map, the opposite conversion, namely from a scalar DBObject to a POJO is trivial and should be quite efficient: 注意,因为BasicDBObject本质上是一个映射,所以相反的转换,即从标量DBObject到POJO是微不足道的,应该非常有效:

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

Lastly, my POJO do not have any JSON annotations and I would like it to keep this way. 最后,我的POJO没有任何JSON注释,我希望它保持这种方式。

You can probably use Mixin annotations to annotate your POJO and the BasicDBObject (or DBObject ), so annotations is not a problem. 您可以使用Mixin注释来注释您的POJO和BasicDBObject (或DBObject ),因此注释不是问题。 Since BasicDBOject is a map, you can use @JsonAnySetter on the put method. 由于BasicDBOject是一个映射,因此可以在put方法上使用@JsonAnySetter

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

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

This is all I can come up with since I have zero experience with MongoDB Object. 这是我能想到的全部因为我没有使用MongoDB Object的经验。

Update: MixIn are basically a Jackson mechanism to add annotation to a class without modifying said class. 更新: MixIn基本上是一种Jackson机制,可以在不修改所述类的情况下向类添加注释。 This is a perfect fit when you don't have control over the class you want to marshal (like when it's from an external jar) or when you don't want to clutter your classes with annotation. 如果您无法控制要编组的类(例如,当它来自外部jar)或者您不希望使用注释混乱类时,这是完美的选择。

In your case here, you said that BasicDBObject implements the Map interface, so that class has the method put , as defined by the map interface. 在这种情况下,你说BasicDBObject实现了Map接口,因此该类具有put方法,由map接口定义。 By adding @JsonAnySetter to that method, you tell Jackson that whenever he finds a property that he doesn't know after introspection of the class to use the method to insert the property to the object. 通过将@JsonAnySetter添加到该方法,您可以告诉Jackson,每当他在内省类中后发现他不知道的属性时,使用该方法将属性插入到对象中。 The key is the name of the property and the value is, well, the value of the property. 关键是属性的名称,值是属性的值。

All this combined makes the intermediate map go away, since Jackson will directly convert to the BasicDBOject because it now knows how to deserialize that class from Json. 所有这些结合使得中间映射消失,因为Jackson将直接转换为BasicDBOject因为它现在知道如何从Json反序列化该类。 With that configuration, you can do: 使用该配置,您可以:

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

Note that I haven't tested this because I don't work with MongoDB, so there might be some loose ends. 请注意,我没有对此进行测试,因为我不使用MongoDB,因此可能会有一些松散的结果。 However, I have used the same mechanism for similar use cases without any problem. 但是,我对相似的用例使用了相同的机制,没有任何问题。 YMMV depending on the classes. YMMV取决于班级。

Here's an example of a simple serializer (written in Scala) from POJO to BsonDocument which could be used with version 3 of Mongo driver . 这是一个从POJO到BsonDocument的简单序列化程序(用Scala编写)的示例,它可以与Mongo驱动程序的版本3一起使用 The de-serializer would be somewhat more difficult to write. 反序列化器会更难写。

Create a BsonObjectGenerator object which would do a streaming serialization to Mongo Bson directly: 创建一个BsonObjectGenerator对象,它将直接对Mongo Bson进行流式序列化:

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

Here's the code for a 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 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
    }
  }
}

You might be intereted in checking how jongo does it. 你可能会检查jongo是如何做到的 It is open source and the code can be found on github . 它是开源的,代码可以在github上找到。 Or you could also simply use their library. 或者您也可以简单地使用他们的库。 I use a mix of jongo and plain DBObject s when I need more flexibility. 当我需要更多灵活性时,我使用jongo和普通DBObject的混合。

They claim that they are (almost) as fast as using the Java driver directly so I suppose their method is efficient. 他们声称它们(几乎)与直接使用Java驱动程序一样快,所以我认为它们的方法是有效的。

I use the little helper utility class below which is inspired from their code base and uses a mix of Jongo (the MongoBsonFactory ) and Jackson to convert between DBObjects and POJOs. 我使用下面的小助手实用程序类,它受到了代码库的启发,并使用MongoBsonFactoryMongoBsonFactory )和Jackson的混合来在DBObjects和POJO之间进行转换。 Note that the getDbObject method does a deep copy of the DBObject to make it editable - if you don't need to customise anything you can remove that part and improve performance. 请注意, getDbObject方法执行getDbObject的深层副本以使其可编辑 - 如果您不需要自定义任何内容,则可以删除该部分并提高性能。

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

Sample usage: 样品用法:

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

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

Here's an update to assylias' answer that doesn't require Jongo and is compatible with the Mongo 3.x drivers. 这是对assylias'答案的更新,不需要Jongo并且与Mongo 3.x驱动程序兼容。 It also handles nested object graphs, I couldn't get that to work with LazyWritableDBObject which has been removed in the mongo 3.x drivers anyway. 它还处理嵌套的对象图,我无法使用LazyWritableDBObject ,它已经在mongo 3.x驱动程序中删除了。

The idea is to tell Jackson how to serialize an object to a BSON byte array, and then deserialize the BSON byte array into BasicDBObject . 想法是告诉Jackson如何将对象序列化为BSON字节数组,然后将BSON字节数组反序列化为BasicDBObject I'm sure you can find some low level API in the mongo-java-drivers if you want to ship the BSON bytes directly to the database. 如果你想将BSON字节直接发送到数据库,我相信你可以在mongo-java-drivers中找到一些低级API。 You will need a dependency to bson4jackson in order for ObjectMapper to serialize BSON when you call writeValues(ByteArrayOutputStream, Object) : 在调用writeValues(ByteArrayOutputStream, Object)时,您需要依赖bson4jackson以使ObjectMapper序列化BSON:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM