7 мая 2010 г.

Java и MongoDB вместе

Сегодня стало очень модным использование нереляционных баз данных. Вот и мне захотелось такое подергать. Мой выбор пал на MongoDB просто потому что слышал о этой штуке много положительных отзывов. Для себя сделал эту заметку, что бы потом не пришлось все изучать по второму разу, если ещё кому-нибудь пригодится, то и замечательно.
Всё, что здесь написано, основывается на документации с официального сайта MongoDB и конкретно на разделе посвященному Java, а кое-что и вовсе является просто переводом. Некоторые примеры из документации я немного переделал для более ясной, на мой субъективный взгляд, демонстрации возможностей.

Установка и запуск

1. Для начала идем сюда и качаем подходящий дистрибутив.

2. Распаковываем архив с MongoDB в какой-нибудь каталог:

$ tar xzf mongodb-linux-i686-1.4.2.tgz

3. Создаем каталог, где будут жить базы данных (по умолчанию MongoDB требует, что бы он был именно такой):

$ mkdir -p /data/db

4. Идем в каталог, в который распаковали MongoDB и запускаем сервер, допустим, в фоне:

$ ./mongod &

Справка по параметрам запуска сервера mongod может быть получена так:

$ ./mongod --help

Подробнее о запуске и остановке можно почитать тут.

5. Для проверки запускаем клиент MongoDB и вводим примерно следующее:

$ ./mongo
> db.foo.save( { a : 1 } )
> db.foo.find()

Для получения справки по командам клиента можно ввести «help» в интерактивной оболочке клиента mongo или зайти сюда.

6. Т.к. нам необходимо работать с MongoDB из java-программы, то качаем java-драйвер подходящей версии отсюда.

7. После этого устанавливаем драйвер в систему либо копированием в [каталог JDK]/jre/lib/ext либо подключаем как User Libraries в своей IDE. Я предпочитаю второй вариант.

Все, теперь можно приступать к программированию.

Подключение к базе данных

Для начала импортируем в программу необходимые классы:
import com.mongodb.*;
Подключение к серверу MongoDB можно осуществить разными способами. Сначала необходимо создать объект класса Mongo и определить адрес сервера, к которому следует подключаться. Вот несколько примеров создания объекта Mongo для подключения к серверу, который крутится на локальной машине на порту по умолчанию:
// подключение с параметрами по умолчанию
// т.е. к порту 27017 локальной машины
Mongo mongo1 = new Mongo();

// подключение к порту 27017 указанной машины
Mongo mongo2 = new Mongo( "localhost" );

// подключение на указанный порт указанной машины
Mongo mongo3 = new Mongo( "localhost" , 27017 );
После этого можно выполнять подключение к базе данных:
DB db = mongo1.getDB( "anydb" );
А вообще, есть возможность сразу подключиться к БД без явного создания объекта Mongo, используя статический метод:
DB db= Mongo.connect(new DBAddress( "127.0.0.1:27017", "anydb" ));
Если в момент подключения окажется, что такой базы данных не существует, то она будет создана автоматически.

Коллекции и Документы

Базы данных в MongoDB состоят из коллекций, наверное, можно рассматривать их как аналог таблиц в реляционных БД. В свою очередь коллекции состоят из документов (объектов), при этом в состав одного документа могут входить другие документы. Документы состоят из набора пар ключ/значение, я буду назвать такие пары полями. К документам в коллекции можно относиться как к записям в таблицах, только документы в одной коллекции могут отличаться по своей структуре друг от друга, в отличие от записей в таблице реляционной БД.

Создание или получение коллекции

Создание коллекции или получение ссылки на уже существующую коллекцию делается одинаково:
DBCollection coll = db.getCollection("myCollection"); 
Получение списка всех коллекций в БД

Просмотреть имена всех коллекций в базе данных можно следующим образом:
Set<string> collNames = db.getCollectionNames();

for (String name : collNames) {
    System.out.println(name);
}
Удаление коллекции

