BDB是使用k-v存储的,默认就是一个key对应一个value,也就是说以key为索引。但是BDB是支持二级索引的,即Secondary Database,这个在官方文档有。不过它没提到的是,BDB支持多个二级索引。
下面的代码,演示了如何创建多个二级索引,插入数据,然后通过二级索引查询数据。
/**
* @author wange
*/
import com.sleepycat.je.*;
import java.io.File;
public class SimpleSecIndex {
private static final String dataPath = "~/data/";
private static final String mainDbName = "mainDB";
public static final String secDbName1 = "secDB1";
public static final String secDbName2 = "secDB2";
public static final String UTF8 = "UTF-8";
private Database mainDb;
private SecondaryDatabase secDb1, secDb2;
private Environment dbEnv;
/**
* 写数据,最终的数据格式如下:
* 其中1,2,3为primary index,即mainDb的key值。
* 并且有两个二级索引,一个以name为key,另一个以no为key,它们指向的是一级索引的key值。
* <p/>
* data:
* {
* 1, name1, no1, birthday1;
* 2, name2, no2, birthday2;
* 3, name3, no3, birthday3;
* ...
* }
* <p/>
* primary index: 1,2,3...
* sec index1: name1-->1, name2-->2, ...
* sec index2: no1-->1, no2-->2, ...
*/
public void writeData() {
openDBs();
try {
for (int i = 0; i < 100; i++) {
String k = i + "";
DatabaseEntry key = createKeyEntry(k);
DatabaseEntry value = createValueEntry(k);
//不需要调用secDb1和secDb2的put方法,会自动创建索引
mainDb.put(null, key, value);
}
dbEnv.sync();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
closeDBs();
}
System.out.println("Write data OK");
}
/**
* 读数据,分别通过两个索引来查询
*/
public void queryData() {
openDBs();
DatabaseEntry v1 = queryEntry("name50", secDb1);
System.out.println(entryToString(v1));
v1 = queryEntry("no55", secDb2);
System.out.println(entryToString(v1));
closeDBs();
}
/**
* join查询,注意join cursor的多个索引条件之间,是与的关系而不是或,即最后join的时候必须都满足
*/
public void jointQueryData() {
openDBs();
String index1 = "name30";
String index2 = "no30"; //这时会返回一条记录,如果改成no45,则返回纪录为空
Cursor cursor1 = null, cursor2 = null;
JoinCursor joinCursor = null;
try {
cursor1 = secDb1.openCursor(null, null);
cursor2 = secDb2.openCursor(null, null);
DatabaseEntry retKey = new DatabaseEntry();
DatabaseEntry retValue = new DatabaseEntry();
DatabaseEntry key1 = createKeyEntry(index1);
DatabaseEntry key2 = createKeyEntry(index2);
OperationStatus status1 = cursor1.getSearchKey(key1, retValue, LockMode.READ_UNCOMMITTED);
OperationStatus status2 = cursor2.getSearchKey(key2, retValue, LockMode.READ_UNCOMMITTED);
if (status1 == OperationStatus.SUCCESS && status2 == OperationStatus.SUCCESS) {
joinCursor = mainDb.join(new Cursor[]{cursor1, cursor2}, null);
while (joinCursor.getNext(retKey, retValue, LockMode.READ_UNCOMMITTED) == OperationStatus.SUCCESS) {
System.out.println(entryToString(retKey) + ":" + entryToString(retValue));
}
} else {
System.out.println("[null]");
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (cursor1 != null) {
cursor1.close();
}
if (cursor2 != null) {
cursor2.close();
}
if (joinCursor != null) {
joinCursor.close();
}
closeDBs();
}
}
public void openDBs() {
File file = new File(dataPath);
dbEnv = new Environment(file, getEnvConfig());
mainDb = dbEnv.openDatabase(null, mainDbName, getMainDbConfig());
secDb1 = dbEnv.openSecondaryDatabase(null, secDbName1, mainDb, getSecDbConfig(new SecKeyCreator("name")));
secDb2 = dbEnv.openSecondaryDatabase(null, secDbName2, mainDb, getSecDbConfig(new SecKeyCreator("no")));
}
public void closeDBs() {
secDb1.close();
secDb2.close();
mainDb.close();
dbEnv.close();
}
public DatabaseEntry queryEntry(String key, Database db) {
DatabaseEntry keyEntry = createKeyEntry(key);
DatabaseEntry valueEntry = new DatabaseEntry();
OperationStatus status = db.get(null, keyEntry, valueEntry,
LockMode.READ_UNCOMMITTED);
if (status == OperationStatus.SUCCESS)
return valueEntry;
return null;
}
public DatabaseEntry createKeyEntry(String key) {
DatabaseEntry entry = null;
try {
entry = new DatabaseEntry(key.getBytes("UTF-8"));
} catch (Exception ex) {
}
return entry;
}
public DatabaseEntry createValueEntry(String key) {
String data = "name" + key + "," + "no" + key + "," + "birthday" + key;
DatabaseEntry entry = null;
try {
entry = new DatabaseEntry(data.getBytes(UTF8));
} catch (Exception ex) {
ex.printStackTrace();
}
return entry;
}
public String entryToString(DatabaseEntry entry) {
try {
String ret = new String(entry.getData(), UTF8);
return ret;
} catch (Exception ex) {
ex.printStackTrace();
}
return "[null]";
}
private EnvironmentConfig getEnvConfig() {
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
envConfig.setReadOnly(false);
envConfig.setTransactional(false);
envConfig.setLocking(false);
return envConfig;
}
private DatabaseConfig getMainDbConfig() {
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
dbConfig.setTransactional(false);
dbConfig.setReadOnly(false);
dbConfig.setDeferredWrite(false);
dbConfig.setSortedDuplicates(false);
return dbConfig;
}
private SecondaryConfig getSecDbConfig(SecondaryKeyCreator keyCreator) {
SecondaryConfig secConfig = new SecondaryConfig();
secConfig.setKeyCreator(keyCreator);
secConfig.setReadOnly(false);
secConfig.setAllowCreate(true);
//这个值为true时,通过二级索引进行查询,会直接把对应key的值取回来
secConfig.setAllowPopulate(true);
secConfig.setSortedDuplicates(true);
return secConfig;
}
/**
* 这个类告诉MainDB如何从它的key/value中创建出二级索引。在这个例子中,我们的值有三个字段name, no, birthday,
* 二级索引为name, no这两个字段。我们只要能够拼出对应记录的这两个字段的值即可。
* 这里没有使用TupleBinding。
*/
class SecKeyCreator implements SecondaryKeyCreator {
private String prefix;
public SecKeyCreator(String prefix) {
this.prefix = prefix;
}
@Override
public boolean createSecondaryKey(SecondaryDatabase secondary, DatabaseEntry key, DatabaseEntry data, DatabaseEntry result) {
try {
String k = prefix + new String(key.getData(), UTF8);
result.setData(k.getBytes(UTF8));
} catch (Exception ex) {
}
return true;
}
}
public static void main(String[] args) {
SimpleSecIndex instance = new SimpleSecIndex();
instance.writeData();
instance.queryData();
instance.jointQueryData();
}
}