Эта статья является частью нашего курса Академии под названием MongoDB — A Scalable NoSQL DB .
В этом курсе вы познакомитесь с MongoDB. Вы узнаете, как установить его и как управлять им через оболочку. Кроме того, вы узнаете, как получить программный доступ к нему через Java и как использовать Map Reduce с ним. Наконец, будут объяснены более сложные понятия, такие как шардинг и репликация. Проверьте это здесь !
Содержание
1. Введение
В предыдущих частях руководства мы кратко рассмотрели многие функции MongoDB , его архитектуру, установку и большинство поддерживаемых команд. Надеемся, вы уже видите, что MongoDB хорошо соответствует требованиям вашего приложения и хотели бы сделать его частью своего программного стека.
В этой части мы рассмотрим интеграцию MongoDB с приложениями, написанными на Java. Наш выбор Java обусловлен его популярностью, но MongoDB предоставляет привязки (или драйверы) ко многим другим языкам, полный список которых можно найти в официальной документации .
Мы собираемся разработать простое приложение для книжного магазина с целью охватить большинство случаев использования, с которыми вы можете столкнуться, с упором на способы их решения MongoDB . Решения будут представлены в виде типичных тестовых примеров JUnit с удивительными плавными утверждениями, предоставляемыми AssertJ , время от времени сопровождаемыми командами оболочки MongoDB в качестве шагов проверки.
Хотя Spring Data MongoDB на сегодняшний день является самым популярным выбором в сообществе Java, мы будем использовать другую замечательную библиотеку под названием Morphia : легковесная библиотека с безопасным типом для отображения объектов Java в MongoDB и из нее . Последняя версия библиотеки Morphia на момент написания статьи — 0.106, и, к сожалению, она пока не поддерживает все функции MongoDB 2.6 (например, текстовые индексы и полнотекстовый поиск).
2. Расширения
Еще одна хорошая вещь о Morphia является его расширяемость. В частности, нас очень интересует расширение JSR 303: Bean Validation 1.0, предоставляемое командой Morphia из коробки. Это очень полезное дополнение, которое помогает обеспечить правильность и значимость объектов данных, проходящих через систему. Мы увидим пару примеров позже.
3. Зависимости
Наш проект будет использовать Apache Maven для управления сборкой и зависимостями, поскольку это довольно популярный выбор в сообществе Java. К счастью, релизы Morphia доступны через публичные репозитории Apache Maven, и мы собираемся использовать эти три модуля:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <properties>    <org.mongodb.morphia.version>0.106</org.mongodb.morphia.version></properties><dependencies>    <dependency>        <groupId>org.mongodb.morphia</groupId>        <artifactId>morphia</artifactId>        <version>${org.mongodb.morphia.version}</version>    </dependency>    <dependency>        <groupId>org.mongodb.morphia</groupId>        <artifactId>morphia-validation</artifactId>        <version>${org.mongodb.morphia.version}</version>    </dependency>            <dependency>        <groupId>org.mongodb.morphia</groupId>              <artifactId>morphia-logging-slf4j</artifactId>        <version>${org.mongodb.morphia.version}</version>    </dependency></dependencies> | 
Основной модуль предоставляет аннотации, сопоставления и, в основном, все необходимые классы, чтобы начать создавать приложения и использовать MongoDB . Модуль проверки (или расширение) обеспечивает интеграцию с JSR 303: Bean Validation 1.0, как мы упоминали ранее. Наконец, модуль регистрации интегрируется с отличной платформой SLF4J .
4. Модель данных
Поскольку мы создаем простое приложение для книжного магазина , его область данных может быть представлена этими тремя классами:
- Автор : автор книги
- Книга : сама книга
- Магазин : магазин по продаже книг
Чтобы спроецировать то, что мы только что сказали, в терминологию MongoDB , мы собираемся создать книжный магазин базы данных с тремя коллекциями документов:
- авторы : все известные авторы книг
- книги : все доступные книги
- магазины : все существующие магазины, продающие книги
Прозрачное отображение между классами Java ( Author, Book, Store ) и коллекциями MongoDB ( авторы, книги, магазины ) является одной из обязанностей Morphia . Давайте посмотрим на класс Authorfirst, так как он самый простой.
| 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | packagecom.javacodegeeks.mongodb;importorg.bson.types.ObjectId;importorg.hibernate.validator.constraints.NotEmpty;importorg.mongodb.morphia.annotations.Entity;importorg.mongodb.morphia.annotations.Id;importorg.mongodb.morphia.annotations.Index;importorg.mongodb.morphia.annotations.Indexes;importorg.mongodb.morphia.annotations.Property;importorg.mongodb.morphia.annotations.Version;@Entity( value = "authors", noClassnameStored = true)@Indexes( {    @Index( "-lastName, -firstName")} )publicclassAuthor {    @IdprivateObjectId id;    @Versionprivatelongversion;    @Property@NotEmptyprivateString firstName;    @Property@NotEmptyprivateString lastName;            publicAuthor() {    }            publicAuthor( finalString firstName, finalString lastName ) {        this.lastName = lastName;        this.firstName = firstName;    }    publicObjectId getId() {        returnid;    }        protectedvoidsetId( finalObjectId id ) {        this.id = id;    }    publiclonggetVersion() {        returnversion;    }    publicvoidsetVersion( finallongversion ) {        this.version = version;    }    publicString getLastName() {        returnlastName;    }    publicvoidsetLastName( finalString lastName ) {        this.lastName = lastName;    }    publicString getFirstName() {        returnfirstName;    }    publicvoidsetFirstName( finalString firstName ) {        this.firstName = firstName;    }} | 
 Для разработчиков Java, работающих с JPA и / или Hibernate , такие POJO (простые старые объекты Java) очень знакомы.  Аннотация @Entity отображает класс Java в коллекцию MongoDB , в данном случае Автор -> авторы .  noClassnameStored примечание о noClassnameStored : по умолчанию Morphia хранит полное имя класса Java внутри каждого документа MongoDB .  Это на самом деле весьма полезно, если одна и та же коллекция может содержать документы разных типов (имеется в виду экземпляры разных классов Java).  В нашем приложении мы не собираемся использовать такую функцию, поэтому Morphia поручено не хранить эту информацию (что делает документы более чистыми). 
