Введение в Spring Roo: Часть 7. Разработка приложений Spring MongoDB при помощи Spring Roo

Быстрая разработка корпоративных приложений Spring MongoDB

MongoDB – это очень популярное документоориентированное горизонтально масштабируемое NoSQL-хранилище данных. При помощи Spring Roo версии 1.2 можно создавать Spring-приложения с MongoDB в качестве хранилища данных. Сначала мы рассмотрим MongoDB, а затем создадим корпоративное Spring MongoDB-приложение при помощи Spring Roo.

Шекхар Гулати, старший консультант, Xebia

Шекхар Гулати (Shekhar Gulati) работает Java-консультантом в Xebia India. Более шести лет он занимается корпоративными Java-приложениями. Имеет обширный опыт работы со Spring-проектами, такими как Spring, Spring-WS и Spring Roo. В сферу его интересов входят Spring, базы данных NoSQL, Hadoop, RAD-среды (такие как Spring Roo), облачные вычисления (в основном PaaS-сервисы, такие как Google App Engine, CloudFoundry, OpenShift). Является активным автором статей для JavaLobby, Developer.com, IBM developerWorks и своего собственного блога http://whyjava.wordpress.com/. Связаться с ним можно в Твиттере (@ http://twitter.com/#!/shekhargulati).



12.03.2013

В части 6 данной серии статей по Spring Roo были рассмотрены многочисленные новые возможности, представленные в Spring Roo 1.2. Одной из таких возможностей была поддержка создания MongoDB-приложений. Spring Roo MongoDB предназначено для реализации классических предложений среды Spring (улучшенная производительность и согласованная модель программирования) в MongoDB-приложения. Spring MongoDB – это подпроект Spring Data. Spring Data – это зонтичный проект с открытыми исходными кодами, содержащий много подпроектов для конкретных хранилищ данных. Сначала я представлю MongoDB, а затем мы создадим Spring MongoDB-приложение при помощи Spring Roo.

Введение в MongoDB

Коротко говоря, MongoDB – это документоориентированное, не использующее схем, быстрое и горизонтально масштабируемое NoSQL-хранилище данных с открытыми исходными кодами. Это определение содержит пять ключевых слов, которые очень важно понимать. Рассмотрим их поочередно:

  1. Документоориентированность. В MongoDB отсутствует понятие "строка", как в реляционных базах данных, но есть понятие "документ". MongoDB хранит данные в виде двоичных JSON-документов, или BSON, которые очень точно отображаются на доменные объекты. В MongoDB отсутствует понятие "соединение", но есть вложенные объекты. Рассмотрим пример приложения блога, имеющего два доменных объекта: blog и comment. В реляционной базе данных blog будет иметь отношение "один ко многим" с таблицей comment, и для извлечения comment для blog потребуется соединение. В MongoDB вместо соединений используются вложенные документы. В примере (см. листинг 1) представлен документ blog (блог), имеющий массив comment (комментарии). Извлечь комментарий конкретного блога можно без каких-либо соединений. MongoDB предоставляет богатые возможности для запросов, позволяя легко отфильтровывать ненужные поля и элементы.

    Листинг 1. Пример вложенного массива
    { 
        "author" : "shekhar", 
        "text" : "Hello MongoDB", 
        "title" : "Hello MongoDB" 
        "comments" : [ 
            { 
                "name" : "anonymous", 
                "comment" : "good blog" 
            }, 
            { 
                "name" : "guest", 
                "comment" : "awesome blog" 
            } 
        ], 
    }
  2. Отсутствие схем. MongoDB хранит документы в коллекциях, аналогично тому, как хранятся строки в таблицах реляционной СУБД. Но MongoDB не требует определения жесткой схемы коллекции. Каждый документ MongoDB может полностью отличаться от уже хранящихся в коллекции. Каждый документ в коллекции MongoDB является гетерогенным и может иметь совершенно иную структуру по сравнению с другими документами. Это означает, что в одной и той же коллекции можно хранить оба вида документов: blog и author (см. листинг 2).

    Листинг 2. Пример коллекции MongoDB
    {"author" : "shekhar", "text" : "Hello MongoDB","title" : "Hello MongoDB"}
    {"fullname":"Shekhar Gulati","email":"shekhargulati84@gmail.com","password":"xxxxxx"}
  3. Быстрота. MongoDB отличается высокой производительностью как по чтению, так и по записи. Конфигурация MongoDB по умолчанию обеспечивает более высокую производительность операций записи (по сравнению с реляционными СУБД), поскольку они работают по принципу "запустил и забыл" – записываются в RAM, а затем на диск. Для управления поведением в MongoDB вместе с операцией записи укажите значение WriteConcern. Рассмотрим различные значения WriteConcern.

    1. Normal. Это значение по умолчанию. Каждая операция записи работает по принципу "запустил и забыл", т.е. осуществляет запись в драйвер и возвращает управление. Она не ждет записи на сервере. Таким образом, если другой поток попытается прочитать документ непосредственно после записи, он может не найти его. При применении этого параметра велика вероятность потери данных. Не рассматривайте этот вариант, если важна долговечность данных и используется только один экземпляр сервера MongoDB.

    2. None. Аналогичен Normal, но с одним отличием. В режиме Normal при выходе сети из строя или возникновении других проблем с сетью генерируется исключительная ситуация. В режиме None проблемы с сетью не вызывают исключительную ситуацию. Это делает его очень ненадежным.

    3. Safe. Как следует из названия, этот вариант безопаснее, чем Normal или None. Операция записи ждет от сервера MongoDB подтверждения записи, но данные еще не пишутся на диск. В режиме Safe вы не столкнетесь с проблемой попытки чтения другим потоком объекта, который только что был записан, но не может быть найден. Safe гарантирует, что записанный объект всегда будет найден. Это прекрасно, но поскольку данные не записаны на диск, они все равно могут потеряться, если на сервере возникнет авария.

    4. Journal Safe. Перед рассмотрением этого варианта давайте поговорим о журналировании в MongoDB. Журналирование – это функциональность MongoDB, которая поддерживает предварительную запись в log-файл для всех операций. MongoDB не всегда отключается чисто, например, в случае использования команды kill -9, но при таком сценарии данные можно восстановить из файлов журнала. По умолчанию данные записываются в файлы журнала каждые 100 миллисекунд (мс). Это значение можно менять в диапазоне от 2 мс до 300 мс. В версии 2.0 на 64-разрядных серверах MongoDB журналирование включено по умолчанию. В случае использования Journal Safe операция записи будет ждать обновления файла журнала.

    5. Fysnc. В случае использования Fsync операции записи будут ждать, когда сервер сбросит данные на диск. Это самый безопасный вариант для единичного узла, потому что данные теряются только при повреждении диска.

    В моем блоге есть подробная запись под названием "Влияние различных значений WriteConcern на производительность единичного узла" (см. раздел Ресурсы). Прочите ее.

  4. Горизонтальная масштабируемость. MongoDB – это горизонтально-масштабируемое хранилище данных, что означает возможность добавления в кластер MongoDB-серверов и обработки большего числа операций чтения/записи. Для достижения горизонтальной масштабируемости MongoDB использует шардинг, т.е. добавляет экземпляры MongoDB-серверов для обработки растущих нагрузки и объема данных без влияния на производительность приложений. Самым привлекательным является то, что приложения не должны выполнять шардинг; MongoDB делает это автоматически. Автоматический шардинг в данной статье не рассматривается. Ссылка на документацию MongoDB с информацией о шардинге приведена в разделе Ресурсы.

  5. NoSQL. Возможно, в последние несколько лет вам приходилось слышать термин NoSQL. NoSQL – это аббревиатура от Not Only SQL (не только SQL). К NoSQL можно отнести обширный перечень хранилищ данных, которые не следуют модели реляционных СУБД и не используют SQL в качестве основного языка запросов. MongoDB поддерживает язык запросов, но он не похож на SQL и основан на JSON-документах.

