«1. Введение

В этой статье мы рассмотрим библиотеку MapDB — встроенный движок базы данных, доступ к которому осуществляется через API, похожий на коллекцию.

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

Наконец, мы рассмотрим некоторые режимы работы в памяти, прежде чем сравнивать MapDB с традиционными базами данных и коллекциями Java.

2. Хранение данных в MapDB

Во-первых, давайте представим два класса, которые мы будем постоянно использовать в этом руководстве, — DB и DBMaker. Класс DB представляет открытую базу данных. Его методы вызывают действия для создания и закрытия коллекций хранилища для обработки записей базы данных, а также обработки транзакционных событий.

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

2.1. Простой пример HashMap

Чтобы понять, как это работает, давайте создадим новую базу данных в памяти.

Во-первых, давайте создадим новую базу данных в памяти, используя класс DBMaker:

DB db = DBMaker.memoryDB().make();

После того, как наш объект БД будет запущен и запущен, мы можем использовать его для создания HTreeMap для работы с записями нашей базы данных: ~~ ~

String welcomeMessageKey = "Welcome Message";
String welcomeMessageString = "Hello Baeldung!";

HTreeMap myMap = db.hashMap("myMap").createOrOpen();
myMap.put(welcomeMessageKey, welcomeMessageString);

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

String welcomeMessageFromDB = (String) myMap.get(welcomeMessageKey);
assertEquals(welcomeMessageString, welcomeMessageFromDB);

Наконец, теперь, когда мы закончили с базой данных, мы должны закрыть ее, чтобы избежать дальнейших изменений:

db.close();

Чтобы хранить наши данные в файле, а не в памяти, все, что нам нужно сделать, это изменить способ создания экземпляра нашего объекта БД:

DB db = DBMaker.fileDB("file.db").make();

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

2.2. Коллекции

MapDB включает различные типы коллекций. Чтобы продемонстрировать, давайте добавим и получим некоторые данные из нашей базы данных с помощью NavigableSet, который работает так, как вы могли бы ожидать от набора Java:

Давайте начнем с простого создания экземпляра нашего объекта БД:

DB db = DBMaker.memoryDB().make();

Далее, давайте создайте наш NavigableSet:

NavigableSet<String> set = db
  .treeSet("mySet")
  .serializer(Serializer.STRING)
  .createOrOpen();

Здесь сериализатор обеспечивает сериализацию и десериализацию входных данных из нашей базы данных с использованием объектов String.

Далее добавим некоторые данные:

set.add("Baeldung");
set.add("is awesome");

Теперь давайте проверим, правильно ли были добавлены два наших различных значения в базу данных:

assertEquals(2, set.size());

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

set.add("Baeldung");

assertEquals(2, set.size());

2.3. Транзакции

Подобно традиционным базам данных, класс DB предоставляет методы для фиксации и отката данных, которые мы добавляем в нашу базу данных.

Чтобы включить эту функцию, нам нужно инициализировать нашу БД с помощью метода transactionEnable:

DB db = DBMaker.memoryDB().transactionEnable().make();

Далее, давайте создадим простой набор, добавим некоторые данные и зафиксируем их в базе данных:

NavigableSet<String> set = db
  .treeSet("mySet")
  .serializer(Serializer.STRING)
  .createOrOpen();

set.add("One");
set.add("Two");

db.commit();

assertEquals(2, set.size());

Теперь давайте добавим в нашу базу данных третью незафиксированную строку:

set.add("Three");

assertEquals(3, set.size());

Если нас не устраивают наши данные, мы можем откатить данные, используя метод отката БД:

db.rollback();

assertEquals(2, set.size());

2.4. Сериализаторы

MapDB предлагает большое количество сериализаторов, которые обрабатывают данные в коллекции. Наиболее важным параметром конструкции является имя, которое идентифицирует отдельную коллекцию в объекте БД:

HTreeMap<String, Long> map = db.hashMap("indentification_name")
  .keySerializer(Serializer.STRING)
  .valueSerializer(Serializer.LONG)
  .create();

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

3. HTreeMap

HTreeMap MapDB предоставляет коллекции HashMap и HashSet для работы с нашей базой данных. HTreeMap представляет собой сегментированное хэш-дерево и не использует хэш-таблицу фиксированного размера. Вместо этого он использует автоматически расширяющееся дерево индексов и не пересчитывает все свои данные по мере роста таблицы. В довершение всего, HTreeMap является потокобезопасным и поддерживает параллельную запись с использованием нескольких сегментов.

