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

虽然类似于使用MongoDB Java驱动程序将DBObject转换为POJO,但我的问题不同,因为我特别感兴趣使用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

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

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

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

您可以使用Mixin注释来注释您的POJO和BasicDBObject (或DBObject ),因此注释不是问题。 由于BasicDBOject是一个映射,因此可以在put方法上使用@JsonAnySetter

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

这是我能想到的全部,因为我对MongoDB Object没有任何经验。

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

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

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

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

请注意,我没有测试过这个,因为我不使用MongoDB,因此可能会有一些松散的结果。 但是,我对相似的用例使用了相同的机制,没有任何问题。 YMMV取决于class级。

这是一个从POJO到BsonDocument的简单序列化程序(用Scala编写)的示例,它可以与Mongo驱动程序的版本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 } } } 

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

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

我使用下面的小助手实用程序类,它受到了代码库的启发,并使用MongoBsonFactoryMongoBsonFactory )和Jackson的混合来在DBObjects和POJO之间进行转换。 请注意, 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 getPojo(DBObject o, Class 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()); 

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

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

我们的想法是告诉Jackson如何将对象序列化为BSON字节数组,然后将BSON字节数组反序列化为BasicDBObject 。 如果你想将BSON字节直接发送到数据库,我相信你可以在mongo-java-drivers中找到一些低级API。 在调用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); } } }