MongoDB поддерживает операции, аналогичные операциям реляционных СУБД, такие как индексы, запросы, план выполнения, а также агрегатные функции (например, group), чтобы сохранить связь с реляционными СУБД и сократить время обучения. Среди других замечательных возможностей MongoDB - способность выполнять операции Map-Reduce, а также геолокационные индексирование и запросы (ссылка на документацию приведена в разделе Ресурсы).

Терминология MongoDB

Очень простую терминологию MongoDB легко освоить и запомнить. В таблице 1 приведено сравнение с реляционными базами данных.

Таблица 1. Сравнение понятий MongoDB с понятиями реляционных баз данных
Реляционная СУБДMongoDB

База данных

База данных

Таблица

Коллекция

Строка

Документ

В MongoDB все находится в базе данных, которую можно создать при помощи команды. База данных в MongoDB может иметь несколько коллекций, так же как база данных в реляционной СУБД может иметь несколько таблиц. Коллекция в MongoDB может иметь несколько документов, так же как таблица в реляционной СУБД может иметь несколько строк. В отличие от реляционной СУБД, где строка имеет фиксированную структуру, которой подчиняются все члены, в MongoDB документ не имеет определенной структуры, и два документа в коллекции могут иметь абсолютно разные структуры.


Работа с MongoDB

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

  1. Загрузите MongoDB. Можно загрузить tarball- или zip-файл последней версии (в настоящее время это 2.0.4) MongoDB для вашей операционной системы с Web-сайта MongoDB (см. раздел Ресурсы).

  2. После загрузки MongoDB распакуйте zip-файл в удобное место и сделайте shell-файлы исполняемыми.

  3. В командной строке перейдите в папку bin распакованной установки MongoDB и выполните команду mongod. По умолчанию mongoDB хранит все данные в папке /data/db, поэтому может понадобиться создать эту папку и сделать пользователя ее владельцем. Можно также не использовать папку /data/db по умолчанию, указав местоположение при помощи атрибута dbpath (см. листинг 3).

Листинг 3. Запуск MongoDB
shekhar@shekhar:~/tools/mongodb/mongodb2/bin$ ./mongod 
--dbpath=/home/shekhar/data/db 
Sat Mar 31 19:16:48 
Sat Mar 31 19:16:48 warning: 32-bit servers don't have journalling enabled by 
default. Please use --journal if you want durability. 
Sat Mar 31 19:16:48 
Sat Mar 31 19:16:48 [initandlisten] MongoDB starting : pid=6033 port=27017 
dbpath=/home/shekhar/data/db 32-bit host=shekhar 
Sat Mar 31 19:16:48 [initandlisten] 
Sat Mar 31 19:16:48 [initandlisten] ** NOTE: when using MongoDB 32 bit, you 
are limited to about 2 gigabytes of data 
Sat Mar 31 19:16:48 [initandlisten] ** see 
http://blog.mongodb.org/post/137788967/32-bit-limitations 
Sat Mar 31 19:16:48 [initandlisten] ** with --journal, the limit is lower 
Sat Mar 31 19:16:48 [initandlisten] 
Sat Mar 31 19:16:48 [initandlisten] db version v2.0.1, pdfile version 4.5 
Sat Mar 31 19:16:48 [initandlisten] git version: 
3a5cf0e2134a830d38d2d1aae7e88cac31bdd684 
Sat Mar 31 19:16:48 [initandlisten] build info: Linux domU-12-31-39-01-70-B4 
2.6.21.7-2.fc8xen #1 SMP Fri Feb 15 12:39:36 EST 2008 i686 BOOST_LIB_VERSION=1_41 
Sat Mar 31 19:16:48 [initandlisten] options: { dbpath: "/home/shekhar/data/db" } 
Sat Mar 31 19:16:48 [websvr] admin web console waiting for connections on port 28017 
Sat Mar 31 19:16:48 [initandlisten] waiting for connections on port 27017

После запуска сервера MongoDB можно просмотреть основную информацию в его Web-консоли по адресу http://localhost:28017/. Для выполнения операций необходим клиент. MongoDB предоставляет клиента командной строки mongo, который находится в папке bin.

