Guide de MapDB

1. Introduction

Dans cet article, nous examinerons la bibliothèque MapDB - un moteur de base de données intégré accessible via une API de type collection.

Nous commençons par explorer les classes de base DB et DBMaker qui aident à configurer, ouvrir et gérer nos bases de données. Ensuite, nous allons plonger dans quelques exemples de structures de données MapDB qui stockent et récupèrent des données.

Enfin, nous examinerons certains des modes en mémoire avant de comparer MapDB aux bases de données traditionnelles et aux collections Java.

2. Stockage des données dans MapDB

Tout d'abord, introduisons les deux classes que nous utiliserons constamment tout au long de ce didacticiel: DB et DBMaker. La classe DB représente une base de données ouverte. Ses méthodes invoquent des actions pour créer et fermer des collections de stockage pour gérer les enregistrements de base de données, ainsi que pour gérer les événements transactionnels.

DBMaker gère la configuration, la création et l'ouverture de la base de données. Dans le cadre de la configuration, nous pouvons choisir d'héberger notre base de données en mémoire ou sur notre système de fichiers.

2.1. Un exemple simple de HashMap

Pour comprendre comment cela fonctionne, instancions une nouvelle base de données en mémoire.

Commençons par créer une nouvelle base de données en mémoire à l'aide de la classe DBMaker :

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

Une fois que notre objet DB est opérationnel, nous pouvons l'utiliser pour créer un HTreeMap pour travailler avec nos enregistrements de base de données:

String welcomeMessageKey = "Welcome Message"; String welcomeMessageString = "Hello Baeldung!"; HTreeMap myMap = db.hashMap("myMap").createOrOpen(); myMap.put(welcomeMessageKey, welcomeMessageString);

HTreeMap est l' implémentation HashMap de MapDB . Donc, maintenant que nous avons des données dans notre base de données, nous pouvons les récupérer en utilisant la méthode get :

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

Enfin, maintenant que nous avons terminé avec la base de données, nous devons la fermer pour éviter toute mutation supplémentaire:

db.close();

Pour stocker nos données dans un fichier, plutôt qu'en mémoire, tout ce que nous devons faire est de changer la façon dont notre objet DB est instancié:

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

Notre exemple ci-dessus n'utilise aucun paramètre de type. En conséquence, nous sommes obligés de convertir nos résultats pour travailler avec des types spécifiques. Dans notre prochain exemple, nous présenterons les sérialiseurs pour éliminer le besoin de cast.

2.2. Les collections

MapDB comprend différents types de collections. Pour démontrer, ajoutons et récupérons des données de notre base de données à l'aide d'un NavigableSet , qui fonctionne comme vous pouvez vous attendre d'un Java Set :

Commençons par une simple instanciation de notre objet DB :

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

Ensuite, créons notre NavigableSet :

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

Ici, le sérialiseur garantit que les données d'entrée de notre base de données sont sérialisées et désérialisées à l'aide d' objets String .

Ensuite, ajoutons quelques données:

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

Maintenant, vérifions que nos deux valeurs distinctes ont été correctement ajoutées à la base de données:

assertEquals(2, set.size());

Enfin, puisqu'il s'agit d'un ensemble, ajoutons une chaîne en double et vérifions que notre base de données ne contient toujours que deux valeurs:

set.add("Baeldung"); assertEquals(2, set.size());

2.3. Transactions

Tout comme les bases de données traditionnelles, la classe DB fournit des méthodes pour valider et restaurer les données que nous ajoutons à notre base de données.

Pour activer cette fonctionnalité, nous devons initialiser notre base de données avec la méthode transactionEnable :

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

Ensuite, créons un ensemble simple, ajoutons des données et validons-les dans la base de données:

NavigableSet set = db .treeSet("mySet") .serializer(Serializer.STRING) .createOrOpen(); set.add("One"); set.add("Two"); db.commit(); assertEquals(2, set.size());

Maintenant, ajoutons une troisième chaîne non validée à notre base de données:

set.add("Three"); assertEquals(3, set.size());

Si nous ne sommes pas satisfaits de nos données, nous pouvons restaurer les données en utilisant la méthode de restauration de DB :

db.rollback(); assertEquals(2, set.size());

2.4. Sérialiseurs

MapDB propose une grande variété de sérialiseurs, qui gèrent les données au sein de la collection. Le paramètre de construction le plus important est le nom, qui identifie la collection individuelle dans l' objet DB :

HTreeMap map = db.hashMap("indentification_name") .keySerializer(Serializer.STRING) .valueSerializer(Serializer.LONG) .create();