Далее мы объявляем поля документа MongoDB как свойства класса Java. В случае Автора это включает в себя:
- id (помеченный @Id)
- версия (помеченная @Version), мы собираемся использовать это свойство для оптимистической блокировки в случае одновременных обновлений
- firstName (помечено @Property), кроме того, определено ограничение проверки @NotEmpty, которое требует установки этого свойства
- lastName (помечено @Property), кроме того, определено ограничение проверки @NotEmpty, которое требует установки этого свойства
  И наконец, класс Author определяет один составной индекс для свойств lastName и firstName (концепции индексирования будут подробно рассмотрены в части 7. Руководство по безопасности, профилированию, индексированию, курсорам и массовым операциям MongoDB ). 
  Давайте перейдем к более сложным примерам, включающим два других класса, Book и Store .  Вот класс Book . 
| 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | packagecom.javacodegeeks.mongodb;importjava.util.ArrayList;importjava.util.Date;importjava.util.List;importjava.util.Set;importjava.util.TreeSet;importjavax.validation.Valid;importjavax.validation.constraints.NotNull;importorg.bson.types.ObjectId;importorg.hibernate.validator.constraints.NotEmpty;importorg.mongodb.morphia.annotations.Embedded;importorg.mongodb.morphia.annotations.Entity;importorg.mongodb.morphia.annotations.Id;importorg.mongodb.morphia.annotations.Indexed;importorg.mongodb.morphia.annotations.Property;importorg.mongodb.morphia.annotations.Reference;importorg.mongodb.morphia.annotations.Version;importorg.mongodb.morphia.utils.IndexDirection;@Entity( value = "books", noClassnameStored = true)publicclassBook {    @IdprivateObjectId id;    @Versionprivatelongversion;        @Property@Indexed( IndexDirection.DESC ) @NotEmptyprivateString title;    @Reference@ValidprivateList< Author > authors = newArrayList<>();    @Property( "published") @NotNullprivateDate publishedDate;                @Property( concreteClass = TreeSet.class) @Indexed    privateSet< String > categories = newTreeSet<>();    @Embedded@Valid@NotNullprivatePublisher publisher;            publicBook() {    }        publicBook( finalString title ) {        this.title = title;    }        publicObjectId getId() {        returnid;    }        protectedvoidsetId( finalObjectId id ) {        this.id = id;    }    publiclonggetVersion() {        returnversion;    }    publicvoidsetVersion( finallongversion ) {        this.version = version;    }    publicString getTitle() {        returntitle;    }    publicvoidsetTitle( finalString title ) {        this.title = title;    }    publicList< Author > getAuthors() {        returnauthors;    }    publicvoidsetAuthors( finalList< Author > authors ) {        this.authors = authors;    }    publicDate getPublishedDate() {        returnpublishedDate;    }    publicvoidsetPublishedDate( finalDate publishedDate ) {        this.publishedDate = publishedDate;    }    publicSet< String > getCategories() {        returncategories;    }    publicvoidsetCategories( finalSet< String > categories ) {        this.categories = categories;    }    publicPublisher getPublisher() {        returnpublisher;    }    publicvoidsetPublisher( finalPublisher publisher ) {        this.publisher = publisher;   }} | 
Большинство аннотаций мы уже видели, но есть пара новых:
- ключ публикации (помечен как @Property с именем «опубликовано» в документе)
- издатель (помеченный @Embedded, весь объект будет храниться внутри каждого документа), кроме того, он имеет определенное ограничение проверки @Validand @NotNull, которое требует, чтобы это свойство было установлено и было действительным (соответствует собственным ограничениям проверки)
- авторы (помеченные @Reference, ссылки на авторов будут храниться внутри каждого документа), кроме того, для него определено ограничение проверки @Valid, которое требует, чтобы каждый автор в коллекции был действительным (соответствует собственным ограничениям проверки)
  Свойства title и categories имеют собственные индексы, определенные с помощью аннотации @Indexed.  Класс Publisher не аннотирован @Entity и поэтому не сопоставляется ни с одной коллекцией MongoDB, являющейся простым Java-бином.  При этом класс Publisher все еще может объявлять индексы, которые будут частью коллекции MongoDB, в которую этот класс встраивается ( books ). 
