在这篇文章中,我们将研究 MapDB 库 - 一个通过类似集合的 API 访问的嵌入式数据库引擎。
我们首先探讨核心类 DB 和 DBMaker,它们帮助配置、打开和管理我们的数据库。然后,我们将深入研究一些存储和检索数据的 MapDB 数据结构的例子。
最后,我们将看一下一些内存模式,然后将 MapDB 与传统数据库和 Java 集合进行比较。
后文代码演示使用的依赖包版本如下
<dependency>
<groupId>org.mapdb</groupId>
<artifactId>mapdb</artifactId>
<version>3.0.10</version>
</dependency>
首先,让我们介绍一下在这个教程中我们将经常使用的两个类 —— DB和DBMaker。DB类代表一个打开的数据库。它的方法调用用于创建和关闭存储集合以处理数据库记录,以及处理事务事件的动作。
DBMaker处理数据库配置,创建和打开。作为配置的一部分,我们可以选择将我们的数据库托管在内存中或者在我们的文件系统上。
@Test
public void testMapDb() {
DB db = DBMaker.fileDB("file.db").make();
String welcomeMessageKey = "Welcome Message";
String welcomeMessageString = "Hello Baeldung!";
HTreeMap myMap = db.hashMap("myMap").createOrOpen();
myMap.put(welcomeMessageKey, welcomeMessageString);
String welcomeMessageFromDB = (String) myMap.get(welcomeMessageKey);
assertEquals(welcomeMessageString, welcomeMessageFromDB);
db.close();
}
首先,让我们使用DBMaker类创建一个新的内存数据库:
一旦我们的DB对象启动并运行,我们就可以使用它来构建一个HTreeMap来处理我们的数据库记录:
HTreeMap是MapDB的HashMap实现。所以,现在我们的数据库中有数据了,我们可以使用get方法来获取数据。
最后,既然我们已经完成了数据库的操作,我们应该关闭它以避免对数据的修改
为了将我们的数据存储在文件中,而不是在内存中,我们需要做的就是改变我们的DB对象的实例化方式:
DB db = DBMaker.fileDB("file.db").make();
上面的例子没有使用类型参数。因此我们必须强制转换我们的结果以便与特定类型一起工作。在我们的下一个例子中,我们将引入序列化器以消除强制转换。
MapDB包括不同的集合类型。为了演示,让我们使用NavigableSet从我们的数据库中添加并获取一些数据,它的工作方式同Java Set类似
@Test
public void testCollections() {
DB db = DBMaker.memoryDB().make();
NavigableSet<String> set = db
.treeSet("mySet")
.serializer(Serializer.STRING)
.createOrOpen();
set.add("Baeldung");
set.add("is awesome");
assertEquals(2, set.size());
set.add("Baeldung");
assertEquals(2, set.size());
db.close();
}
首先序列化器确保我们数据库中的输入数据使用String对象进行序列化和反序列化。接着,我们添加一些数据。最后我们检查我们的两个不同的值是否已正确添加到数据库中:由于这是一个集合,我们添加一个重复的字符串并验证我们的数据库仍然只包含两个值。
就像传统的数据库一样,DB类提供了提交和回滚我们添加到数据库中的数据的方法。
@Test
public void testTransaction() {
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());
}
为了启用这个功能,我们需要使用transactionEnable方法初始化我们的DB
接下来,让我们创建一个简单的集合,添加一些数据,并将其提交到数据库。然后我们在数据库中添加第三个未提交的字符串。最后我们可以使用DB的rollback方法回滚数据。
MapDB提供了大量的序列化器,它们处理集合内的数据。最重要的构造参数是名称,它在DB对象中标识单个集合。
HTreeMap<String, Long> map = db.hashMap("indentification_name")
.keySerializer(Serializer.STRING)
.valueSerializer(Serializer.LONG)
.create();
虽然推荐使用序列化,但它是可选的,可以跳过。然而,值得注意的是,这将导致更慢的通用序列化过程。
MapDB的HTreeMap为我们的数据库提供了HashMap和HashSet集合。HTreeMap是一个分段的哈希树,不使用固定大小的哈希表。相反,它使用一个自动扩展的索引树,并且随着表的增长不会重新哈希所有的数据。最重要的是,HTreeMap是线程安全的,并支持使用多个段进行并行写入。
@Test
public void testHTreeMap() {
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());
hTreeMap.put("key1", "value3");
assertEquals(2, hTreeMap.size());
assertEquals("value3", hTreeMap.get("key1"));
}
首先,让我们实例化一个简单的HashMap,它对键和值都使用String。我们为键和值定义了单独的序列化器。HashMap已经创建,接着使用put方法添加数据。由于HashMap依赖于对象的hashCode方法,使用相同的键添加数据会导致值被覆盖。
MapDB的SortedTableMap在固定大小的表中存储键,并使用二分搜索进行查找。值得注意的是,一旦创建,map就是只读的。
@Test
public void testSortedTableMap() {
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();
for(int i = 0; i < 100; i++){
sink.put(i, "Value " + Integer.toString(i));
}
sink.create();
Volume openVol = MappedFileVol.FACTORY.makeVolume(VOLUME_LOCATION, true);
SortedTableMap<Integer, String> sortedTableMap = SortedTableMap
.open(
openVol,
Serializer.INTEGER,
Serializer.STRING);
assertEquals(100, sortedTableMap.size());
}
让我们来看一下创建和查询SortedTableMap的过程。我们首先创建一个map来存储数据,添加数据。在我们第一次调用时,我们将只读标志设置为false,确保我们可以写入数据。
接下来,我们将添加我们的数据并调用create方法来创建我们的map。
现在我们的map已经存在,我们可以定义一个只读存储空间并使用SortedTableMap的open方法打开我们的map。
在继续介绍后面章节之前,让我们了解SortedTableMap的工作原理。
SortedTableMap将存储分割成多个页面,每个页面包含多个由键和值组成的节点。在这些节点中,我们在Java代码中定义了键值对。
SortedTableMap执行二分查找来获取正确的值:
MapDB提供了三种类型的内存存储方式。让我们快速了解每种模式,了解其工作原理并研究其优势。
第一种是on-heap模式,它将对象存储在一个简单的Java Collection Map中。它不使用序列化,对于小型数据集来说速度非常快。
然而,由于数据存储在堆上,数据集由垃圾回收(GC)管理。GC的持续时间随着数据集的大小而增加,导致性能下降。
让我们看一个指定on-heap模式的示例:
DB db = DBMaker.heapDB().make();
二种存储类型是基于字节数组的。在这种模式下,数据被序列化并存储在大小最多为1MB的数组中。
DB db = DBMaker.memoryDB().make();
最后一种存储方式是基于DirectByteBuffer。Direct memory是在Java 1.4中引入的,它允许将数据直接传递到本机内存而不是Java堆。因此,数据将完全存储在堆外。我们可以通过以下方式调用这种类型的存储:
DB db = DBMaker.memoryDirectDB().make();
MapDB提供了大量的数据库功能,只需几行Java代码就可以配置。当我们使用MapDB时,我们可以避免经常耗时的设置各种服务和连接以使我们的程序工作。
除此之外,MapDB还允许我们以熟悉的Java集合的形式访问数据库的复杂性。使用MapDB,我们不需要SQL,我们可以通过简单的get方法调用来访问记录
一旦我们的应用程序停止执行,Java集合将不会保留我们的应用程序的数据。MapDB提供了一个简单、灵活、可插拔的服务,使我们能够快速、轻松地保持应用程序中的数据,同时保持Java集合类型的实用性。
在这篇文章中,我们深入研究了MapDB的嵌入式数据库引擎和集合框架。
我们首先研究了核心类DB和DBMaker来配置、打开和管理我们的数据库。然后,我们通过一些例子来看MapDB提供的数据结构如何处理我们的记录。最后,我们看了MapDB相比传统数据库或Java集合的优势。
mapdb官网: https://mapdb.org/