С помощью метода drop() можно удалить коллекцию. Этим же методом можно пользоваться для очищения коллекции от всех документов.
DBCollection coll = db.getCollection("collection");
coll.drop();
// ниже в коде можно осуществлять 
// добавление новых документов в пустую коллекцию
Создание документа и добавление его в коллекцию

Для создания документа можно воспользоваться классом BasicDBObject. В примере ниже создается объект (документ) «человек», который обладает полями «имя», «профессия», «возраст» и «параметры». Поле «параметры» является вложенным документом и состоит из полей «рост» и «вес».
// создание документа human
BasicDBObject human = new BasicDBObject();
human.put("name", "Alexander");
human.put("profession", "engineer");
human.put("age", 25);

// создание документа parameters
BasicDBObject parameters = new BasicDBObject();
parameters.put("height", 178);
parameters.put("weight", 80);

// добавление документа parameters в документ human
human.put("parameters", parameters);

// добавление документа human в коллекцию
coll.insert(human);
Просмотр всех документов в коллекции

Для просмотра документов в коллекции можно использовать метод find() объекта класса DBCollection. Этот метод возвращает объект типа DBCursor, с помощью которого можно пробежаться по всем документам коллекции.
DBCursor cur = coll.find();

while(cur.hasNext()) {
    System.out.println(cur.next());
}
Получение списка документов удовлетворяющих условиям

Тот же метод find() можно использовать и для получения списка каких-либо конкретных документам, которые удовлетворяют определенным условиям. Для осуществления этого необходимо создать нечто вроде документа-шаблона, с которым будут сравниваться документы в коллекции.
// создание документа-шаблона
BasicDBObject query = new BasicDBObject();

// условие, что поле profession должно иметь значение engineer
query.put("profession", "engineer");
// условие, что значение поля age должно быть < 25 
query.put("age", new BasicDBObject("$lt", 25));
  
// получение объекта DBCursor на основе запроса 
DBCursor cur = coll.find(query);  

while(cur.hasNext()) 
{  
    System.out.println(cur.next()); 
}  
В данном примере из коллекции будут выбраны только те документы, у которых есть поле profession, имеющее значение engineer, и поле age, значение которого меньше 25.
Возможные операторы сравнения (в скобках указаны аналогичные операторы в Java): $gt (>), $lt (<), $gte (>=), $lte (<=), $ne (!=).

Получение значения поля документа

Используя метод get(String key) объекта BasicDBObject можно получить значение поля:
DBCursor cur = coll.find();

while(cur.hasNext()) {
    System.out.println(cur.next().get("name"));
}

Количество документов в коллекции


Количество документов в коллекции можно узнать так:
DBCollection coll = db.getCollection("collection");
System.out.println(coll.getCount());

Создание индексов

MongoDB поддерживает индексы и их очень легко создавать в коллекции. Для создания индекса необходимо указать поле, которое должно быть проиндексировано и установить какой тип индекса нужно использовать возрастающий (1) или убывающий (-1).
DBCollection coll = db.getCollection("people"); 
coll.createIndex(new BasicDBObject("profession", 1));  
Посмотреть список всех индексов, которые есть в коллекции можно следующим образом:
List<dbobject> list = coll.getIndexInfo();

for (DBObject obj : list) {
    System.out.println(obj);
}
Результат будет примерно таким:

{ "name" : "_id_" , "ns" : "db1.people" , "key" : { "_id" : 1}}
{ "name" : "profession_1" , "ns" : "db1.people" , "key" : { "profession" : 1}}

Дополнительно

Получение одного документа в коллекции

С помощью метода findOne() можно получить первый встретившийся документ в коллекции. Так же можно воспользоваться findOne(DBObject query) для получения одного документа удовлетворяющего условиям.
DBObject document = coll.findOne();
System.out.println(document);
Создание документов с несколькими полями одной строкой