| 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 | packagecom.javacodegeeks.mongodb;importorg.hibernate.validator.constraints.NotBlank;importorg.mongodb.morphia.annotations.Indexed;importorg.mongodb.morphia.annotations.Property;importorg.mongodb.morphia.utils.IndexDirection;publicclassPublisher {    @Property@Indexed( IndexDirection.DESC ) @NotBlankprivateString name;        publicPublisher() {    }        publicPublisher( finalString name ) {        this.name = name;    }    publicString getName() {        returnname;    }    publicvoidsetName(String name) {        this.name = name;    }} | 
  Наконец, давайте посмотрим на класс Store .  Этот класс в основном склеивает все концепции, которые мы видели в классах Author и Book . 
| 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | packagecom.javacodegeeks.mongodb;importjava.util.ArrayList;importjava.util.List;importjavax.validation.Valid;importorg.bson.types.ObjectId;importorg.hibernate.validator.constraints.NotEmpty;importorg.mongodb.morphia.annotations.Embedded;importorg.mongodb.morphia.annotations.Entity;importorg.mongodb.morphia.annotations.Id;importorg.mongodb.morphia.annotations.Indexed;importorg.mongodb.morphia.annotations.Property;importorg.mongodb.morphia.annotations.Version;importorg.mongodb.morphia.utils.IndexDirection;@Entity( value = "stores", noClassnameStored = true)publicclassStore {    @IdprivateObjectId id;    @Versionprivatelongversion;    @Property@Indexed( IndexDirection.DESC ) @NotEmptyprivateString name;    @Embedded@ValidprivateList< Stock > stock = newArrayList<>();    @Embedded@Indexed( IndexDirection.GEO2D ) privateLocation location;        publicStore() {    }        publicStore( finalString name ) {        this.name = name;    }    publicObjectId getId() {        returnid;    }    protectedvoidsetId( finalObjectId id ) {        this.id = id;    }    publicString getName() {        returnname;    }    publicvoidsetName( finalString name ) {        this.name = name;    }    publicList< Stock > getStock() {        returnstock;    }    publicvoidsetStock( finalList< Stock > stock ) {        this.stock = stock;    }    publicLocation getLocation() {        returnlocation;    }    publicvoidsetLocation( finalLocation location ) {        this.location = location;    }    publiclonggetVersion() {        returnversion;    }    publicvoidsetVersion(longversion) {        this.version = version;    }} | 
  Единственное, что вводит этот класс, — это местоположение и геопространственный индекс, помеченный @Indexed ( IndexDirection. GEO2D ).  В MongoDB есть несколько способов представления координат местоположения: 
- как массив координат: [55.5, 42.3]
- как внедренный объект с двумя свойствами: {lon: 55.5, lat: 42.3}
  Класс Location встроенный в класс Store использует второй параметр и является простым как обычный Java-компонент, аналогичный классу Publisher . 
| 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 29 30 31 32 | packagecom.javacodegeeks.mongodb;importorg.mongodb.morphia.annotations.Property;publicclassLocation {    @Propertyprivatedoublelon;    @Propertyprivatedoublelat;    publicLocation() {    }        publicLocation( finaldoublelon, finaldoublelat ) {        this.lon = lon;        this.lat = lat;    }        publicdoublegetLon() {        returnlon;    }        publicvoidsetLon( finaldoublelon ) {        this.lon = lon;    }    publicdoublegetLat() {        returnlat;    }    publicvoidsetLat( finaldoublelat ) {        this.lat = lat;    }} | 
  Класс Stock содержит книгу и ее доступное количество в каждом магазине, очень похожее на классы Publisher и Location . 
