Статьи

Использование MongoDB с Morphia

За последние несколько лет базы данных NoSQL, такие как CouchDB, Cassandra и MongoDB, приобрели некоторую популярность для приложений, которые не требуют семантики и накладных расходов при запуске традиционной СУБД. Я не буду вдаваться в проектные решения, связанные с выбором базы данных NoSQL, поскольку другие уже проделали достаточно хорошую работу, но я поделюсь своим опытом работы с MongoDB и некоторыми приемами по его эффективному использованию в Java.

Недавно у меня был шанс поработать с MongoDB (как в случае с humongoous), которая является документно-ориентированной базой данных, написанной на C ++. Он идеально подходит для хранения документов, которые могут различаться по структуре, и использует формат, аналогичный JSON, что означает, что он поддерживает типы данных и структуры, аналогичные JSON. Он предоставляет богатый, но простой язык запросов и позволяет индексировать ключевые поля для быстрого поиска. Документы хранятся в коллекциях, которые эффективно ограничивают область запроса, но на самом деле нет никаких ограничений на типы разнородных данных, которые вы можете хранить в коллекции. На сайте MongoDB есть приличные документы, если вам нужно изучить основы MongoDB.

MongoDB на Java

Драйвер Mongo Java в основном предоставляет все документы в виде пар ключ-значение, отображаемых в виде карты, и списков значений. Это означает, что если нам нужно хранить или извлекать документы в Java, нам нужно будет выполнить некоторое сопоставление наших POJO с этим интерфейсом карты. Ниже приведен пример типа кода, который мы обычно должны писать для сохранения документа в MongoDB из Java:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
BasicDBObject doc = new BasicDBObject();
 
doc.put("user", "carfey");
 
BasicDBObject post1 = new BasicDBObject();
post1.put("subject", "spam & eggs");
post1.put("message", "first!");
 
BasicDBObject post2 = new BasicDBObject();
post2.put("subject", "sorry about the spam");
 
doc.put("posts", Arrays.asList(post1, post2));
 
coll.insert(doc);

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

Введите Морфия

Morphia — это библиотека Java, которая действует как ORM для MongoDB — она ​​позволяет нам легко отображать объекты Java в хранилище данных MongoDB. Он использует аннотации, чтобы указать, в какой коллекции хранится класс, и даже поддерживает полиморфные коллекции. Одной из самых приятных функций является то, что она может использоваться для автоматической индексации ваших коллекций на основе ваших аннотаций на уровне коллекции или на уровне свойств. Это значительно упрощает развертывание и развертывание изменений.

Я упомянул полиморфное хранение нескольких типов в одной коллекции. Это может помочь нам отобразить различные структуры документов и действует как дискриминатор в чем-то вроде Hibernate.

Вот пример того, как определить сущности, которые будут поддерживать полиморфное хранение и запросы. Класс Return является дочерним по отношению к Order и ссылается на одно и то же имя коллекции. Morphia автоматически обрабатывает полиморфизм при запросе или хранении данных. Вы бы сделали то же самое для аннотирования коллекций, которые не являются полиморфными, но у вас не будет нескольких классов, использующих одно и то же имя коллекции.

Примечание. Это не совсем пример того типа данных, который я бы рекомендовал хранить в MongoDB, поскольку он больше подходит для традиционных СУБД, но он хорошо демонстрирует принципы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Entity("orders") // store in the orders collection
@Indexes({ @Index("-createdDate, cancelled") }) // multi-column index
public class Order {
    @Id private ObjectId id; // always required
 
    @Indexed
    private String orderId;
 
    @Embedded // let's us embed a complex object
    private Person person;
    @Embedded
    private List<Item> items;
 
    private Date createdDate;
    private boolean cancelled;
 
    // .. getters and setters aren't strictly required
    // for mapping, but they would be here
}
 
@Entity("orders") // note the same collection name
public class Return extends Order {
    // maintain legacy name but name it nicely in mongodb
    @Indexed
    @Property("rmaNumber") private String rma;
    private Date approvedDate;
    private Date returnDate;
}