Для запуска клиента выполните команду ./mongo. Должна появиться информация, приведенная в листинге 4.

Листинг 4. Запуск клиента MongoDB
shekhar@shekhar:~/tools/mongodb/mongodb2/bin$ ./mongo 
MongoDB shell version: 2.0.1 
connecting to: test

Листинг 4 показывает, что клиент mongo подключился к базе данных test. После подключения к серверу можно выполнять различные операции, например вставку документа в коллекцию и поиск документа.

Создание базы данных

Для начала создадим базу данных. В MongoDB нет команды создания базы данных. Необходимо просто выбрать базу данных при помощи команды use, и она создастся сама (см. листинг 5).

Листинг 5. Выбор базы данных
> use springroopart7
switched to db springroopart7

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

Создание коллекций

Следующим шагом после создания базы данных является создание коллекции в базе данных. Коллекцию можно создать при помощи операции db.createCollection() (см. листинг 6).

Листинг 6. Создание коллекции
> db.createCollection("blogs") 
{ "ok" : 1 }

В листинге 6 выполняется операция создания коллекции под названием blogs, и MongoDB отвечает, что эта коллекция успешно создана.

Другим способом создания коллекции является сохранение документа в коллекцию. MongoDB создает коллекцию, если она не существует. В противном случае MongoDB добавит документ в существующую коллекцию. Это будет рассмотрено ниже.

Вставка документов

После создания коллекции blogs вставим документ blog в коллекцию. Необходимо создать JSON-документ и вставить его в коллекцию (см. листинг 7).

Листинг 7. Пример вставки JSON-документа
> db.blogs.insert({"author":"shekhar","title" : "Getting started with MongoDB",
"text":"MongoDB is an open-source document oriented, schema-free, fast and horizontally 
        scalable NoSQL datastore", 
"tags":["mongodb","nosql"]})

Команда, приведенная в листинге 7, создает новый документ blog и вставляет его в коллекцию blogs. Как видите, документ достаточно функциональный, поскольку содержит теги array field. Можно легко запросить все документы blog, соответствующие конкретному тегу. Можно иметь текстовые и числовые поля, даты, массивы и т.д.

Поиск документов

Поскольку мы сохранили только один документ, его можно просмотреть при помощи операции findOne, которая возвратит любой документ (один) этой коллекции или первый документ, соответствующий указанному запросу. Для поиска любого документа в коллекции blogs выполните команду, приведенную в листинге 8.

Листинг 8. Пример извлечения документа
> db.blogs.findOne() 
{ 
    "_id" : ObjectId("4f784a1c61d2e3bcf01bcff6"), 
    "author" : "shekhar", 
    "title" : "Getting started with MongoDB", 
    "text" : "MongoDB is an open-source document oriented, schema-free, fast and 
horizontally scalable NoSQL datastore", 
    "tags" : [ 
        "mongodb", 
        "nosql" 
    ] 
}

Чтобы найти только один документ, автором которого является shekhar, выполните операцию db.blogs.findOne({"author":"shekhar"}).

Для запроса по тегам выполните команду, приведенную в листинге 9. Как видите, она похожа на предыдущие запросы.

Листинг 9. Пример работы findOne
> db.blogs.findOne({"tags":"mongodb"}) 
{ 
    "_id" : ObjectId("4f784a1c61d2e3bcf01bcff6"), 
    "author" : "shekhar", 
    "title" : "Getting started with MongoDB", 
    "text" : "MongoDB is an open-source document oriented, schema-free, fast and 
horizontally scalable NoSQL datastore", 
    "tags" : [ 
        "mongodb", 
        "nosql" 
    ] 
}

Приведенные выше запросы возвратят только один документ. Если нужно найти все документы, автором которых является shekhar, с тегами mongodb, можно использовать метод find:
> db.blogs.find({"author":"shekhar","tags":"mongodb"}) .

Следует знать, что метод find возвращает указатель, не документ. По умолчанию в MongoDB-клиенте отображаются первые десять документов. Для просмотра всех документов следует выполнить итерацию по указателю (см. листинг 10).

Листинг 10. Пример отображения документов посредством нескольких итераций
> var cursor = db.blogs.find({"author":"shekhar","tags":"mongodb"}) 
> while(cursor.hasNext())printjson(cursor.next()) 
{ 
    "_id" : ObjectId("4f784a1c61d2e3bcf01bcff6"), 
    "author" : "shekhar", 
    "title" : "Getting started with MongoDB", 
    "text" : "MongoDB is an open-source document oriented, schema-free, fast and 
horizontally scalable NoSQL datastore", 
    "tags" : [ 
        "mongodb", 
        "nosql" 
    ] 
}

Другие команды

В MongoDB можно выполнить множество других команд. Для просмотра всех доступных операций с коллекцией выполните команду help (см. листинг 11). Для краткости я не показываю все команды. Полный список вы можете увидеть в своем собственном клиенте mongo.

Листинг 11. Результаты выполнения команды help
> db.blogs.help() 
DBCollection help
db.blogs.find().help() - show DBCursor help 
db.blogs.count() 
db.blogs.dataSize() 
db.blogs.distinct( key ) - for example db.blogs.distinct( 'x' ) 
db.blogs.drop() drop the collection 
db.blogs.dropIndex(name) 
db.blogs.dropIndexes() 
db.blogs.ensureIndex(keypattern[,options]) - options is an object with these 
possible fields: name, unique, dropDups 
db.blogs.reIndex() 
db.blogs.find([query],[fields]) - query is an optional query filter. fields is 
optional set of fields to return.

Теперь, после краткого знакомства с основами MongoDB, можно приступить к созданию Spring-приложения, использующего MongoDB в качестве хранилища данных. Дополнительную информацию можно получить в документации по MongoDB или других публикациях, размещенных на developerWorks (см. раздел Ресурсы).


Дизайн схемы

