Guida a MapDB

1. Introduzione

In questo articolo, esamineremo la libreria MapDB, un motore di database incorporato a cui si accede tramite un'API simile a una raccolta.

Iniziamo esplorando le classi principali DB e DBMaker che aiutano a configurare, aprire e gestire i nostri database. Quindi, ci immergeremo in alcuni esempi di strutture dati MapDB che archiviano e recuperano dati.

Infine, esamineremo alcune delle modalità in memoria prima di confrontare MapDB con i database tradizionali e le raccolte Java.

2. Memorizzazione dei dati in MapDB

Innanzitutto, introduciamo le due classi che utilizzeremo costantemente durante questo tutorial: DB e DBMaker. La classe DB rappresenta un database aperto. I suoi metodi richiamano azioni per la creazione e la chiusura delle raccolte di archiviazione per gestire i record del database, nonché per la gestione degli eventi transazionali.

DBMaker gestisce la configurazione, la creazione e l'apertura del database. Come parte della configurazione, possiamo scegliere di ospitare il nostro database in memoria o sul nostro file system.

2.1. Un semplice esempio di HashMap

Per capire come funziona, istanziamo un nuovo database in memoria.

Innanzitutto, creiamo un nuovo database in memoria utilizzando la classe DBMaker :

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

Una volta che il nostro oggetto DB è attivo e funzionante, possiamo usarlo per creare una HTreeMap per lavorare con i nostri record di database:

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

HTreeMap è l' implementazione HashMap di MapDB . Quindi, ora che abbiamo i dati nel nostro database, possiamo recuperarli utilizzando il metodo get :

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

Infine, ora che abbiamo finito con il database, dovremmo chiuderlo per evitare ulteriori mutazioni:

db.close();

Per memorizzare i nostri dati in un file, piuttosto che in memoria, tutto ciò che dobbiamo fare è cambiare il modo in cui viene istanziato il nostro oggetto DB :

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

Il nostro esempio sopra non utilizza parametri di tipo. Di conseguenza, siamo bloccati con il casting dei nostri risultati per lavorare con tipi specifici. Nel nostro prossimo esempio, introdurremo i serializzatori per eliminare la necessità di eseguire il casting.

2.2. Collezioni

MapDB include diversi tipi di raccolta. Per dimostrare, aggiungiamo e recuperiamo alcuni dati dal nostro database utilizzando un NavigableSet , che funziona come ci si potrebbe aspettare da un Java Set :

Cominciamo con una semplice istanziazione del nostro oggetto DB :

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

Quindi, creiamo il nostro NavigableSet :

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

Qui, il serializzatore garantisce che i dati di input dal nostro database siano serializzati e deserializzati utilizzando oggetti String .

Successivamente, aggiungiamo alcuni dati:

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

Ora, controlliamo che i nostri due valori distinti siano stati aggiunti correttamente al database:

assertEquals(2, set.size());

Infine, poiché questo è un set, aggiungiamo una stringa duplicata e verifichiamo che il nostro database contenga ancora solo due valori:

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

2.3. Transazioni

Proprio come i database tradizionali, la classe DB fornisce metodi per eseguire il commit e il rollback dei dati che aggiungiamo al nostro database.

Per abilitare questa funzionalità, dobbiamo inizializzare il nostro DB con il metodo transactionEnable :

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

Successivamente, creiamo un set semplice, aggiungiamo alcuni dati e inseriteli nel database:

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

Ora aggiungiamo una terza stringa non salvata al nostro database:

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

Se non siamo soddisfatti dei nostri dati, possiamo eseguire il rollback dei dati utilizzando il metodo di rollback di DB :

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

2.4. Serializzatori

MapDB offre un'ampia varietà di serializzatori, che gestiscono i dati all'interno della raccolta. Il parametro di costruzione più importante è il nome, che identifica la singola collezione all'interno dell'oggetto DB :

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

Sebbene sia consigliata la serializzazione, è facoltativa e può essere ignorata. Tuttavia, vale la pena notare che questo porterà a un processo di serializzazione generico più lento.

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

Le raccolte Java non manterranno i dati della nostra applicazione una volta interrotta l'esecuzione. MapDB offre un servizio semplice, flessibile e collegabile che ci consente di mantenere rapidamente e facilmente i dati nella nostra applicazione mantenendo l'utilità dei tipi di raccolta Java.

7. Conclusione

In questo articolo, abbiamo approfondito il motore di database incorporato e il framework di raccolta di MapDB.

Abbiamo iniziato esaminando le classi principali DB e DBMaker per configurare, aprire e gestire il nostro database. Quindi, abbiamo esaminato alcuni esempi di strutture dati che MapDB offre per lavorare con i nostri record. Infine, abbiamo esaminato i vantaggi di MapDB rispetto a un database tradizionale o Java Collection.

Come sempre, il codice di esempio è disponibile su GitHub.