| 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 29 30 31 32 33 34 35 36 | packagecom.javacodegeeks.mongodb;importjavax.validation.Valid;importjavax.validation.constraints.Min;importorg.mongodb.morphia.annotations.Property;importorg.mongodb.morphia.annotations.Reference;publicclassStock {            @Reference@ValidprivateBook book;    @Property@Min( 0) privateintquantity;        publicStock() {    }        publicStock( finalBook book, finalintquantity ) {        this.book = book;        this.quantity = quantity;    }        publicBook getBook() {        returnbook;    }        publicvoidsetBook( finalBook book ) {        this.book = book;    }    publicintgetQuantity() {        returnquantity;    }    publicvoidsetQuantity( finalintquantity ) {        this.quantity = quantity;    }} | 
  Свойство quantity имеет определенное ограничение проверки @Min (0), которое утверждает, что значение этого свойства никогда не должно быть отрицательным. 
5. Подключение
Теперь, когда модель данных для книжного магазина завершена, пришло время посмотреть, как мы можем применить ее к реальному экземпляру MongoDB . В следующих разделах мы предполагаем, что у вас есть локальный экземпляр MongoDB , работающий на локальном хосте и по умолчанию порт 27017 .
  В Morphia первым шагом является создание экземпляра класса Morhia и его инициализация с желаемыми расширениями (в нашем случае это расширение проверки). 
| 1 2 | finalMorphia morphia = newMorphia();newValidationExtension( morphia ); | 
  После выполнения этого шага экземпляр класса Datastore (фактически, база данных MongoDB ) может быть создан (если база данных не существует) или получен (если существует) с использованием экземпляра класса Morhia .  Подключение MongoDB требуется на этом этапе и может быть предоставлено с использованием MongoClient класса MongoClient .  Кроме того, сопоставленные объекты ( Автор, Книга, Магазин ) могут быть указаны одновременно, что приводит к созданию соответствующих коллекций документов. 
| 1 2 3 4 | finalMongoClient client = newMongoClient( "localhost", 27017);finalDatastore dataStore = morphia    .map( Store.class, Book.class, Author.class)    .createDatastore( client, "bookstore"); | 
В случае, если хранилище данных (или база данных) создается с нуля, удобно попросить Morphia также создать все индексы и ограниченные коллекции.
| 1 2 | dataStore.ensureIndexes();dataStore.ensureCaps(); | 
Отлично, с экземпляром хранилища данных на месте, мы готовы создавать новые документы, выполнять запросы и выполнять сложные обновления и агрегации.
6. Создание документов
Было бы неплохо начать с создания пары документов и сохранения их в MongoDB . Наш первый тестовый пример делает именно это, создавая нового автора.
| 1 2 3 4 5 6 7 8 9 | @TestpublicvoidtestCreateNewAuthor() {    assertThat( dataStore.getCollection( Author.class).count() ).isEqualTo( 0);            finalAuthor author = newAuthor( "Kristina", "Chodorow");    dataStore.save( author );            assertThat( dataStore.getCollection( Author.class).count() ).isEqualTo( 1);} | 
  Для любопытных читателей давайте запустим оболочку MongoDB и дважды проверим, что коллекция авторов действительно содержит недавно созданного автора: bin/mongo bookstore 
  Ну, это было легко.  Но что произойдет, если автор, которого мы собираемся сохранить в MongoDB, нарушит ограничения валидации?  В этом случае вызов метода save() завершится с VerboseJSR303ConstraintViolationException как VerboseJSR303ConstraintViolationException в нашем следующем тестовом примере. 
| 1 2 3 4 5 | @Test( expected = VerboseJSR303ConstraintViolationException.class)publicvoidtestCreateNewAuthorWithEmptyLastName() {    finalAuthor author = newAuthor( "Kristina", "");    dataStore.save( author );} | 
Не написав ни единой строчки кода, а лишь заявив некоторые ожидания относительно правильного состояния объектов, мы можем выполнять сложные задачи проверки буквально бесплатно. В следующем тестовом примере мы собираемся сделать немного больше работы, чтобы сохранить новую книгу.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | @TestpublicvoidtestCreateNewBook() {    assertThat( dataStore.getCollection( Author.class).count() ).isEqualTo( 0);    assertThat( dataStore.getCollection( Book.class).count() ).isEqualTo( 0);            finalPublisher publisher = newPublisher( "O'Reilly");    finalAuthor author = newAuthor( "Kristina", "Chodorow");            finalBook book = newBook( "MongoDB: The Definitive Guide");    book.getAuthors().add( author );    book.setPublisher( publisher );    book.setPublishedDate( newLocalDate( 2013, 05, 23).toDate() );                    dataStore.save( author );    dataStore.save( book );            assertThat( dataStore.getCollection( Author.class).count() ).isEqualTo( 1);    assertThat( dataStore.getCollection( Book.class).count() ).isEqualTo( 1);        } | 
Используя оболочку MongoDB, давайте удостоверимся, что вновь созданная книга хранится в коллекции книг, а свойство ее авторов ссылается на документы из коллекции авторов.
  Чтобы увидеть некоторые нарушения ограничений проверки, давайте попробуем сохранить новую книгу без набора свойств publisher .  Это должно привести к исключению VerboseJSR303ConstraintViolationException . 
