MapDB 是一个快速、易用的嵌入式Java数据库引擎. 最主要的特点之一就是支持磁盘存储,直接把内存中的Hash Map同步写入到磁盘. 另外特别惊喜的是它支持ACID事务,MVCC隔离, 且有全职的开发者支持.
看完官方的文档与示例后,基本上可以确定它符合业务场景的使用要求.另外发现官方正在重构3.x的版本, 但应该不会这么快发布吧.用google搜索了下关于MapDB的使用案例, 也不是很多. 可能是本来官方的文档就齐全有关吧,API也不复杂,跟着官方的文档走一遍就可以上手了.
动手测试了简单的示例后, 突然冒出一个疑问, 如何实现同时操作磁盘上的一个数据库, 以及同一个HashMap呢? 这里需要明白的, MapDB存储到磁盘上的数据库文件,并非只是存放了一个HashMap, 这有点类似数据库里可以有多张表的概念相同. 那么数据库是可以支持多连接的, MapDB是否也同样支持呢?(理想确实很丰满,但现实太骨感了!)
初步检验的结果是, MapDB并不支持同时访问磁盘上的同一文件. 那么也就是只能创建一个长连接, 直到业务功能处理完成再关闭它. 幸运的是它支持对已经存在或是运行中的同一个HashMap进行读写操作. 下面来看看简单的示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
import org.mapdb.BTreeMap;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import java.io.File;
import java.util.Map;
import java.util.Random;
import java.util.SortedMap;
import static org.testng.Assert.*;
/**
* MapDB 测试
*
* @author 凡梦星尘(elkan1788@gmail.com)
* @since 2016.1.19
*/
public class MapDBTest {
private DB diskDB;
Map<Integer, String> data;
@BeforeTest
public void init() {
// 文件名字可以自己定义
File dbFile = new File("D:/mapdb.data");
// DB有且只打开一次连接
diskDB = DBMaker.fileDB(dbFile)
// 很是好奇,关闭锁定,还是不支持多事务访问同一个数据库文件
.fileLockDisable()
// 最好开启,在程度异常或JVM关闭可正常关闭数据库
// 有过一次无法访问未关闭数据库文件的异常
.closeOnJvmShutdown()
// 如果不需要回滚的可以关闭,提高读写效率
.transactionDisable()
// 这里测试所没不保留磁盘文件
.deleteFilesAfterClose()
// 这里没有找到读取的API,或者就是不支持多连接的吧
.make();
}
@AfterTest
public void destroy() {
assertTrue(!data.isEmpty());
// 本应该是99才对,但会合并内存中其它数据
Map<Integer, String> temp = diskDB.treeMap("sort_mapdb");
assertEquals(temp.size(), 100);
// 这里需要注意下,有可能make成功的数据库也是关闭的
// 如果不做检查的话,可能抛出:IllegalAccessError("DB was already closed")
if (diskDB.isClosed()) {
diskDB.isClosed();
}
}
@Test(invocationCount = 10)
public void testSyncWrite() throws Exception {
// 支持多种类型Map,如B+ tree, sort等等
// 但value貌似支持引用类型, 不支持Object, 可能是
// 跟序列化到磁盘存储有关
data = diskDB.treeMapCreate("nice_mapdb")
// 开启快速计数器
.counterEnable()
// 这步很关键,如果不带get,那么就只是make,无法支持多连接
.makeOrGet();
int len = 99;
int ran = new Random().nextInt(100)+1;
while (--len >= 0) {
data.put(len * ran, "value-"+len * ran);
}
assertFalse(data.isEmpty());
}
@Test(invocationCount = 10)
public void testReadAndDel() throws Exception {
data = diskDB.treeMapCreate("nice_mapdb")
.counterEnable()
.makeOrGet();
if (!data.isEmpty()) {
for (Integer key : data.keySet()) {
if (key % 2 == 0 || key % 5 == 0) {
data.remove(key);
}
}
assertTrue(data.size() > 0);
}
}
@Test
public void testOtherMap() throws Exception {
SortedMap<Integer, String> data = diskDB.treeMapCreate("sort_mapdb")
.counterEnable()
.makeOrGet();
int len = 99;
while (--len >= 0) {
data.put(len, "sorted-"+len);
}
assertNotNull(data);
// 创建另一个map
BTreeMap<Integer, String> btree = diskDB.treeMapCreate("sort_mapdb2")
.counterEnable()
.makeOrGet();
// 很是奇怪, 为何这里的name没有效果, 会自动合并到同时一时内存所有treeMap中
SortedMap<Integer, String> tree = diskDB.treeMap("sort_mapdb1");
tree.put(100, "sorted-100");
btree.put(100, "sorted-101");
assertEquals(tree.get(100), "sorted-100");
assertEquals(data.get(100), "sorted-100");
}
}
|
在这里没有详细的探讨关于MapDB是如何实现与磁盘持久化同步, 直接使用官方默认的值, 当然你也可以自己配置读写同步的心跳时间间隔. 在测试的过程观察发现, MapDB在创建磁盘的数据库文件时, 初始化大小写为2MB, 然后在同步内存数据时, 会先产生出一个临时文件, 当这个临时文件达到一定大小时就会合并到主体数据库文件. 至于其它的功能和代码中的疑问, 有待继续观察, 欢迎共同交流.
参考资料: