Подтвердить что ты не робот

Эффективное сопоставление POJO в/из Java Mongo DBObject с использованием Jackson

Хотя похож на Преобразование DBObject в POJO с использованием драйвера Java MongoDB, мой вопрос отличается тем, что я специально заинтересован в использовании Jackson for отображение.

У меня есть объект, который я хочу преобразовать в экземпляр 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. Таким образом, он должен легко вписаться в трубопровод.

Теперь я знаю, что могу преобразовать объект в Map с помощью функции ObjectMapper.convertValue, а затем рекурсивно преобразовать карту в экземпляр BasicDBObject, используя конструктор карты типа BasicDBObject. Но, я хочу знать, могу ли я удалить промежуточную карту и создать BasicDBObject напрямую.

Заметим, что, поскольку a BasicDBObject по существу является отображением, противоположное преобразование, а именно скаляр DBObject в POJO, тривиально и должно быть достаточно эффективным:

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

Наконец, у моего POJO нет аннотаций JSON, и я бы хотел, чтобы это продолжалось.

4b9b3361

Ответ 1

Возможно, вы можете использовать аннотации Mixin для аннотации POJO и BasicDBObject (или DBObject), поэтому аннотации не являются проблемой. Поскольку BasicDBOject - это отображение, вы можете использовать @JsonAnySetter в методе put.

m_objectMapper.addMixInAnnotations(YourMixIn.class, BasicDBObject.class);

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

Это все, что я могу придумать, поскольку у меня есть нулевой опыт с объектом MongoDB.

Обновление: MixIn - это в основном механизм Джексона для добавления аннотации к классу без изменения указанного класса. Это идеальная подгонка, когда у вас нет контроля над классом, который вы хотите маршалировать (например, когда он находится из внешней банки) или когда вы не хотите загромождать свои классы аннотацией.

В вашем случае вы сказали, что BasicDBObject реализует интерфейс Map, поэтому класс имеет метод put, как определено интерфейсом карты. Добавив @JsonAnySetter к этому методу, вы скажете Джексону, что всякий раз, когда он находит свойство, которое он не знает после интроспекции класса, использовать метод для вставки свойства к объекту. Ключ - это имя свойства, а значение - это значение свойства.

Все это приводит к тому, что промежуточная карта исчезает, так как Джексон будет напрямую преобразовываться в BasicDBOject, потому что теперь он знает, как десериализовать этот класс от Json. С помощью этой конфигурации вы можете:

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

Обратите внимание, что я не тестировал это, потому что я не работаю с MongoDB, поэтому могут быть некоторые свободные концы. Тем не менее, я использовал один и тот же механизм для подобных случаев использования без каких-либо проблем. YMMV в зависимости от классов.

Ответ 2

Вот пример простого сериализатора (написанного в 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 [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
    }
  }
}

Ответ 3

Возможно, вы захотите проверить, как это делает 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());

Ответ 4

Здесь обновляется ответ 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);
        }
    }
}

Ответ 5

Я понимаю, что это очень старый вопрос, но если меня спросят сегодня, я бы вместо этого рекомендовал встроенную поддержку POJO на официальном Драйвер Mongo Java.