| 01 02 03 04 05 06 07 08 09 10 11 | @Test( expected = VerboseJSR303ConstraintViolationException.class)publicvoidtestCreateNewBookWithEmptyPublisher() {    finalAuthor author = newAuthor( "Kristina", "Chodorow");            finalBook book = newBook( "MongoDB: The Definitive Guide");    book.getAuthors().add( author );    book.setPublishedDate( newLocalDate( 2013, 05, 23).toDate() );                    dataStore.save( author );    dataStore.save( book );         } | 
Финальные тестовые примеры демонстрируют создание нового магазина, а также проверку на работе, когда на складе хранится книга с отрицательным количеством.
| 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 | @TestpublicvoidtestCreateNewStore() {    assertThat( dataStore.getCollection( Author.class).count() ).isEqualTo( 0);    assertThat( dataStore.getCollection( Book.class).count() ).isEqualTo( 0);            assertThat( dataStore.getCollection( Store.class).count() ).isEqualTo( 0);    finalPublisher publisher = newPublisher( "O'Reilly");    finalAuthor author = newAuthor( "Kristina", "Chodorow");           finalBook book = newBook( "MongoDB: The Definitive Guide");            book.setPublisher( publisher );    book.setPublishedDate( newLocalDate( 2013, 05, 23).toDate() );    book.getAuthors().add( author );    book.getCategories().addAll( Arrays.asList( "Databases", "Programming", "NoSQL") );           finalStore store = newStore( "Waterstones Piccadilly");    store.setLocation( newLocation( -0.135484, 51.50957) );    store.getStock().add( newStock( book, 10) );    dataStore.save( author );    dataStore.save( book );          dataStore.save( store );           assertThat( dataStore.getCollection( Author.class).count() ).isEqualTo( 1);    assertThat( dataStore.getCollection( Book.class).count() ).isEqualTo( 1);            assertThat( dataStore.getCollection( Store.class).count() ).isEqualTo( 1);} | 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | @Test( expected = VerboseJSR303ConstraintViolationException.class)publicvoidtestCreateNewStoreWithNegativeBookQuantity() {    finalAuthor author = newAuthor( "Kristina", "Chodorow");           finalBook book = newBook( "MongoDB: The Definitive Guide");    book.getAuthors().add( author );    book.setPublisher( newPublisher( "O'Reilly") );    book.setPublishedDate( newLocalDate( 2013, 05, 23).toDate() );                   finalStore store = newStore( "Waterstones Piccadilly");    store.getStock().add( newStock( book, -1) );    dataStore.save( author );    dataStore.save( book );          dataStore.save( store );} | 
7. Запрос документов
Обладая знаниями о том, как создавать новые документы и сохранять их в MongoDB с помощью Morphia, мы теперь готовы взглянуть на запросы к существующим документам. Как мы увидим очень скоро, Morphia предоставляет гибкий и простой в использовании, строго типизированный (где это имеет смысл) API запросов. Чтобы сделать следующие тестовые примеры немного проще, пара авторов , книги :
- MongoDB: Полное руководство Кристины Шодоров, опубликованное O’Reilly (23 мая 2013 г.) в категориях «Базы данных», Программирование, NoSQL
- MongoDB Прикладные шаблоны проектирования Рика Коупленда, опубликованные O’Reilly (19 марта 2013 г.) в категориях Базы данных, Программирование, NoSQL, Шаблоны
- MongoDB в действии Kyle Banker, опубликовано Мэннингом (16 декабря 2011 г.) в категориях Базы данных, Программирование, NoSQL
- NoSQL Distilled: краткое руководство по формирующемуся миру стойкости полиглота Прамода Дж. Садаладжа и Мартина Фаулера, опубликованное Addison Wesley (18 августа 2012 г.) в категориях Базы данных, NoSQL
-   Waterstones Piccadilly расположен по адресу (51.50957, -0.135484) и снабжен:
- MongoDB: полное руководство в количестве 10
- MongoDB Applied Design в количестве 45
- MongoDB в действии в количестве 2
- NoSQL перегоняется в количестве 0
 
