使用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驱动程序一样快,因此我认为它们的方法是有效的。
我使用下面的小助手实用程序类,它受到了代码库的启发,并使用MongoBsonFactory
( MongoBsonFactory
)和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); } } }