Java и MongoDB: работа с кодеками

В предыдущей публикации я описал базовые операции при работе с MongoDB в Java.
Использовать org.bson.Document для работы с данными, хранящимися в коллекциях далеко не всегда удобно. Как правило, в коллекциях хранятся данные имеющие какую-то определённую структуру, соответствующую, например, POJO-классам вашего проекта.

Для реализации преобразования BSON в объекты соответствующих классов и обратно в драйвере MongoDB предусмотрены кодеки, реализующие интерфейс org.bson.codecs.Codec. В этой публикации я рассмотрю варианты разработки собственного кодека и использования готовых.

POJO-классы проекта

Структуру данных, использовавшуюся мной в предыдущей публикации можно описать в виде простого класса:

Чтобы пример был более сложным и наглядным, я добавил лист со списком комментариев и список тэгов в свойства класса Todo. Класс TodoComment выглядит следующим образом:

Использование стандартных кодеков

Клиентская библиотека MongoDB содержит достаточно большое количество кодеков, которые могут решить большую часть потребноестей проекта. Для нашего примера понадобятся три провайдера кодеков:

Для подключения кодеков можно использовать метод withCodecRegistry(), вызываемый при получении базы данных или коллекции. Пример использования стандартных кодеков:

Обратите внимание, что PojoCodecProvider конфигурируется при помощи класса-билдера, метод register() которого регистрирует POJO-классы, для которых должны применяться сериализация и десериализация.

Собственный кодек

Для большинства случаев стандартных кодеков будет достаточно, но по тем или иным причинам вы можете захотеть реализовать собственные кодеки.

Для сериализации и десериализации каждого класса должен существовать собственный класс-кодек, реализующий интерфейс org.bson.codecs.Codec, как уже было сказано выше. Этот интерфейс в свою очередь расширяет интерфейсы org.bson.codecs.Encoder и org.bson.codecs.Decoder, в результате чего разработчику нужно реализовать три метода: encode() — для сериализации объекта POJO-класса в BSON, decode() — для десериализации и getEncoderClass() — для определения, для сериализации объектов какого класса должен использоваться этот кодек.

Сериализация

При сериализации объекта в обязательном порядке нужно указать начало и конец документа:

Сериализация свойств объекта выполняется методами write…() объекта типа org.bson.BsonWriter и представляет собой сохранение пар ключ-значение в BSON-документе. Имя свойства может быть записано отдельно при помощи метода writeName():

В приведённом выше примере имя свойства dateCreated сериализуется вызовом метода writeName(), а его значение — вызовом метода writeDateTime(), в то время как пара ключ-значение content сериализуется вызовом лишь одного метода — writeString().

При сериализации массивов простых типов потребуется указать границы массива и сериализовать его элементы в цикле:

Немного иначе обстоят дела со вложенными объектами и массивами вложенных объектов. С одной стороны сериализацию/десериализацию вложенных объектов можно реализовать в этом же методе, но в большинстве случаев для этого логичнее использовать соответствующие кодеки:

Десериализация

Процесс десериализации практически полностью аналогичен процессу сериализации, однако есть свои нюансы:

Если использовать такой способ десериализации, то можно столкнуться с следющими проблемами:

  • Порядок свойств в документе не гарантирован
  • Наличие свойств тоже не гарантировано
  • Значение свойства может иметь значение null

Исходя из этих проблем, логичнее всего будет организовать таким образом чтобы десериализация каждого свойства в следующем порядке:

  1. Получить тип свойства методом BsonReader.readBsonType()
  2. Получить имя свойства методом BsonReader.readName()
  3. Получить значение при помощи
    • Методов BsonReader.read…(), если тип свойства простой
    • Метода DecoderContext.decodeWithChildContext(), если нужно десериализовать вложенный объект
    • Метода BsonReader.readNull(), если тип свойства — BsonType.NULLПолучить значение при помощи:

Пример десериализации с учётом вышеуказанных проблем:

Полный код классов TodoCodec и TodoCommentCodec:

Пример использования кодеков:

Полезные ссылки

Документация MongoDB по кодекам