Теперь ниже я покажу, как запрашивать эти полиморфные экземпляры. Обратите внимание, что нам не нужно делать ничего особенного при хранении данных. MongoDB хранит атрибут className вместе с документом, чтобы поддерживать полиморфные извлечения и запросы. Следуя приведенному выше примеру, я могу запросить все типы заказов, выполнив следующие действия:

1
2
3
4
5
6
7
// ds is a Datastore instance
Query<Order> q = ds.createQuery(Order.class).filter("createdDate >=", date);
List<Order> ordersAndReturns = q.asList();
 
// and returns only
Query<Return> rq = ds.createQuery(Return.class).filter("createdDate >=", date);
List<Return> returnsOnly = rq.asList();

Если я хочу запрашивать только простые заказы, я бы использовал фильтр className следующим образом. Это позволяет нам эффективно отключить полиморфное поведение и ограничить результаты одним целевым типом.

1
2
3
4
5
Query<Order> q = ds.createQuery(Order.class)
    .filter("createdDate >=", cutOffDate)
    .filter("className", Order.class.getName());
 
List<Order> ordersOnly = q.asList();

В настоящее время Morphia использует атрибут className для фильтрации результатов, но в какой-то момент в будущем вероятно будет использоваться столбец дискриминатора, и в этом случае вам, возможно, придется фильтровать это значение.

Примечание. В какой-то момент во время запуска приложения вам необходимо зарегистрировать сопоставленные классы, чтобы они могли использоваться Morphia. Смотрите здесь для полной информации . Быстрый пример ниже.

1
2
3
4
5
6
Morphia m = ...
Datastore ds = ...
 
m.map(MyEntity.class);
ds.ensureIndexes(); //creates all defined with @Indexed
ds.ensureCaps(); //creates all collections for @Entity(cap=@CappedAt(...))

Проблемы с изменяющейся структурой в документах

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

К сожалению, Morphia это не очень нравится, поскольку она предназначена для отображения всех сохраненных атрибутов в известные поля POJO. В настоящее время я нашел два способа, которые позволят нам справиться с этим.

Первый — отключение проверки по запросам. Это будет означать, что значения, которые существуют в хранилище данных, но не могут быть сопоставлены с нашими POJO, будут сброшены, а не взорваны:

1
2
// drop unmapped fields quietly
Query<Order> q = ds.createQuery(Order.class).disableValidation();

Другой вариант — хранить весь неструктурированный контент в одном элементе корзины, используя карту. Это может содержать любые базовые типы, поддерживаемые драйвером MongoDB, включая списки и карты, но не сложные объекты, если вы не зарегистрировали конвертеры с Morphia (например, morphia.getMapper (). GetConverters (). AddConverter (new MyCustomTypeConverter ()).

1
2
3
4
5
@Entity("orders")
public class Order {
    // .. our base attributes here
    private Map<String, Object> attributes; // bucket for everything else (
}

Обратите внимание, что при запуске Morphia может жаловаться, что не может проверить поле (поскольку объявление generics не является строгим), но с текущей версией выпуска (0.99) оно будет работать без проблем, нормально хранить любые атрибуты и извлекать их. как карты и списки значений.

Примечание. Когда он заполняет карту со свободным шрифтом из полученного документа, он будет использовать базовые типы драйверов MongoDB для Java — BasicDBObject и BasicDBList. Они реализуют Map и List соответственно, поэтому они будут работать почти так же, как вы ожидаете, за исключением того, что они не будут равны () никаким входным картам или спискам, которые вы, возможно, сохранили, даже если структура и содержимое выглядят одинаковыми. Если вы хотите избежать этого, вы можете использовать аннотацию @PostLoad для аннотирования метода, который может выполнять нормализацию для карт и списков JDK после загрузки документа. Я лично сделал это, чтобы убедиться, что мы всегда видим единообразное представление документов MongoDB, независимо от того, взяты они из коллекции или еще не сохранены.

Ссылка: Использование MongoDB с Morphia от наших партнеров JCG в блоге Carfey Software .

Статьи по Теме :