虽然与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
。
注意,因为BasicDBObject
本质上是一个地图,所以相反的转换,即从标量DBObject
到POJO是微不足道的,应该非常有效:
DBObject dbo = getDBO();
Class clazz = getObjectClass();
Object pojo = m_objectMapper.convertValue(dbo, clazz);
最后,我的POJO没有任何JSON注释,我希望它能保持这种状态。
答案 0 :(得分:11)
您可以使用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基本上是一种杰克逊机制,可以在不修改所述类的情况下向类中添加注释。如果您无法控制要编组的类(例如,当它来自外部jar)或者您不希望使用注释使类混乱时,这是完美的选择。
在这种情况下,您说BasicDBObject
实现了Map
接口,因此该类具有方法put
,由地图接口定义。通过向该方法添加@JsonAnySetter,您可以告诉Jackson,每当他在类内省后发现他不知道的属性时,使用该方法将属性插入到对象中。关键是属性的名称,值是属性的值。
所有这些组合使得中间地图消失,因为Jackson将直接转换为BasicDBOject
,因为它现在知道如何从Json反序列化该类。使用该配置,您可以执行以下操作:
DBObject dbo = m_objectMapper.convertValue(pojo, BasicDBObject.class);
请注意,我没有对此进行测试,因为我不使用MongoDB,因此可能会有一些松散的结果。但是,我对相似的用例使用了相同的机制,没有任何问题。 YMMV取决于班级。
答案 1 :(得分:3)
以下是从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
}
}
}
答案 2 :(得分:2)
您可能会检查jongo如何做到这一点。它是开源的,代码可以在github找到。或者您也可以简单地使用他们的库。当我需要更多的灵活性时,我会使用jongo和plain DBObject
的混合。
他们声称他们(几乎)与直接使用Java驱动程序一样快,所以我认为他们的方法是有效的。
我使用下面的小助手实用程序类,它受到代码库的启发,并使用Jongo(MongoBsonFactory
)和Jackson的混合来在DBObjects和POJO之间进行转换。请注意,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());
答案 3 :(得分:1)
我知道这是一个非常古老的问题,但如果今天被问到,我会建议在官方的Mongo Java驱动程序上使用built-in POJO support。
答案 4 :(得分:0)
以下是对assylias'答案的更新,该答案不需要Jongo并且与Mongo 3.x驱动程序兼容。它还处理嵌套的对象图,我无法使用已经在mongo 3.x驱动程序中删除的LazyWritableDBObject
。
这个想法是告诉Jackson如何将对象序列化为BSON字节数组,然后将BSON字节数组反序列化为BasicDBObject
。如果你想将BSON字节直接发送到数据库,我相信你可以在mongo-java-drivers中找到一些低级API。当您致电ObjectMapper
时,您需要依赖bson4jackson才能让writeValues(ByteArrayOutputStream, Object)
序列化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);
}
}
}