2013-04-03 78 views
13

儘管類似於Convert DBObject to a POJO using MongoDB Java Driver我的問題是不同的,因爲我對特別是有興趣使用Jackson進行映射。使用Jackson的Java Mongo 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註釋,我希望它保持這種方式。

回答

9

您可以使用Mixin註釋來註釋POJO和BasicDBObject(或DBObject),所以註釋不是問題。由於BasicDBOject是一個地圖,因此您可以在放置方法上使用@JsonAnySetter

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取決於類別。

+0

你能否詳細說明一下? – mark

+0

@mark我已經更新了有關MixIns和JsonAnySetter的解釋的答案。還有什麼你想知道的? –

+0

這是非常有前途的。我一定會檢查它。其中一個問題 - 是否有辦法告訴傑克遜不要檢查財產存在並始終使用put方法?我不想支付不必要的反思的代價。 – mark

1

您可能有興趣在jongo做它。它是開源的,代碼可以在github上找到。或者你也可以簡單地使用他們的庫。當我需要更多的靈活性時,我使用jongo和普通DBObject的組合。

他們聲稱它們(幾乎)與直接使用Java驅動程序一樣快,所以我想他們的方法是有效的。

我使用下面的小幫手實用程序類,它受其代碼庫的啓發,並使用Jongo(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()); 
+0

Jongo將不得不等待,我無法將Jackson升級到版本2.停滯在1.9.x,但我會檢查他們的方法。 – mark

+0

@mark除了我剛纔使用的MongoBsonFactory(並且最多依賴2或3個其他類)之外,他們的認可歸結爲上面的代碼 - 它可能與Jackson 1.9完全兼容 - 我不確定。 – assylias

0

這裏有一個更新assylias的回答,不需要Jongo並與蒙戈3.x的驅動程序不兼容。它也處理嵌套的對象圖,我無法得到與LazyWritableDBObject一起工作,無論如何,它已在mongo 3.x驅動程序中刪除。

這個想法是告訴Jackson如何序列化一個對象到BSON字節數組,然後將BSON字節數組反序列化爲BasicDBObject。我敢肯定你可以在mongo-java-drivers中找到一些低級別的API,如果你想把BSON字節直接發送到數據庫。您將需要一個依賴於bson4jackson爲了ObjectMapper當你調用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); 
     } 
    } 
} 
2

下面是BsonDocument 從POJO簡單的串行器(用Scala編寫)的例子可能有3版本一起使用Mongo司機。解串器的編寫會更困難。

創建一個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 [email protected](o) => 
     MongoObject(null, p, o) 
     case [email protected](_, a) => 
     val doc = new BsonDocument 
     a.add(doc) 
     MongoObject(null, p, doc) 
     case [email protected](_, _, 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 [email protected](_) => p 
     case MongoArray(p, a) => unsupported() 
     case MongoObject(_, p, _) => p 
    } 
    } 
} 
0

我明白,這是一個很古老的問題,但如果今天被問到,我會推薦在官方的Mongo Java驅動上使用built-in POJO support

相關問題