Leitfaden zu MapDB

1. Einleitung

In diesem Artikel sehen wir uns die MapDB-Bibliothek an - eine eingebettete Datenbank-Engine, auf die über eine sammlungsähnliche API zugegriffen wird.

Wir beginnen mit der Untersuchung der Kernklassen DB und DBMaker , die beim Konfigurieren, Öffnen und Verwalten unserer Datenbanken helfen. Anschließend werden einige Beispiele für MapDB-Datenstrukturen vorgestellt, in denen Daten gespeichert und abgerufen werden.

Schließlich werden wir uns einige der In-Memory-Modi ansehen, bevor wir MapDB mit herkömmlichen Datenbanken und Java-Sammlungen vergleichen.

2. Speichern von Daten in MapDB

Lassen Sie uns zunächst die beiden Klassen vorstellen, die wir in diesem Lernprogramm ständig verwenden werden - DB und DBMaker. Die DB- Klasse repräsentiert eine offene Datenbank. Seine Methoden rufen Aktionen zum Erstellen und Schließen von Speichersammlungen auf, um Datenbankeinträge sowie Transaktionsereignisse zu verarbeiten.

DBMaker übernimmt die Konfiguration, Erstellung und Öffnung der Datenbank. Im Rahmen der Konfiguration können wir unsere Datenbank entweder im Arbeitsspeicher oder in unserem Dateisystem hosten.

2.1. Ein einfaches HashMap- Beispiel

Um zu verstehen, wie dies funktioniert, instanziieren wir eine neue Datenbank im Speicher.

Erstellen wir zunächst eine neue In-Memory-Datenbank mit der DBMaker- Klasse:

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

Sobald unser DB- Objekt betriebsbereit ist, können wir damit eine HTreeMap erstellen , die mit unseren Datenbankeinträgen arbeitet:

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

HTreeMap ist die HashMap- Implementierung von MapDB . Nachdem wir nun Daten in unserer Datenbank haben, können wir sie mit der get- Methode abrufen :

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

Nachdem wir mit der Datenbank fertig sind, sollten wir sie schließen, um weitere Mutationen zu vermeiden:

db.close();

Um unsere Daten in einer Datei und nicht im Speicher zu speichern, müssen wir lediglich die Art und Weise ändern, in der unser DB- Objekt instanziiert wird:

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

In unserem obigen Beispiel werden keine Typparameter verwendet. Infolgedessen bleiben wir beim Casting unserer Ergebnisse, um mit bestimmten Typen zu arbeiten. In unserem nächsten Beispiel werden Serializer eingeführt , um das Casting zu vermeiden.

2.2. Sammlungen

MapDB enthält verschiedene Sammlungstypen. Fügen Sie zur Demonstration einige Daten mit einem NavigableSet hinzu und rufen Sie sie aus unserer Datenbank ab. Dies funktioniert wie von einem Java- Set erwartet :

Beginnen wir mit einer einfachen Instanziierung unseres DB- Objekts:

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

Als nächstes erstellen wir unser NavigableSet :

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

Hier stellt der Serializer sicher, dass die Eingabedaten aus unserer Datenbank mithilfe von String- Objekten serialisiert und deserialisiert werden .

Als nächstes fügen wir einige Daten hinzu:

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

Lassen Sie uns nun überprüfen, ob unsere beiden unterschiedlichen Werte korrekt zur Datenbank hinzugefügt wurden:

assertEquals(2, set.size());

Da dies eine Menge ist, fügen wir eine doppelte Zeichenfolge hinzu und stellen sicher, dass unsere Datenbank nur noch zwei Werte enthält:

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

2.3. Transaktionen

Ähnlich wie bei herkömmlichen Datenbanken bietet die DB- Klasse Methoden zum Festschreiben und Zurücksetzen der Daten, die wir unserer Datenbank hinzufügen.

Um diese Funktionalität zu aktivieren, müssen wir unsere Datenbank mit der transactionEnable- Methode initialisieren :

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

Als Nächstes erstellen wir einen einfachen Satz, fügen einige Daten hinzu und übertragen sie in die Datenbank:

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

Fügen wir unserer Datenbank nun eine dritte, nicht festgeschriebene Zeichenfolge hinzu:

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

Wenn wir mit unseren Daten nicht zufrieden sind, können wir die Daten mithilfe der Rollback- Methode von DB zurücksetzen :

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

2.4. Serializer

MapDB bietet eine Vielzahl von Serialisierern, die die Daten in der Sammlung verarbeiten. Der wichtigste Konstruktionsparameter ist der Name, der die einzelne Sammlung innerhalb des DB- Objekts identifiziert :

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

Die Serialisierung wird zwar empfohlen, ist jedoch optional und kann übersprungen werden. Es ist jedoch anzumerken, dass dies zu einem langsameren generischen Serialisierungsprozess führt.

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 Simple Java Collections

Java Collections behält die Daten unserer Anwendung nicht mehr bei, sobald sie nicht mehr ausgeführt wird. MapDB bietet einen einfachen, flexiblen und steckbaren Dienst, mit dem wir die Daten in unserer Anwendung schnell und einfach beibehalten und gleichzeitig den Nutzen von Java-Sammlungstypen beibehalten können.

7. Fazit

In diesem Artikel haben wir uns eingehend mit der eingebetteten Datenbank-Engine und dem Sammlungsframework von MapDB befasst.

Wir haben uns zunächst die Kernklassen DB und DBMaker angesehen , um unsere Datenbank zu konfigurieren, zu öffnen und zu verwalten. Anschließend gingen wir einige Beispiele für Datenstrukturen durch, die MapDB für die Arbeit mit unseren Datensätzen anbietet. Schließlich haben wir uns die Vorteile von MapDB gegenüber einer herkömmlichen Datenbank oder Java Collection angesehen.

Wie immer ist der Beispielcode auf GitHub verfügbar.