-   Компания Barnes & Noble расположена по адресу (40.786277, -73.978693) и снабжена:
- MongoDB: полное руководство в количестве 7
- MongoDB Applied Design в количестве 12 штук
- MongoDB в действии в количестве 15
- NoSQL перегоняется в количестве 2
 
и магазины :
предварительно заполняются перед каждым тестом.
Тестовый пример, который мы собираемся начать с простых запросов ко всем книгам, в названии которых есть слово «mongodb», с использованием сравнения без учета регистра.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | @TestpublicvoidtestFindBooksByName() {    finalList< Book > books = dataStore.createQuery( Book.class)        .field( "title").containsIgnoreCase( "mongodb")        .order( "title")        .asList();            assertThat( books ).hasSize( 3);    assertThat( books ).extracting( "title")        .containsExactly(            "MongoDB Applied Design Patterns",            "MongoDB in Action",            "MongoDB: The Definitive Guide"        );} | 
  В этом тестовом примере используется один из преимуществ API запросов Morphia.  Но есть и другой, использующий вызов метода менее подробного filter который демонстрируется в следующем тестовом примере, просматривая книгу ее автора. 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 | @TestpublicvoidtestFindBooksByAuthor() {    finalAuthor author = dataStore.createQuery( Author.class)        .filter( "lastName =", "Banker")        .get();            finalList< Book > books = dataStore.createQuery( Book.class)        .field( "authors").hasThisElement( author )        .order( "title")        .asList();            assertThat( books ).hasSize( 1);    assertThat( books ).extracting( "title").containsExactly( "MongoDB in Action");        } | 
Следующий тестовый пример использует немного более сложные составные критерии: он запрашивает все книги в категориях NoSQL, Базы данных и опубликован после 1 января 2013 года . Результат также сортируется по названию книги в порядке возрастания ( -title ).
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | @TestpublicvoidtestFindBooksByCategoryAndPublishedDate() {    finalQuery< Book > query = dataStore.createQuery( Book.class).order( "-title");                    query.and(        query.criteria( "categories")            .hasAllOf( Arrays.asList( "NoSQL", "Databases") ),        query.criteria( "published")            .greaterThan( newLocalDate( 2013, 01, 01).toDate() )    );                finalList< Book > books =  query.asList();            assertThat( books ).hasSize( 2);    assertThat( books ).extracting( "title")        .containsExactly(            "MongoDB: The Definitive Guide",            "MongoDB Applied Design Patterns"        );} | 
Если вы случайно оказались в Лондоне и хотели бы купить книгу MongoDB в ближайшем магазине, следующий тестовый пример демонстрирует возможности геопространственных запросов MongoDB , просматривая книжный магазин недалеко от центра.
| 01 02 03 04 05 06 07 08 09 10 11 | @TestpublicvoidtestFindStoreClosestToLondon() {    finalList< Store > stores = dataStore                         .createQuery( Store.class)        .field( "location").near( 51.508035, -0.128016, 1.0)        .asList();    assertThat( stores ).hasSize( 1);    assertThat( stores ).extracting( "name")        .containsExactly( "Waterstones Piccadilly");} | 
И наконец, было бы неплохо знать, содержит ли книжный магазин, который вы собираетесь посетить, достаточно копий, чтобы вы могли его купить (при условии, что вы хотели бы купить хотя бы 10 из них).
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | @TestpublicvoidtestFindStoreWithEnoughBookQuantity() {    finalBook book = dataStore.createQuery( Book.class)        .field( "title").equal( "MongoDB in Action")        .get();                finalList< Store > stores = dataStore        .createQuery( Store.class)        .field( "stock").hasThisElement(             dataStore.createQuery( Stock.class)                .field( "quantity").greaterThan( 10)                .field( "book").equal( book )                .getQueryObject() )        .retrievedFields( true, "name")        .asList();                assertThat( stores ).hasSize( 1);            assertThat( stores ).extracting( "name").containsExactly( "Barnes & Noble");        } | 
  Этот тестовый пример демонстрирует очень важную технику для запроса встроенных (или внутренних) документов с $elemMatch оператора $elemMatch .  Поскольку у каждого магазина есть собственный склад (представленный в виде списка объектов Stock ), мы хотели бы найти магазин, у которого есть как минимум 10 копий MongoDB в книге действий.  Кроме того, мы хотели бы получить из MongoDB только название магазина, отфильтровывая все остальные свойства, применяя фильтр retrievedFields .  Кроме того, каждый элемент акции содержит ссылку на книгу, и поэтому рассматриваемая книга должна быть извлечена заранее и передана в критерии запроса. 