Создаваемое нами приложение будет называться ShortNotes. Оно имеет два доменных объекта: Notebook (блокнот) и Note (заметка). Notebook может иметь много объектов note. Существует несколько способов спроектировать схему для этого приложения. Давайте рассмотрим различные варианты дизайна схемы (Schema Design).

Объект Notebook с массивом встроенных объектов Notes

Документ Notebook с вложенным, встроенным массивом документов Note выглядит наиболее очевидным дизайном схемы для хранилища документов. Этот дизайн устраняет соединения и предоставляет функциональную доменную модель. В листинге 12 показана коллекция объектов notebook, имеющая два документа со встроенным массивом документов note.

Листинг 12. Пример схемы со встроенным массивом
{ 
    "_id" : ObjectId("4f7ff4c9c2fceff338eb9a82"), 
    "name" : "MongoDB Notebook", 
    "author" : "shekhar", 
    "created" : ISODate("2012-04-07T08:02:59.572Z"), 
    "notes" : [ 
        { 
             "_id" :"f7ff4c9c2fceff338eb9a443"
            "title" : "Getting Started with MongoDB", 
            "note" : "Getting Started with MongoDB", 
            "created" : ISODate("2012-04-07T08:02:59.572Z") 
        }, 
        { 
            "_id" :"efvvgf7ff4c9ceff338eb9a443"
            "title" : "Setting up Replication", 
            "note" : "Setting up Replica Set in MongoDB", 
            "created" : ISODate("2012-04-07T08:02:59.572Z") 
        }
    ] 
} 
{ 
    "_id" : ObjectId("4f7ff4dcc2fceff338eb9a83"), 
    "name" : "MongoDB Notebook", 
    "author" : "tom", 
    "created" : ISODate("2012-04-07T08:03:31.491Z"), 
    "notes" : [ 
        { 
            "_id" :"be7ff4c332fceff338eb9a443"
            "title" : "Getting Started with MongoDB", 
            "note" : "Getting Started with MongoDB", 
            "created" : ISODate("2012-04-07T08:03:31.491Z") 
        }, 
        { 
            "_id" :"aaff4c9c3fceff338eb9a443"
            "title" : "Setting up Sharding", 
            "note" : "Setting up Sharded Cluster in MongoDB", 
            "created" : ISODate("2012-04-07T08:03:31.491Z") 
        } 
    ] 
}

Схема в листинге 12 выглядит хорошо, и можно запрашивать документы notebook и note.

Для поиска всех документов notebooks с именем MongoDB Notebook и автором shekhar можно написать следующий запрос:

 db.notebooks.find({"name":"MongoDB Notebook","author":"shekhar"})

Также можно легко запросить документы note при помощи следующей команды:

db.notebooks.findOne({"notes._id":"be7ff4c332fceff338eb9a443"})

Все немного усложнится, если потребуется обновить конкретный документ note в массиве или удалить конкретный элемент из массива. Для этого можно использования модификаторы $set и $pull, но необходимо хорошо их знать. Пример, приведенный в листинге 13, удаляет note с _id f7ff4c9c2fceff338eb9a443 и notebook с автором shekhar.

Листинг 13. Пример удаления документа note при помощи модификаторов $set и $pull
> db.notebooks.update({"author":"shekhar"},{"$pull":{"notes":
{"_id":"f7ff4c9c2fceff338eb9a443"}}}) 
> 
> 
> db.notebooks.findOne({"author":"shekhar"}) 
{ 
    "_id" : ObjectId("4f80137fc2fceff338eb9a8a"), 
    "author" : "shekhar", 
    "created" : ISODate("2012-04-07T10:14:01.827Z"), 
    "name" : "MongoDB Notebook", 
    "notes" : [ 
        { 
            "_id" : "efvvgf7ff4c9ceff338eb9a443", 
            "title" : "Setting up Replication", 
            "note" : "Setting up Replica Set in MongoDB", 
            "created" : ISODate("2012-04-07T10:14:01.827Z") 
        } 
    ] 
}

Эта схема имеет несколько проблем.

  1. Первой проблемой является то, что нельзя извлечь конкретный документ note. Кажется, например, что можно легко найти документ note с _id, равным "f7ff4c9c2fceff338eb9a443", выполнив запрос, приведенный в листинге 14.

    Листинг 14. Пример попытки удалить note на основе его ID
    > db.notebooks.find({"notes._id":"f7ff4c9c2fceff338eb9a443"})
    >{ 
        "_id" : ObjectId("4f7ff9edc2fceff338eb9a84"), 
        "name" : "MongoDB Notebook", 
        "author" : "shekhar",
        "created" : ISODate("2012-04-07T08:24:41.782Z"),
        "notes" : [ 
            { 
                "_id" : "f7ff4c9c2fceff338eb9a443",
                "title" : "Getting Started with MongoDB", 
                "note" : "Getting Started with MongoDB",
                "created" : ISODate("2012-04-07T08:24:41.782Z")
            },
            {
                "_id" : "efvvgf7ff4c9ceff338eb9a443",
                "title" : "Setting up Replication",
                "note" : "Setting up Replica Set in MongoDB",
                "created" : ISODate("2012-04-07T08:24:41.782Z")
            }
        ]
    }

    Этот запрос возвратит объект notebook со всеми объектами note. Чтобы получить нужный объект note, необходимо самому сделать всю работу по фильтрации на стороне клиента. Запрос на эту функциональность зарегистрирован в MongoDB jira (см. раздел Ресурсы), и возможно вы сможете увидеть ее в будущем.

  2. Вторая проблема, с которой можно столкнуться, – наличие тысяч документов note в одном документе notebook. Операции записи в MongoDB могут завершиться неудачно из-за ограничений размера одного документа MongoDB – 16 MБ. Если документ больше 16 МБ, подумайте о нормализации схемы, чтобы иметь несколько коллекций.

  3. Третья проблема – наличие тысяч документов note большого размера. Можно столкнуться с нехваткой памяти на стороне клиента. Для такого сценария подумайте о разбиении на страницы при помощи оператора MongoDB $slice.

Реляционный подход: Notebook и Note как отдельные коллекции