Для того, что бы создавать документы необязательно всегда вызывать метод put для добавления полей. Можно создать нужный документ одной строкой, используя метод append() класса BasicDBObject, который добавляет пару ключ/значение.
for (int i=0; i < 5; i++) {
    coll.insert(new BasicDBObject("i",i).append("i^2", i*i).append("i^3", i*i*i));
}
Содержание коллекции после выполнения этого кода будет примерно таким:

{ "_id" : "4be349fc60dc030d811cc4e1" , "i" : 0 , "i^2" : 0 , "i^3" : 0}
{ "_id" : "4be349fc60dc030d821cc4e1" , "i" : 1 , "i^2" : 1 , "i^3" : 1}
{ "_id" : "4be349fc60dc030d831cc4e1" , "i" : 2 , "i^2" : 4 , "i^3" : 8}
{ "_id" : "4be349fc60dc030d841cc4e1" , "i" : 3 , "i^2" : 9 , "i^3" : 27}
{ "_id" : "4be349fc60dc030d851cc4e1" , "i" : 4 , "i^2" : 16 , "i^3" : 64}

Этот же метод применим при создании условий запроса, например:
BasicDBObject  query = new BasicDBObject();
query.put("i", new BasicDBObject("$gt", 20).append("$lte", 30));  // условие что 20 < i <= 30
cur = coll.find(query);
while(cur.hasNext()) {
    System.out.println(cur.next());
}
Аутентификация (необязательна)

Сервер MongoDB может быть запущен в безопасном режиме (с аргументом --auth), тогда доступ к базам данных будет контроллироваться по имени и паролю. Когда MongoDB запущен в этом режиме, то любое клиентское приложение должно предоставить имя пользователя и пароль перед тем как выполнять какие-либо действия с базой данных. Делается это, например, так:
DB db = mongo.getDB("mydb"); 
boolean auth = db.authenticate("user", "secret".toCharArray());
Для добавления пользователя базы данных можно использовать метод addUser(String username, char[] passwd)
DB db = mongo.getDB("mydb");
db.addUser("user", "secret".toCharArray());
Получение списка имен баз данных

Список имен доступных на сервере баз данных можно получить следующим образом:
Mongo mongo = new Mongo();

for (String dbname : mongo.getDatabaseNames()) {
    System.out.println(dbname);
}
Удаление базы данных

Для удаления базы данных нужно использовать метод dropDatabase(String dbName) экземпляра класса Mongo.
Mongo mongo = new Mongo();
mongo.dropDatabase("db");

Пример программы
import java.net.UnknownHostException;
import java.util.List;
import com.mongodb.*;

public class TestMongoDB {

 public static void main(String[] args) {

  Mongo mongo = null;
  
  try { 
   // создаем объект Mongo
   mongo = new Mongo();
   System.out.println("Адрес для подключения: "+mongo.getConnectPoint());
  
  } catch (UnknownHostException e) {
   e.printStackTrace();
  } catch (MongoException e) {
   e.printStackTrace();
  }

  // подключаемся к БД
  DB db = mongo.getDB("mydb"); 
  System.out.println("Имя базы данных: "+db.getName()+"\n");
  
  // создание коллекции
  DBCollection coll = db.getCollection("people");
  
  // удаляем все документы из коллекции, если они есть
  coll.drop();
  
  // добавление документов в коллекцию
  coll.insert(makeHuman("Alexander","engineer",25,178,80));
  coll.insert(makeHuman("Andrey","engineer",23,184,76));
  coll.insert(makeHuman("Vasily","doctor",30,176,83));
  coll.insert(makeHuman("Anna","jurist",32,172,69));
  
  // Выводим все документы
  System.out.println("Список документов:");
  
  DBCursor cur = coll.find();
  
  while(cur.hasNext()) {
   System.out.println(cur.next());
  }
  System.out.println();
  
  // создаем документ-шаблон для выборки и
  // выбираем только инженеров младше 25 лет
  System.out.println("Список инженеров младше 25 лет:");
  
  BasicDBObject query = new BasicDBObject();

  query.put("profession", "engineer");
  query.put("age", new BasicDBObject("$lt", 25));
         
  cur = coll.find(query);
          
  while(cur.hasNext()) {
   System.out.println(cur.next());
  }
          
  System.out.println();
          
  // создаем запрос на удаление всех инженеров
  query = new BasicDBObject();
  query.put("profession", "engineer");
  coll.remove(query);
  
  System.out.println("Список всех документов после удаления инженеров:");
  cur = coll.find();
  while(cur.hasNext()) {
   System.out.println(cur.next());
  }
  System.out.println();
  
  // создание индексов для поля profession
  coll.createIndex(new BasicDBObject("profession", 1));
         
  System.out.println("Индексы:");
  List<dbobject> list = coll.getIndexInfo();
  for (DBObject o : list) {
   System.out.println(o);
  }
 }