8. Обновление документов
Обновления или модификации документов являются наиболее интересными и мощными функциями MongoDB . Он в значительной степени использует возможности запросов, которые мы видели в разделе « Запрос документов », и богатую семантику обновлений, которую мы собираемся изучить.
  Неудивительно, что Morphia предоставляет несколько способов обновления документов на месте, используя семейства методов save()/merge() , update()/updateFirst() и findAndModify() .  Мы собираемся начать с тестового примера, используя метод save() как мы уже видели в действии. 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | @TestpublicvoidtestSaveStoreWithNewLocation() {    finalStore store = dataStore        .createQuery( Store.class)        .field( "name").equal( "Waterstones Piccadilly")        .get();            assertThat( store.getVersion() ).isEqualTo( 1);            store.setLocation( newLocation( 50.50957,-0.135484) );    finalKey< Store > key = dataStore.save( store );                           finalStore updated = dataStore.getByKey( Store.class, key );    assertThat( updated.getVersion() ).isEqualTo( 2);        } | 
  Это самый простой способ выполнить изменения, если вы уже работаете с некоторыми экземплярами документов, полученными из MongoDB .  Вы просто вызываете обычные установщики Java для обновления некоторых свойств и передаете обновленный объект методу save() .  Обратите внимание, что каждый вызов метода save () увеличивает версию документа: если версия сохраняемого объекта не совпадает с версией в базе данных, ConcurrentModificationException (как смоделируется в приведенном ниже тестовом примере). 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | @Test( expected = ConcurrentModificationException.class)publicvoidtestSaveStoreWithConcurrentUpdates() {    finalQuery< Store > query = dataStore        .createQuery( Store.class)        .filter( "name =", "Waterstones Piccadilly");            finalStore store1 = query.get();            finalStore store2 = query.cloneQuery().get();            store1.setName( "New Store 1");    dataStore.save( store1 );        assertThat( store1.getName() ).isEqualTo( "New Store 1");               store2.setName( "New Store 2");    dataStore.save( store2 );                              } | 
  Другой способ выполнения единичных или массовых изменений документа — использование вызова метода update() .  По умолчанию update() изменяет все документы, соответствующие критериям запроса.  Если вы хотите ограничить область действия только одним документом, используйте updateFirst() . 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 | @TestpublicvoidtestUpdateStoreLocation() {    finalUpdateResults< Store > results = dataStore.update(         dataStore            .createQuery( Store.class)            .field( "name").equal( "Waterstones Piccadilly"),                     dataStore            .createUpdateOperations( Store.class)            .set( "location", newLocation( 50.50957,-0.135484) )    );            assertThat( results.getUpdatedCount() ).isEqualTo( 1);        } | 
  Возвращаемое значение метода update() — это не документ (или документы), а более общий статус операции: сколько документов было обновлено или вставлено и т. Д. Это очень полезно и быстрее по сравнению с другими параметрами, если вам не требуется обновленный документ (ы) должен быть возвращен вам, но, чтобы убедиться, что по крайней мере что-то было обновлено. 
  findAndModify — самая мощная и многофункциональная операция.  Это приводит к тем же результатам, что и метод update() но также может возвращать обновленный документ (после применения изменений) или старый (до внесения изменений).  Кроме того, он может создать новый документ, если ни один не соответствует запросу (выполнение так называемого upsert ). 
| 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 | @TestpublicvoidtestFindStoreWithEnoughBookQuantity() {    finalBook book = dataStore.createQuery( Book.class)        .field( "title").equal( "MongoDB in Action")        .get();            finalStore store = dataStore.findAndModify(         dataStore            .createQuery( Store.class)            .field( "stock").hasThisElement(                 dataStore.createQuery( Stock.class)                    .field( "quantity").greaterThan( 10)                    .field( "book").equal( book )                    .getQueryObject() ),                     dataStore            .createUpdateOperations( Store.class)            .disableValidation()            .inc( "stock.$.quantity", -10),        false    );            assertThat( store ).isNotNull();            assertThat( store.getStock() )        .usingElementComparator( comparator )        .contains( newStock( book, 5) );} | 
Приведенный выше тестовый пример находит хранилище, у которого есть как минимум 10 копий книги MongoDB в действии, и уменьшает ее запас на эту сумму (оставляя только 5 копий из первоначальных 15 ). Конструкция stock. $. Amount предназначена для обновления точного элемента stock, соответствующего критериям запроса. Модифицированный магазин был возвращен звонком, подтверждающим, что на складе осталось только 5 MongoDB в книгах действий.
9. Удаление документов
  Как и при обновлении документов , удаление документов из коллекции может быть выполнено несколькими способами, в зависимости от варианта использования.  Например, если у вас уже есть экземпляр документа, извлеченный из MongoDB , он может быть передан непосредственно в метод delete() . 