Второй подход к проектированию схемы – отдельные коллекции для Notebook и Notes, где каждый документ note содержит ссылку на свой notebook. Кажется, что это не соответствует дизайну схемы MongoDB, поскольку мы движемся к парадигме реляционных СУБД. Но все-таки это один из возможных подходов в некоторых ситуациях. Смысл в том, что вы не обязаны использовать какой-либо подход. При разработке дизайна нужно руководствоваться особенностями своего приложения. Поскольку Notebook является очень простым документом всего лишь с парой полей, каждый документ note может содержать документ notebook, а не его идентификатор. При этом устраняется взаимодействие между коллекциями и упрощаются запросы. В листинге 15 приведен документ notebook.

Листинг 15. Пример простого документа notebook
{ 
    "name" : "MongoDB Notebook", 
    "author" : "shekhar", 
    "created" : ISODate("2012-04-07T09:36:01.062Z") 
}

Документ note в MongoDB Notebook мог бы выглядеть так, как показано в листинге 16.

Листинг 16. Пример документа note
{ 
    "_id" : ObjectId("4f800b5cc2fceff338eb9a87"), 
    "title" : "Getting Started with MongoDB", 
    "note" : "Getting Started with MongoDB", 
    "created" : ISODate("2012-04-07T09:38:42.462Z"), 
    "notebook" : { 
        "_id" : ObjectId("4f800af3c2fceff338eb9a86"), 
        "name" : "MongoDB Notebook", 
        "author" : "shekhar", 
        "created" : ISODate("2012-04-07T09:36:01.062Z") 
    } 
}

При таком дизайне мы можем легко выполнять запросы к обеим коллекциям: notes и notebooks. Для поиска всех узлов в Notebook с именем MongoDB Notebook создадим запрос db.notes.findOne({"notebook.name":"MongoDB Notebook"}).

Можно также создать индекс по notebook.name для очень быстрого чтения запросов: db.notes.ensureIndex({"notebook.name":1}).

Такой дизайн схемы имеет одну проблему: при изменении документа в коллекции Notebooks необходимо вручную изменить все ее ссылки в коллекции Notes.

Для нашего приложения мы используем реляционный подход с двумя отдельными коллекциями для Notes и Notebooks.


Установка Spring Roo

Перед созданием приложения убедитесь в том, что на вашей машине установлена Spring Roo 1.2.1. Для установки автономной версии Spring Roo:

  1. Загрузите автономный процессор командной строки Roo или используйте плагин Roo SpringSource Tool Suite (STS) (см. раздел Ресурсы). Я предлагаю загрузить обе версии и использовать их совместно, поскольку STS предлагает много дополнительных функциональных возможностей Eclipse для Spring-приложений.

  2. Распакуйте Spring Roo в выбранный вами каталог.

  3. Установите переменные окружения:

    1. На Windows-машинах добавьте %ROO_HOME%\bin в переменную path, где ROO_HOME – это путь к распакованным файлам Roo.

    2. На *nix-машинах создайте символическую ссылку на $ROO_HOME/bin/roo.sh (например, sudo ln -s ~/spring-roo-1.x.x/bin/roo.sh /usr/bin/roo).


Создание приложения ShortNotes при помощи Spring Roo

