Berkeley DB(以下简称Bdb)是一个嵌入式的键值数据库。Bdb目前有两个版本,一个是使用c++构建的版本,还有一个java版本。c++版本支持在众多的语言中使用,Berkeley DB Java Edition(以下简称JE)完全用java语言编写。JE执行在应用程序中,完全不需要Client/Server的通信。JE更容易部署和嵌入到java程序中,所以我选择了使用Berkeley DB Java Edition(sleep cat)。
Berkeley DB编程接口
在Java程序中使用JE,首先需要初始化一个数据库环境。
File home = new File(envHome);
EnvironmentConfig environmentConfig = new EnvironmentConfig();
environmentConfig.setTransactional(true);
environmentConfig.setAllowCreate(true);
environmentConfig.setDurability(Durability.COMMIT_WRITE_NO_SYNC);
Environment environment = new Environment(home, environmentConfig);
home是一个存在的目录,JE将把自己所有的数据库文件存储到这个目录下。setTransactional设置数据库支持事务。setAllowCreate设置数据库不存在时,是否创建数据库。setDurability设置Bdb的写数据的方式,这个很重要,对性能和数据安全的影响和很大。Bdb数据为了性能考虑,写入的数据不会立即写回到磁盘中,必须手动同步和关闭数据库的时候才回写回数据。可以通过设置Durability修改默认行为。Durability预设值了以下几种策略
COMMIT_SYNC
事务提交时,同步数据到磁盘,并等待磁盘完成,这是最安全的策略,也是最耗时的策略。
COMMIT_WRITE_NO_SYNC
事务提交时,写数据到磁盘,不等待磁盘完成,安全和速度折中的策略。
COMMIT_NO_SYNC
COMMIT_WRITE_NO_SYNC
READ_ONLY_TXN
COMMIT_SYNC 能保证应用或者系统挂掉而不丢失数据,保证了事务的完整性。而COMMIT_WRITE_NO_SYNC 只能保证应用挂掉不丢失数据,不能保证系统挂掉时的事务完整性。
JE提供了两个层次的编程接口,高层的Direct Persistence Layer(DPL),DPL适合直接保存和读取一个Java对象的场景。DPL读取使用annotation配置的元信息保存和读取数据。
Direct Persistence Layer
待存取的用户类
@Entity
public class User {
@PrimaryKey(sequence = "SEQ_USER_ID")
private Integer userId;
@SecondaryKey(relate = MANY_TO_ONE)
private String nick;
//constructor, getter and setter
}
PrimaeyKey配置userId为主键,sequence配置了一个自增序列,@SecondaryKey配置了可查询的索引。MANY_TO_ONE表示,nick的值在数据库中可重复,多个user可使用同一个nick,按nick查询会返回一个列表。
private static EntityStore createStore(Environment envionment) throws DatabaseException {
StoreConfig storeConfig = new StoreConfig();
storeConfig.setAllowCreate(true);
storeConfig.setTransactional(true);
return new EntityStore(envionment, "store1", storeConfig);
}
创建一个EntityStore
PrimaryIndex primaryKey = entityStore.getPrimaryIndex(Integer.class, User.class);
primaryKey.put(new User("user" + i));
primaryKey.get(1);
通过EntityStore获得对应的PrimaryIndex,即可保存,读取,查询Java对象了。
Base API
同样,首先创建一个Database。
DatabaseConfig myDbConfig = new DatabaseConfig();
myDbConfig.setAllowCreate(true);
myDbConfig.setTransactional(true);
myDbConfig.setSortedDuplicates(true);
Database myDb = myEnv.openDatabase(null, // txn handle
dbName, // Database file name
myDbConfig);
使用Database保存数据时,提供的Key和Value都需要序列化为DatabaseEntry。
Bdb JE Java Collections
JE提供了一组高层的Collection API,通过java的Map,List等API封装了底层存取操作。比较适合用来构建持久化的数据结构。
构建持久化队列
public class BdbMessageQueue {
private static Logger log = Logger.getLogger(BdbMessageQueue.class);
private static final String MESSAGE_STORE = "message_store";
private Database messageDb;
private StoredSortedMap messageMap;
private TransactionRunner transactionRunner;
//EnqueueWorker和DequeueWorker的同步对象
private Object syncObject = new Object();
public BdbMessageQueue(BdbEnvironment bdbEnvironment, String queueName)
throws DatabaseException {
try {
// Set the Berkeley DB config for opening all stores.
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setTransactional(true);
dbConfig.setAllowCreate(true);
// Open the Berkeley DB database for the part, supplier and shipment
// stores. The stores are opened with no duplicate keys allowed.
messageDb = bdbEnvironment.getEnvironment().openDatabase(null, MESSAGE_STORE + queueName, dbConfig);
EntryBinding messageKeyBinding =
new SerialBinding(bdbEnvironment.getJavaCatalog(), Long.class);
EntryBinding messageValueBinding =
new SerialBinding(bdbEnvironment.getJavaCatalog(), Object.class);
messageMap = new StoredSortedMap(messageDb, messageKeyBinding, messageValueBinding, true);
// Create transactionRunner for the transactional operation
transactionRunner = new TransactionRunner(bdbEnvironment.getEnvironment());
} catch (DatabaseException dbe) {
throw new VipServiceException(VipErrorCode.DBD_ERROR, dbe);
}
}
/**
* 安全关闭berkeley 数据库
*/
public void close() {
try {
if (messageDb != null) messageDb.close();
} catch (DatabaseException dbe) {
throw new VipServiceException(VipErrorCode.DBD_ERROR, dbe);
}
}
/**
* 出队
*
* @return
*/
public void dequeue(TaskCallback task) {
try {
DequeueWorker worker = new DequeueWorker(task);
transactionRunner.run(worker);
} catch (Exception e) {
throw new VipServiceException(VipErrorCode.DBD_QUEUE_ERROR, e);
}
}
/**
* 入队
*
* @param message
*/
public void enqueue(T message) {
try {
EnqueueWorker worker = new EnqueueWorker(message);
transactionRunner.run(worker);
} catch (Exception e) {
throw new VipServiceException(VipErrorCode.DBD_QUEUE_ERROR, e);
}
}
/**
* 出队列事务Worker,内部类
*/
private class DequeueWorker implements TransactionWorker {
private TaskCallback task;
private DequeueWorker(TaskCallback task) {
this.task = task;
}
public void doWork() throws Exception {
Long firstKey;
T message;
synchronized (syncObject) {
//没有获得消息就不起床
while ((firstKey = messageMap.firstKey()) == null ||
(message = messageMap.get(firstKey)) == null) {
syncObject.wait();
}
}
//如果执行任务的时候,抛出了RuntimeException,消息不会从队列中删除。BDB事务也会回滚。
task.handelMessage(message);
messageMap.remove(firstKey);
if (log.isDebugEnabled()) {
log.debug(String.format("DequeueWorker dequeue %1$s. ", message));
}
}
}
/**
* 入队列事务Worker,内部类
*/
private class EnqueueWorker implements TransactionWorker {
private T message;
private EnqueueWorker(T message) {
this.message = message;
}
public void doWork() throws Exception {
synchronized (syncObject) {
Long lastKey = messageMap.lastKey();
lastKey = (lastKey == null) ? 1L : lastKey + 1;
messageMap.put(lastKey, message);
syncObject.notify();
}
if (log.isDebugEnabled()) {
log.debug(String.format("EnqueueWorker enqueue %1$s. ", message));
}
}
}
}
代码很长,代码中使用的JE提供的StoredMap高层次的API。
文本只是一个code show,如果你对Berkeley db感兴趣,请阅读Oracle的官方文档。
Getting Started Guide
Writing Transactional Applications
Java Collections Tutorial
Getting Started with BDB JE High Availability