| 01 02 03 04 05 06 07 08 09 10 | @TestpublicvoidtestDeleteStore() {    finalStore store = dataStore        .createQuery( Store.class)        .field( "name").equal( "Waterstones Piccadilly")        .get();            finalWriteResult result = dataStore.delete( store );    assertThat( result.getN() ).isEqualTo( 1);                } | 
Метод возвращает статус операции с несколькими затронутыми документами. Поскольку мы удаляем один экземпляр хранилища , ожидаемое количество затронутых документов равно 1 .
  В случае массового удаления метод delete() позволяет предоставить запрос, а также возвращает статус с рядом затронутых документов.  Следующий тестовый пример удаляет все магазины (которые мы создали только 2 ). 
| 1 2 3 4 5 | @TestpublicvoidtestDeleteAllStores() {    finalWriteResult result = dataStore.delete( dataStore.createQuery( Store.class) );    assertThat( result.getN() ).isEqualTo( 2);                } | 
  Следовательно, существует метод findAndDelete() который принимает запрос и возвращает один (или первый в случае нескольких совпадений) удаленный документ, который удовлетворяет критериям.  Если ни один документ не соответствует критериям, findAndDelete() возвращает findAndDelete() . 
| 01 02 03 04 05 06 07 08 09 10 11 | @TestpublicvoidtestFindAndDeleteBook() {    finalBook book = dataStore.findAndDelete(         dataStore            .createQuery( Book.class)            .field( "title").equal( "MongoDB in Action")        );            assertThat( book ).isNotNull();                    assertThat( dataStore.getCollection( Book.class).count() ).isEqualTo( 3);} | 
10. Агрегации
Агрегации являются наиболее сложной частью операций MongoDB из-за гибкости, которую они предоставляют для манипулирования документами. Морфия изо всех сил пытается упростить использование агрегатов из кода Java, но вы все равно можете столкнуться с некоторыми трудностями при работе с ними.
Мы собираемся начать с простого примера группировки книг по издателю. Результатом операции является имя издателя и количество книг.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | @TestpublicvoidtestGroupBooksByPublisher() {    finalDBObject result = dataStore        .getCollection( Book.class)        .group(            newBasicDBObject( "publisher.name", "1"),            newBasicDBObject(),            newBasicDBObject( "total", 0),            "function ( curr, result ) { result.total += 1 }"        );            assertThat( result ).isInstanceOf( BasicDBList.class);                             finalBasicDBList groups = ( BasicDBList )result;    assertThat( groups ).hasSize( 3);            assertThat( groups ).containsExactly(         newBasicDBObject( "publisher.name", "O'Reilly").append( "total", 2.0),         newBasicDBObject( "publisher.name", "Manning").append( "total", 1.0),        newBasicDBObject( "publisher.name", "Addison Wesley").append( "total", 1.0)     );} | 
Как вы можете видеть, даже будучи вызовом метода Java, аргументы group () во многом напоминают параметры групповой команды, которые мы видели в части 2. Руководство по оболочке MongoDB — Операции и команды .
Чтобы закончить с агрегациями, давайте рассмотрим более интересную задачу: сгруппировать книги по категориям. На данный момент MongoDB не поддерживает группирование по свойству массива (какие категории). К счастью, мы можем построить конвейер агрегации (который мы также затронули в части 2. Руководство по оболочке MongoDB — Операции и команды ) для достижения желаемого результата.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @TestpublicvoidtestGroupBooksByCategories() {    finalDBCollection collection = dataStore.getCollection( Book.class);            finalAggregationOutput output = collection        .aggregate(            Arrays.< DBObject >asList(                newBasicDBObject( "$project", newBasicDBObject( "title", 1)                   .append( "categories", 1) ),                newBasicDBObject( "$unwind", "$categories"),                newBasicDBObject( "$group", newBasicDBObject( "_id", "$categories")                   .append( "count", newBasicDBObject( "$sum", 1) ) )            )                       );            assertThat( output.results() ).hasSize( 4);    assertThat( output.results() ).containsExactly(                      newBasicDBObject( "_id", "Patterns").append( "count", 1),        newBasicDBObject( "_id", "Programming").append( "count", 3),        newBasicDBObject( "_id", "NoSQL").append( "count", 4),                    newBasicDBObject( "_id", "Databases").append( "count", 4)                   );} | 
Первое, что делает конвейер агрегации , это выбирает только заголовки и категории из коллекции документов книги . Затем он применяет операцию разматывания к массиву категорий, чтобы преобразовать его в простое поле с одним значением (которое для каждого документа книги создаст столько промежуточных документов, сколько категорий в этой книге). И наконец, группировка выполняется.
11. Что дальше
Эта часть дала нам первый взгляд на MongoDB с точки зрения разработчика приложений. В следующей части урока мы рассмотрим возможности шардинга MongoDB .