После настройки и запуска Spring Roo можно приступить к созданию приложения. Для создания приложения ShortNotes выполните следующие действия:

  1. Откройте процессор командной строки вашей операционной системы и создайте каталог shortnotes, используя команду mkdir.

  2. Перейдите в каталог shortnotes, выполните команду roo (или roo.sh) и нажмите клавишу Enter. Загрузится Roo shell.

  3. Прежде всего, создайте проект в Roo shell, используя команду project:

    project --topLevelPackage com.xebia.shortnotes --projectName shortnotes

    Команда project создаст проект шаблона Spring Maven. Главными артефактами, генерируемыми этой командой, являются файлы pom.xml и applicationContext.xml. Файл pom.xml содержит все необходимые jar-файлы Spring, различные maven-плагины и репозитории. Файл applicationContext.xml является обобщенным файлом контекста Spring, специфичные компоненты которого сканируют пакет com.xebia.shortnotes, так что Spring может найти все классы, аннотированные аннотациями @Component, @Service и @Repository. Сканирование компонентов исключит все классы, аннотированные аннотацией @Controller, так как они будут загружаться контекстом Web-приложения.

  4. Выполните команду hint, которая порекомендует следующее действие. Будет предложено выполнить jpa setup или mongo setup. Поскольку мы создаем MongoDB-приложение, используйте команду mongo setup:

    mongo setup --databaseName shortnotes --host localhost --port 27017

    У команды mongo setup нет обязательных атрибутов, но для данного примера я указал атрибуты databaseName, host и port. Если не указать эту команду, Spring Roo будет использовать значения по умолчанию, аналогичны указанным. Можно указать три дополнительных атрибута: username, password и cloudfoundry. Атрибуты username и password используются для доступа к MongoDB в случае, если база данных требует аутентификации. Атрибут cloudfoundry используется в случае, если нужно развернуть приложение на платформе CloudFoundry в качестве сервиса. Я продемонстрирую это позже.

    Команда mongo setup делает три вещи:

    • Добавляет необходимые зависимости в pom.xml.
    • Создает новый файл Spring-контекста applicationContext-mongo.xml.
    • Создает файл database.properties.

    Зависимости, добавляемые этой командой, предназначены для поддержки Spring MongoDB и проверки bean-компонентов. Файл database.properties содержит свойства host, port и databaseName. Файл applicationContext- mongo.xml содержит наиболее важную информацию. Он использует пространство имен mongo, позволяя избежать написания объявлений bean-комонентов Spring, что упрощает жизнь разработчикам. Просмотрите фрагмент кода applicationContext-mongo.xml (см. листинг 17).

    Листинг 17. Пример applicationContext-mongo.xml
    <mongo:db-factory dbname="${mongo.database}" host="${mongo.host}" -----> (1) 
    id="mongoDbFactory" port="${mongo.port}"/> 
    
    <mongo:repositories base-package="com.xebia.shortnotes"/> -------------> (2)
    
    <context:annotation-config/> ------------------------------------------> (3)
    
    <bean ----->(4) id="mongoTemplate"> 
    
        <constructor-arg ref="mongoDbFactory"/> 
    
    </bean>

    В файле applicationContext-mongo.xml есть небольшая ошибка. Она заключается в том, что dbname соответствует свойству mongo.database, но это свойство отсутствует в файле database.properties. Замените mongo.database на mongo.name. Взгляните на четыре строки в XML-фрагменте, приведенном в листинге 17:

    1. Первая строка создает фабрику, которая сгенерирует экземпляр Mongo для подключения к базе данных MongoDB с названием shortnotes, работающую на localhost, порт 27017. Это объявление может также принимать username и password для аутентификации. Еще одним важным и полезным атрибутом, отсутствующим в этом объявлении, является write-concern, о котором я упоминал ранее. Он позволяет менять поведение операций записи. Значением по умолчанию является NONE, работающее по принципу "запустил и забыл", поэтому существует вероятность потери данных.

    2. Вторая строка гарантирует, что все интерфейсы, расширяющие интерфейс MongoRepository, или любые их супертипы в пакете com.xebia.shortnotes будут иметь bean-компонент репозитория Spring. Bean-компонент реализации репозитория, создаваемый динамически, будет поддерживать CRUD и разбиение на страницы. Это экономит время разработчика и удаляет большой объем кода шаблона для методов CRUD и pagination. Также могут генерироваться CRUD-методы и методы finder, основанные на определенных соглашениях. Например, для поиска всех объектов notebooks данного автора можно объявить метод, как показано ниже, и его реализация сгенерируется во время исполнения. Более подробная информация приведена в документации по Spring MongoDB (см. раздел Ресурсы).

       public List<Notebook> findByAuthor(String author)
    3. Третья строка используется для перевода всех RuntimeException в соответствующие исключительные ситуации иерархии Spring.

    4. Последняя строка создает bean-комнент MongoTemplate, который может использоваться для выполнения операций create, delete, find и update с другими полезными методами. Она также обеспечивает отображение между вашими доменными объектами и документами MongoDB. С MongoTemplate можно делать все, что можно делать с репозиториями, но не наоборот. Это самый важный класс поддержки Spring MongoDB. Методы, предоставляемые в классе MongoTemplate, очень точно соответствуют методам, которые вы будете выполнять в клиенте командной строки MongoDB. Класс MongoTemplate использует Query и Criteria API для создания запросов. В нашем примере для поиска всех документов notebooks определенного автора выполните команду:

      List<Notebook> myNotebooks = 
           mongoTemplate.find(Query.query(Criteria.where("author").is
                     ("shekhar")),Notebook.class)
  5. После настройки MongoDB создайте логические объекты Notebook и Note. Используйте команду mongo entity (см. листинг 18).

    Листинг 18. Создание логических объектов
     entity mongo --class ~.domain.Notebook --testAutomatically 
     entity mongo --class ~.domain.Note --testAutomatically

    Две команды в листинге 18 создают Notebook.java и Note.java вместе с некоторыми ITD-файлами и кодом инфраструктуры тестирования. Атрибут testAutomatically ответственен за создание тестов интеграции для соответствующих доменных классов. Важными артефактами, генерируемыми командой mongo entity, являются Notebook_Roo_Mongo_Entity.aj и Note_Roo_Mongo_Entity.aj. Эти ITD-файлы указывают, что логические объекты должны иметь аннотацию @Persistent, а id имеет тип BigInteger. Spring MongoDB преобразует BigInteger id в ObjectId перед сохранением. Остальные артефакты предназначены для указания методов getter и setter для полей логических объектов и метода toString().

  6. После создания добавьте в объекты некоторые поля, используя команду field. Сначала добавьте поля в объект Notebook, используя команды, приведенные в листинге 19.
    Листинг 19. Создание полей Notebook
    focus --class ~.domain.Notebook 
    field string --fieldName name --notNull 
    field string --fieldName author --notNull 
    field date --fieldName created --type java.util.Date --notNull

    Теперь добавьте поля в объект Note, используя команды, приведенные в листинге 20.

    Листинг 20. Создание полей Note
    focus --class ~.domain.Note 
    field string --fieldName title --notNull 
    field string --fieldName content --notNull --sizeMax 4000 
    field date --fieldName created --type java.util.Date --notNull

    Интересно в листинге 20 то, что в обоих логических объектах создается необновляемое поле name. Это означает, что при построении пользовательского интерфейса для этих объектов созданное поле нельзя редактировать.

  7. Созданная нами доменная модель не завершена, поскольку мы не определили отношение между Notebook и Note. Как уже говорилось, каждый документ note будет иметь встроенный документ notebook. Это означает, что если имеется Notebook со 100 документами Notes, будет иметься один оригинальный документ notebook в коллекции Notebook и 100 копий notebook в 100 notes. Однако такой дизайн схемы проще и больше соответствует нашим требованиям. Для добавления поля notebook в логический объект Note выполните команду:

    field reference --type ~.domain.Notebook --fieldName notebook --notNull

    Эта команда гарантирует, что Note будет создан только в том случае, если Notebook уже существует.

  8. После завершения доменной модели можно создать репозитории для созданных ранее логических объектов. Репозитории предоставляются службой поддержки Spring MongoDB. Чтобы создать репозиторий для объекта Notebook выполните команду, приведенную в листинге 21. В нем также содержится пример выходных данных.

    Листинг 21. Пример выходных данных создания репозитория
    repository mongo --interface ~.repository.NotebookRepository 
                   --entity ~.domain.Notebook
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/repository
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/repository/NotebookRepository.java
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/repository/
                   NotebookRepository_Roo_Mongo_Repository.aj

    Выходные данные показывают, что созданы папка repository, Java™-файл и ITD-файл. Файл NotebookRepository.java содержит метод findAll(), предназначенный для поиска всех Notebooks и аннотированный аннотацией @RooMongoRepository, которая указывает Roo генерировать ITD-файл NotebookRepository_Roo_Mongo_Repository.aj. Во время компиляции код из ITD будет помещен в интерфейс. ITD-файл находится там, где и весь код. В листинге 22 приведен ITD-файл, сгенерированный Spring Roo.

    Листинг 22. Пример ITD-файла
    privileged aspect NotebookRepository_Roo_Mongo_Repository { 
        declare parents: NotebookRepository extends 
            PagingAndSortingRepository<Notebook, BigInteger>; 
        declare @type: NotebookRepository: @Repository; 
    }

    ITD-файл определяет, что интерфейс NoteBookRepository аннотируется аннотацией @Repository и должен расширять интерфейс PagingAndSortingRepository. Созданный интерфейс NotebookRepository имеет аннотацию @Repository и расширяет PagingAndSortingRepository. Как говорилось на шаге 4, реализация репозитория создается во время исполнения. После реализации интерфейса PagingAndSortingRepository у нас появятся CRUD-методы и дополнительные методы для извлечения логических объектов при помощи разбиения на страницы и сортировки.

    Аналогичным образом можно создать репозиторий для объекта Note:

    repository mongo --interface ~.repository.NoteRepository --entity ~.domain.Note
  9. Затем добавим уровень сервиса, соответствующий объектам Notebook и Note. Он является фасадом между уровнем контроллера и уровнем репозитория. Сюда можно поместить специфичную бизнес-логику приложения. В листинге 23 приведена команда service для объекта Notebook и ее выходные данные.

    Листинг 23. Выходные данные команды service для Notebook
    service --interface ~.service.NotebookService --entity ~.domain.Notebook
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service/NotebookService.java
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service/NotebookServiceImpl.java
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service/NotebookService_Roo_Service.aj
    Created SRC_MAIN_JAVA/com/xebia/shortnotes/service/NotebookServiceImpl_Roo_Service.aj

    Аналогично можно создать уровень сервиса для объекта Note: service --interface ~.service.NoteService --entity ~.domain.Note.

  10. Наконец, можно создать контроллеры Spring MVC для приложения, используя команды web mvc (см. листинг 24).

    Листинг 24. Пример команд web mvc
    web mvc setup 
    web mvc all --package ~.web

    Команда web mvc setup установит необходимую инфраструктуру для Spring MVC-приложения, добавив зависимости Spring MVC в файл pom.xml, создав файл контекста Web-приложения webmvc-config.xml, сгенерировав библиотеки тегов и настроив интернационализацию. Команда web mvc all создаст классы контроллера и сгенерирует соответствующие представления.

  11. Выйдите из roo shell.

  12. Запустите сервер MongoDB, используя исполняемый файл mongod.

  13. Для запуска приложения выполните команду mvn tomcat:run, которая запустит сервер tomcat. Откройте Web-браузер и перейдите по адресу http://localhost:8080/shortnotes/. Можно создать notebook, а затем note, указав, какой объект notebook использовать. Например, я создал объект notebook с названием MongoDB Notes и автором shekhar. Затем я создал объект note с заголовком и содержимым "Getting Started with MongoDB" и назначил его созданному объекту notebook. В листинге 25 показаны документы Notebook и Notes.

    Листинг 25. Примеры документов Notebook и Notes
    {
        "_id" : ObjectId("4f808ee084aeed43f24b692b"),
        "_class" : "com.xebia.shortnotes.domain.Notebook", 
        "name" : "MongoDB Notes", 
        "author" : "shekhar", 
        "created" : ISODate("2012-04-07T19:00:48.386Z") 
    }
    
    {
        "_id" : ObjectId("4f808ef184aeed43f24b692c"), 
        "_class" : "com.xebia.shortnotes.domain.Note", 
        "title" : "Getting Started with MongoDB", 
        "content" : "Getting Started with MongoDB", 
        "created" : ISODate("2012-04-07T19:01:05.031Z"),
        "notebook" : { 
            "_id" : ObjectId("4f808ee084aeed43f24b692b"), 
            "name" : "MongoDB Notes", 
            "author" : "shekhar", 
            "created" : ISODate("2012-04-07T19:00:48.386Z")
        } 
    }
  14. При обновлении имени Notebook в MongoDB Cookbook ни одна из Notes этого notebook не будет обновлена. Это ожидаемое поведение. Для исправления этой ситуации обновите все Notes после обновления Notebook. Создайте новый метод updateNotesWithNotebook в NoteServiceImpl (см. листинг 26).

    Листинг 26. Создание команды updateNotesWithNotebook
    @Autowired 
    MongoTemplate mongoTemplate; 
    	 
    public void updateNotesWithNoteBook(Notebook notebook){ 
        Update update = new Update().set("notebook.name", 
        notebook.getName()).set("notebook.author", notebook.getAuthor()); 
    
    Query query = Query.query(Criteria.where("notebook._id").is(new 	
        ObjectId(notebook.getId().toString(16)))); 
            mongoTemplate.updateMulti(query, update, Note.class);

    Код, приведенный в листинге 26, использует метод updateMulti класса MongoDBTemplate для обновления всех документов, соответствующих обновленному Notebook. Метод updateMulti() принимает три аргумента: query object, update object и collection entity type. Объект query указывает выбрать все документы Note, имеющие Notebooks с данным id. Затем создается объект update для установки автора и имени Notebook.

    После создания метода необходимо вызвать его после обновления Notebook в NotebookController (см. листинг 27). Не забудьте поместить метод update из ITD-файла в java-класс NotebookController.

    Листинг 27. Пример копирования метода update из ITD-файла
    @Autowired 
    private NoteService noteService; 
    
    @RequestMapping(method = RequestMethod.PUT, produces = "text/html") 
    public String update(@Valid Notebook notebook, BindingResult bindingResult, 
    Model uiModel, HttpServletRequest httpServletRequest) { 
    
            if (bindingResult.hasErrors()) { 
                populateEditForm(uiModel, notebook); 
                return "notebooks/update"; 
            } 
            uiModel.asMap().clear(); 
            notebookService.updateNotebook(notebook); 
            noteService.updateNotesWithNoteBook(notebook); 
            return "redirect:/notebooks/" + 	
                encodeUrlPathSegment(notebook.getId().toString(), httpServletRequest); 
     }
  15. С этой же проблемой вы столкнетесь при удалении Notebook, поскольку Notes все еще будут ссылаться на него. Поэтому имеет смысл удалить все Notes для конкретного Notebook при его удалении. Добавьте метод removeNotes() в класс NoteServiceImpl (см. листинг 28).

    Листинг 28. Пример метода removeNotes()
    public void removeNotes(Notebook notebook){ 
        mongoTemplate.remove(Query.query(Criteria.where("notebook._id").is(new
            ObjectId(notebook.getId().toString(16)))), Note.class);
    }

    Метод removeNotes() вызывается при удалении Notebook. Добавьте вызов removeNotes в метод delete() контроллера NotebookController (см. листинг 29).

    Листинг 29. Добавление removeNotes в NotebookController
    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE,
            produces = "text/html")
    public String delete(@PathVariable("id") BigInteger id,
            @RequestParam(value = "page", required = false) Integer page,
            @RequestParam(value = "size", required = false) Integer size,
            Model uiModel) {
    Notebook notebook = notebookService.findNotebook(id);
    
    noteService.removeNotes(notebook);
    notebookService.deleteNotebook(notebook);
    uiModel.asMap().clear();
    uiModel.addAttribute("page", (page == null) ? "1" :
        page.toString());
    uiModel.addAttribute("size", (size == null) ? "10" :
        size.toString());
    return "redirect:/notebooks";
    }

    Можно снова выполнить приложение, используя mvn tomcat:run, и создать notebook, а затем создать notes и обновить notebook. Вы увидите, что notes тоже содержат обновленный документ notebook. Можно также попытаться удалить notebook, и все notes для него тоже удалятся.

