Эффективное отображение POJO в / из Java Mongo DBObject с использованием Jackson
Хотя этот вопрос похож на Convert DBObject в POJO с использованием MongoDB Java Driver, мой вопрос отличается тем, что меня особенно интересует использование Джексона для отображения.
У меня есть объект, который я хочу преобразовать в экземпляр Mongo DBObject. Я хочу использовать каркас 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
функция, а затем рекурсивно преобразовать карту в BasicDBObject
экземпляр с использованием конструктора карты BasicDBObject
тип. Но я хочу знать, смогу ли я удалить промежуточную карту и создать BasicDBObject
непосредственно.
Обратите внимание, что потому что BasicDBObject
по сути карта, обратное преобразование, а именно из скаляра DBObject
POJO тривиален и должен быть достаточно эффективным:
DBObject dbo = getDBO();
Class clazz = getObjectClass();
Object pojo = m_objectMapper.convertValue(dbo, clazz);
Наконец, в моем POJO нет аннотаций JSON, и я бы хотел, чтобы это продолжалось.
5 ответов
Вы, вероятно, можете использовать аннотации 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
, как определено интерфейсом карты. Добавляя @JsonAnySetter к этому методу, вы сообщаете Джексону, что всякий раз, когда он находит свойство, которое он не знает после самоанализа класса, использовать метод для вставки свойства в объект. Ключ - это имя свойства, а значение - это значение свойства.
Все это вместе убирает промежуточную карту, так как Джексон будет непосредственно преобразован в BasicDBOject
потому что теперь он знает, как десериализовать этот класс из Json. С этой конфигурацией вы можете сделать:
DBObject dbo = m_objectMapper.convertValue(pojo, BasicDBObject.class);
Обратите внимание, что я не проверял это, потому что я не работаю с MongoDB, поэтому могут быть некоторые слабые стороны. Тем не менее, я использовал тот же механизм для подобных случаев без каких-либо проблем. YMMV в зависимости от классов.
Вот пример простого сериализатора (написанного на Scala) из POJO в BsonDocument, который можно использовать с версией 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 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
}
}
}
Я понимаю, что это очень старый вопрос, но если бы меня спросили сегодня, я бы порекомендовал встроенную поддержку POJO в официальном драйвере Mongo Java.
Вы можете быть заинтересованы в проверке того, как это делает Джонго. Это открытый исходный код, и код можно найти на github. Или вы также можете просто использовать их библиотеку. Я использую смесь Джонго и равнины DBObject
когда мне нужно больше гибкости.
Они утверждают, что они (почти) так же быстро, как напрямую используют драйвер Java, поэтому я полагаю, что их метод эффективен.
Я использую небольшой вспомогательный класс утилит, приведенный ниже, который основан на их кодовой базе и использует смесь Jongo (MongoBsonFactory
) и Джексон для конвертации между объектами DBObject и 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());
Вот обновление ответа assylias, которое не требует Jongo и совместимо с драйверами Mongo 3.x. Он также обрабатывает вложенные графы объектов, я не мог заставить это работать LazyWritableDBObject
который был удален в драйверах mongo 3.x в любом случае.
Идея состоит в том, чтобы сказать Джексону, как сериализовать объект в байтовый массив BSON, а затем десериализовать байтовый массив BSON в BasicDBObject
, Я уверен, что вы можете найти низкоуровневый API в драйверах mongo-java, если вы хотите отправить байты BSON непосредственно в базу данных. Вам понадобится зависимость от bson4jackson для того, чтобы ObjectMapper
сериализовать BSON при вызове writeValues(ByteArrayOutputStream, Object)
:
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);
}
}
}