Bien que la sérialisation soit recommandée, elle est facultative et peut être ignorée. Cependant, il convient de noter que cela entraînera un processus de sérialisation générique plus lent.

3. HTreeMap

MapDB's HTreeMap provides HashMap and HashSet collections for working with our database. HTreeMap is a segmented hash tree and does not use a fixed-size hash table. Instead, it uses an auto-expanding index tree and does not rehash all of its data as the table grows. To top it off, HTreeMap is thread-safe and supports parallel writes using multiple segments.

To begin, let's instantiate a simple HashMap that uses String for both keys and values:

DB db = DBMaker.memoryDB().make(); HTreeMap hTreeMap = db .hashMap("myTreeMap") .keySerializer(Serializer.STRING) .valueSerializer(Serializer.STRING) .create();

Above, we've defined separate serializers for the key and the value. Now that our HashMap is created, let's add data using the put method:

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

As HashMap works on an Object's hashCode method, adding data using the same key causes the value to be overwritten:

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

4. SortedTableMap

MapDB's SortedTableMap stores keys in a fixed-size table and uses binary search for retrieval. It's worth noting that once prepared, the map is read-only.

Let's walk through the process of creating and querying a SortedTableMap. We'll start by creating a memory-mapped volume to hold the data, as well as a sink to add data. On the first invocation of our volume, we'll set the read-only flag to false, ensuring we can write to the volume:

String VOLUME_LOCATION = "sortedTableMapVol.db"; Volume vol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, false); SortedTableMap.Sink sink = SortedTableMap.create( vol, Serializer.INTEGER, Serializer.STRING) .createFromSink();

Next, we'll add our data and call the create method on the sink to create our map:

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

Now that our map exists, we can define a read-only volume and open our map using SortedTableMap's open method:

Volume openVol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, true); SortedTableMap sortedTableMap = SortedTableMap .open( openVol, Serializer.INTEGER, Serializer.STRING); assertEquals(100, sortedTableMap.size());

4.1. Binary Search

Before we move on, let's understand how the SortedTableMap utilizes binary search in more detail.

SortedTableMap splits the storage into pages, with each page containing several nodes comprised of keys and values. Within these nodes are the key-value pairs that we define in our Java code.

SortedTableMap performs three binary searches to retrieve the correct value:

  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.

5. In-Memory Mode

MapDB offers three types of in-memory store. Let's take a quick look at each mode, understand how it works, and study its benefits.

5.1. On-Heap

The on-heap mode stores objects in a simple Java Collection Map. It does not employ serialization and can be very fast for small datasets.

However, since the data is stored on-heap, the dataset is managed by garbage collection (GC). The duration of GC rises with the size of the dataset, resulting in performance drops.

Let's see an example specifying the on-heap mode:

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

5.2. Byte[]

The second store type is based on byte arrays. In this mode, data is serialized and stored into arrays up to 1MB in size. While technically on-heap, this method is more efficient for garbage collection.

This is recommended by default, and was used in our ‘Hello Baeldung' example:

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

5.3. DirectByteBuffer

The final store is based on DirectByteBuffer. Direct memory, introduced in Java 1.4, allows the passing of data directly to native memory rather than Java heap. As a result, the data will be stored completely off-heap.

We can invoke a store of this type with:

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

6. Why MapDB?

So, why use MapDB?

6.1. MapDB vs Traditional Database

MapDB offers a large array of database functionality configured with just a few lines of Java code. When we employ MapDB, we can avoid the often time-consuming setup of various services and connections needed to get our program to work.

Beyond this, MapDB allows us to access the complexity of a database with the familiarity of a Java Collection. With MapDB, we do not need SQL, and we can access records with simple get method calls.

6.2. MapDB vs collections Java simples

Les collections Java ne conserveront pas les données de notre application une fois son exécution terminée. MapDB propose un service simple, flexible et enfichable qui nous permet de conserver rapidement et facilement les données dans notre application tout en conservant l'utilité des types de collecte Java.

7. Conclusion

Dans cet article, nous avons plongé en profondeur dans le moteur de base de données intégré et le cadre de collecte de MapDB.

Nous avons commencé par examiner les classes de base DB et DBMaker pour configurer, ouvrir et gérer notre base de données. Ensuite, nous avons parcouru quelques exemples de structures de données que MapDB propose pour travailler avec nos enregistrements. Enfin, nous avons examiné les avantages de MapDB par rapport à une base de données traditionnelle ou à une collection Java.

Comme toujours, l'exemple de code est disponible à l'adresse over sur GitHub.