 /**
  * Метод создает документ "человек"
  */
 private static BasicDBObject makeHuman(String name, String profession,
   int age, int height, int weight) {

  // создание документа human
  BasicDBObject human = new BasicDBObject();
  human.put("name", name);
  human.put("profession", profession);
  human.put("age", age);

  // создание документа parameters
  BasicDBObject parameters = new BasicDBObject();
  parameters.put("height", height);
  parameters.put("weight", height);

  // добавление документа parameters в документ human
  human.put("parameters", parameters);

  return human;
 }
}

Результат выполнения программы:


Адрес для подключения: 127.0.0.1:27017
Имя базы данных: mydb

Список документов:
{ "_id" : "4be3fc1e9e6d9bac60ff6d16" , "name" : "Alexander" , "profession" : "engineer" , "age" : 25 , "parameters" : { "height" : 178 , "weight" : 178}}
{ "_id" : "4be3fc1e9e6d9bac61ff6d16" , "name" : "Andrey" , "profession" : "engineer" , "age" : 23 , "parameters" : { "height" : 184 , "weight" : 184}}
{ "_id" : "4be3fc1e9e6d9bac62ff6d16" , "name" : "Vasily" , "profession" : "doctor" , "age" : 30 , "parameters" : { "height" : 176 , "weight" : 176}}
{ "_id" : "4be3fc1e9e6d9bac63ff6d16" , "name" : "Anna" , "profession" : "jurist" , "age" : 32 , "parameters" : { "height" : 172 , "weight" : 172}}

Список инженеров младше 25 лет:
{ "_id" : "4be3fc1e9e6d9bac61ff6d16" , "name" : "Andrey" , "profession" : "engineer" , "age" : 23 , "parameters" : { "height" : 184 , "weight" : 184}}

Список всех документов после удаления инженеров:
{ "_id" : "4be3fc1e9e6d9bac62ff6d16" , "name" : "Vasily" , "profession" : "doctor" , "age" : 30 , "parameters" : { "height" : 176 , "weight" : 176}}
{ "_id" : "4be3fc1e9e6d9bac63ff6d16" , "name" : "Anna" , "profession" : "jurist" , "age" : 32 , "parameters" : { "height" : 172 , "weight" : 172}}

Индексы:
{ "name" : "_id_" , "ns" : "mydb.people" , "key" : { "_id" : 1}}
{ "name" : "profession_1" , "ns" : "mydb.people" , "key" : { "profession" : 1}}

