Что такое Map<D,B>

Вот так они рекламируют себя на сайте:

MapDB provides Java Maps, Sets, Lists, Queues and other collections backed by off-heap or on-disk storage. It is a hybrid between java collection framework and embedded database engine.

Что можно перевести при мерно как:

MapDB представляет возможность работы с Java Maps, Sets, Lists, Queues и другими коллекциями с хранением их вне кучи или на диске. Это что-то среднее между java collection framework и embedded database engine.

В данном случае посмотрим как можно использовать Map<D,B> для хранения данных на диске, чтобы не заморачиваться с разворачиванием отдельной СУБД.

Пример использования

Рассмотрим небольшой пример Spring boot приложения, в котором будем использовать Map<D,B> в качестве основного хранилища данных. Предположим что у нас уже есть готовы проект со базовым набором Spring boot зависимостей, а в качестве системы сборки используется Maven.

Зависимости

Для начала добавим зависимость Map<D,B> в наш проект с актуальной на момент написания поста версией.

<dependency>
    <groupId>org.mapdb</groupId>
    <artifactId>mapdb</artifactId>
    <version>3.1.0</version>
</dependency>

Определим Bean-ы

Начнем с класса конфигурации в котором определим bean нашей БД.

import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MapDBConfig {
    @Bean
    public DB mapDBContext(@Value("${app.mapdb.path-to-file}") String filename) {
        return DBMaker.fileDB(filename)
                      .fileMmapEnableIfSupported()
                      .closeOnJvmShutdown()
                      .transactionEnable()
                      .make();
    }
}

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

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

import org.mapdb.DB;
import org.mapdb.Serializer;
import org.springframework.stereotype.Repository;

@Repository
public class MapDbRepository {
    private final DB db;
    private final DB.HashMapMaker<String, String> keyValueStore;

    public MapDbRepository(DB db) {
        // инжектим наш бин с БД в поле класса, он нам ещё пригодиться
        this.db = db;
        // инициализируем наше хранилище ключ-значение
        this.keyValueStore = db.hashMap("keyValueStore") // указываем название
                // указываем сериализатор для ключей и значений нашего хранилища
                .keySerializer(Serializer.STRING).valueSerializer(Serializer.STRING);
    }
}

Теперь добавим обработчик события удаления бина из контекста с помощью аннотации @PreDestroy, чтобы корректно завершить доступ к файлам и исзбежать возможной потери данных

    @PreDestroy
    void close() {
        db.close(); // закрываем БД чтобы не потерять данные
    }

Дальше можем добавить метод сохранения значения в наш репозиторий.

    public void save(String key, String value) {
        // открываем наш ресурс для записи или создаем новый, если мы еще ничего не сохраняли
        HTreeMap<String, String> map = keyValueStore.createOrOpen();
        // добавляем наше значение
        map.put(key, value);
        // пишем данные на диск
        db.commit();
    }

Тут IDE или статические анализаторы кода могут ругаться что мы не закрываем ресурс HTreeMap<String, String> map, но мы это делаем сознательно. В противном случае при повторном обращении к ресурсу мы получим ошибку что он не доступен.

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

    public String findValue(String key) {
        HTreeMap<String, String> map = keyValueStore.createOrOpen();
        return map.get(key);
    }

На этом наш простенький репозиторий готов и мы можем его использовать как Spring bean внутри нашего приложения.