Для начала давайте создадим простой HashMap, который использует String как для ключей, так и для значений:

DB db = DBMaker.memoryDB().make();

HTreeMap<String, String> hTreeMap = db
  .hashMap("myTreeMap")
  .keySerializer(Serializer.STRING)
  .valueSerializer(Serializer.STRING)
  .create();

«

hTreeMap.put("key1", "value1");
hTreeMap.put("key2", "value2");

assertEquals(2, hTreeMap.size());

«Выше мы определили отдельные сериализаторы для ключа и значения. Теперь, когда наш HashMap создан, давайте добавим данные с помощью метода put:

hTreeMap.put("key1", "value3");

assertEquals(2, hTreeMap.size());
assertEquals("value3", hTreeMap.get("key1"));

Поскольку HashMap работает с методом hashCode объекта, добавление данных с использованием того же ключа приводит к перезаписи значения:

4 SortedTableMap

SortedTableMap MapDB хранит ключи в таблице фиксированного размера и использует двоичный поиск для поиска. Стоит отметить, что после подготовки карта доступна только для чтения.

String VOLUME_LOCATION = "sortedTableMapVol.db";

Volume vol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, false);

SortedTableMap.Sink<Integer, String> sink =
  SortedTableMap.create(
    vol,
    Serializer.INTEGER,
    Serializer.STRING)
    .createFromSink();

Давайте рассмотрим процесс создания и запроса SortedTableMap. Мы начнем с создания тома с отображением памяти для хранения данных, а также приемника для добавления данных. При первом вызове нашего тома мы установим для флага только для чтения значение false, гарантируя, что сможем записывать в том:

for(int i = 0; i < 100; i++){
  sink.put(i, "Value " + Integer.toString(i));
}

sink.create();

Затем мы добавим наши данные и вызовем метод create на приемнике. для создания нашей карты:

Volume openVol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, true);

SortedTableMap<Integer, String> sortedTableMap = SortedTableMap
  .open(
    openVol,
    Serializer.INTEGER,
    Serializer.STRING);

assertEquals(100, sortedTableMap.size());

Теперь, когда наша карта существует, мы можем определить объем только для чтения и открыть нашу карту, используя метод open SortedTableMap:

4.1. Двоичный поиск

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

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

  1. Keys for each page are stored on-heap in an array. The SortedTableMap performs a binary search to find the correct page.
  2. Next, decompression occurs for each key in the node. A binary search establishes the correct node, according to the keys.
  3. Finally, the SortedTableMap searches over the keys within the node to find the correct value.

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

5. Режим в памяти

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

5.1. On-Heap

В режиме on-heap объекты хранятся в простой карте коллекции Java. Он не использует сериализацию и может быть очень быстрым для небольших наборов данных.

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

DB db = DBMaker.heapDB().make();

Давайте посмотрим на пример, определяющий режим в куче:

5.2. Byte[]

Второй тип хранилища основан на байтовых массивах. В этом режиме данные сериализуются и сохраняются в массивы размером до 1 МБ. Хотя технически этот метод находится в куче, он более эффективен для сборки мусора.

DB db = DBMaker.memoryDB().make();

Это рекомендуется по умолчанию и использовалось в нашем примере «Hello Baeldung»:

5.3. DirectByteBuffer

Последнее хранилище основано на DirectByteBuffer. Прямая память, представленная в Java 1.4, позволяет передавать данные непосредственно в собственную память, а не в кучу Java. В результате данные будут храниться полностью вне кучи.

DB db = DBMaker.memoryDirectDB().make();

Мы можем вызвать хранилище этого типа с помощью:

6. Почему MapDB?

Итак, зачем использовать MapDB?

6.1. MapDB и традиционная база данных

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

Помимо этого, MapDB позволяет нам получить доступ к сложности базы данных с привычным отношением к коллекции Java. С MapDB нам не нужен SQL, и мы можем получить доступ к записям с помощью простых вызовов метода get.

6.2. MapDB против простых коллекций Java

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

7. Заключение

В этой статье мы подробно рассмотрели встроенный движок базы данных MapDB и структуру коллекций.

Мы начали с рассмотрения основных классов DB и DBMaker для настройки, открытия и управления нашей базой данных. Затем мы рассмотрели несколько примеров структур данных, которые MapDB предлагает для работы с нашими записями. Наконец, мы рассмотрели преимущества MapDB по сравнению с традиционной базой данных или коллекцией Java.