Развертывание в Cloud Foundry

После создания приложения shortnotes приступим к его развертыванию. Spring-приложения можно развертывать в открытом облаке Cloud Foundry без каких-либо изменений. Более подробно Cloud Foundry я описывал в частях 4 и 6 данной серии. Обратитесь к этим статьям, если в этом есть потребность. В данной статье для развертывания приложения conference в Cloud Foundry мы будем использовать vmc ruby gem. Выполните следующие действия:

  1. Снова запустите roo shell, поскольку нужно повторно запустить команду mongo setup. На этот раз мы переключимся в экземпляр облака Cloud Foundry MongoDB. Выполните приведенную ниже команду в Roo shell. Она обновит applicationContext-mongo.xml.

    mongo setup --cloudFoundry
  2. Установите клиент vmc на вашей машине. (Ссылка на пошаговое руководство по установке интерфейса командной строки приведена в разделе Ресурсы.)

  3. Войдите в открытое облако Cloud Foundry, используя учетные данные, полученные при регистрации. Выполните команду vmc login. Появится запрос адреса электронной почты и пароля (см. листинг 30).

    Листинг 30. Вход в Cloud Foundry при помощи vmc
    shekhar@shekhar:~/dev/writing/shortnotes/target$ vmc login 
    Attempting login to [http://api.cloudfoundry.com] 
    Email: shekhargulati84@gmail.com 
    Password: ************* 
    Successfully logged into [http://api.cloudfoundry.com]
  4. После установки клиента vmc выполните команду maven build для приложения shortnotes. Выполните команду mvn clean install.

  5. После компоновки проекта поместите приложение в Cloud Foundry. Для этого в командной строке перейдите в целевую папку и выполните команду vmc push. Команда vmc push будет задавать вопросы. Ответьте на них так, как показано в листинге 31.

    Листинг 31. Размещение приложения в Cloud Foundry при помощи vmc
    shekhar@shekhar:~/dev/writing/dw/shortnotes/target$ vmc push 
    Would you like to deploy from the current directory? [Yn]: Y 
    Application Name: shortnotes 
    Application Deployed URL [shortnotes.cloudfoundry.com]: 
    Detected a Java SpringSource Spring Application, is this correct? [Yn]: Y 
    Memory Reservation (64M, 128M, 256M, 512M, 1G) [512M]: 
    Creating Application: OK 
    Would you like to bind any services to 'shortnotes'? [yN]: y 
    Would you like to use an existing provisioned service? [yN]: N 
    The following system services are available 
    1: mongodb 
    2: mysql 
    3: postgresql 
    4: rabbitmq 
    5: redis 
    Please select one you wish to provision: 1 
    Specify the name of the service [mongodb-484f6]: 
    Creating Service: OK 
    Binding Service [mongodb-484f6]: OK 
    Uploading Application: 
      Checking for available resources: OK 
      Processing resources: OK 
      Packing application: OK 
      Uploading (85K): OK   
    Push Status: OK 
    Staging Application: OK                                                         
    Starting Application: OK
  6. Наконец, вы увидите приложение shortnotes, работающее в облаке по адресу http://shortnotes.cloudfoundry.com.

Для получения исходного кода приложения shortnotes выполните клонирование репозитория shortnotes github (см. раздел Ресурсы).


Заключение

В статье представлено хранилище MongoDB и рассмотрено создание приложения Spring MongoDB при помощи Spring Roo. Вы познакомились с основами MongoDB, выбором схемы, наиболее соответствующей требованиям вашей среды, а также узнали, как Spring Roo помогает быстро запускать Spring MongoDB-приложения.

В данной статье я не рассматривал такие темы, как MongoDB MapReduce, модификаторы массива, настройка репликации MongoDB, геолокационные запросы и т.д. Еще есть много тем для исследования. Хорошие ресурсы приведены в документации, перечисленной в разделе Ресурсы.

Ресурсы

Научиться

Получить продукты и технологии

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Технология Java, Linux
ArticleID=861179
ArticleTitle=Введение в Spring Roo: Часть 7. Разработка приложений Spring MongoDB при помощи Spring Roo
publish-date=03122013