10 комментариев:

  1. Как бы привязать выборку документов к графической таблице SWING?

    ОтветитьУдалить
  2. Во-первых, я не большой специалист в Swing'е.

    Во-вторых, подозреваю, что вариантов множество и выбор варианта зависит от конкретной задачи.

    В-третьих, нужно понимать, что документо-ориентированная БД состоит не из таблиц, а из коллекций, которые могут содержать совершенно разнородные документы, и, следовательно, если вам нужно использовать коллекции как таблицы, то нужно налагать на них определённые ограничения, а если вам нужны таблицы, то зачем вам mongodb? Тут надо бы определиться.

    В-четвертых, если вы всё же уверены, что вам нужна именно mongodb, то сначала можно посмотреть, как обычно используется JTable для отображения содержимого реляционных БД (например здесь: http://javaswing.wordpress.com/2010/05/05/jtabletablemodel/) и подумать как можно это переделать для вашей задачи.

    ОтветитьУдалить
  3. Скажем так- стоит задача использовать Монго.Да и надоело если честно стандартное решение- SQL .
    Думаю,для JTable все равно коллекция там или нет.Кстати она хорошо понимает коллекции.

    Так что я и ответил на свой вопрос.Теперь дело за реализацией.

    ОтветитьУдалить
  4. > стоит задача использовать Монго

    Это и есть главная задача? :) По-моему СУБД это средство для решения задачи, а сами задачи звучат немного иначе. И СУБД надо выбирать исходя из задачи - иногда лучше взять такую вещь как mongodb, а иногда и реляционную СУБД.

    > надоело если честно стандартное решение- SQL

    надоело или нет, но оно есть и в некоторых случаях без него никуда

    > Думаю,для JTable все равно коллекция там или нет

    Конечно ей все равно, потому что она вообще не знает с чем она там работает - вы ей подсовываете объекты и всё зависит от того, как вы это реализовали. Именно об этом я и говорил. Как вы одновременно будете выводить в JTable совершенно разнородные документы? Например, если один документ выглядит так:

    {"name" : "Andrey" , "profession" : "engineer" , "age" : 23 }

    а другой так:

    {"name" : "Alexander" , "student" : "Moscow University" ,"age" : 19 , "interests" : { "sport" : "snowboard" , "music" : "guitar"}}

    т.е. в обоих случаях разное количество пар ключ/знчение и есть разные ключи, а во втором документе еще к тому же поле "interests" в качестве значения хранит вообще вложенный документ. Это совершенно нормальная ситуация для документо-ориентированной БД. Как в таком случае должна выглядеть таблица? Что использовать в качестве заголовков и данных в таблице? Или такой ситуации не должно быть? Об этом и стоит подумать.

    > Кстати она хорошо понимает коллекции.

    Эту фразу вообще не понял. Вы имеете ввиду коллекции в терминах mongodb или коллекции всмысле Java Collections Framework? Я в этой статье и комментариях к ней когда говорил слово "коллекция" имел ввиду коллекции mongodb, которые совершенно не связаны с понятием коллекций в Java, надеюсь это понятно.

    > Так что я и ответил на свой вопрос.Теперь дело за реализацией.

    Я рад, если это так. Удачи :)

    ОтветитьУдалить
  5. Да.придется подумать над этим..

    ОтветитьУдалить
  6. Тогда через что выводить эту коллекцию?Если не через jTable?

    ОтветитьУдалить
  7. Не мне здесь вам давать советы по конкретному выбору. Я лишь предупредил о том, что необходимо понимать существенные различия между mongodb и реляционными БД. Если вы первый раз взяли в руки mongodb и попробуете пользоваться ей, как привычной реляционной БД, то скорее всего через некоторое время вы сами поймете, что усложнили себе жизнь. Хотя опять же это зависит от задачи и соответственно структуры БД. Просто голову под mongodb надо все же немного перестроить.

    Повторяю, выбор зависит от вашей конкретной задачи, о которой мне ничего не известно. Можно и в JTable выводить, если все предусмотреть. А может лучше взять SQL СУБД. Решать только вам.

    А если вы просто тестируете mongodb, тогда вообще советы не уместны. Просто берите и пробуйте её по-всякому и через некоторое время сами возможно придете к верному варианту использования.

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

    ОтветитьУдалить
  9. Вопрос: в какую сторону копать, если нужно из Java-кода передавать запросы напрямую mongodb, получать ответ и выдавать юзеру?

    ОтветитьУдалить
  10. Вопрос мне не понятен. Что значит "передавать запросы напрямую mongodb"? В данной статье не напрямую что ли? Или под "непрямостью" вы подразумеваете использование java-драйвера? Попробуйте задать вопрос по-другому и более развернуто.

    ОтветитьУдалить