JetBrains Xodus 簡介
1. 概述
Xodus是 JetBrains 建構的開源嵌入式資料庫。我們可以用它作為關聯式資料庫的替代品。使用 Xodus,我們獲得了高效能事務鍵值儲存和物件導向的資料模型。該儲存具有僅追加機制,可最大限度地減少隨機 IO 開銷,並預設提供快照隔離。
在 Xodus 中,我們有快照隔離,保證事務中所有讀取的整個資料庫的快照一致。對於每個提交的事務,我們將擁有資料庫的新快照(版本),使後續事務能夠引用該快照。
在本教程中,我們將介紹 Xodus 資料庫概念的大部分功能。
2. Xodus 中資料處理的工作原理
Xodus中的資料處理流程如下圖所示:
在這裡,我們有一個Environment
類別來處理日誌檔案和記憶體儲存之間的所有同步。 EntityStore
類別充當環境的包裝器,簡化了資料操作的過程。
3. 環境
環境是最低等級的 Xodus API。我們可以將其用作事務性鍵值存儲。讓我們建立一個範例儲存庫,我們將在其中使用環境。
3.1.依賴關係
我們將開始新增[xodus-openAPI](https://mvnrepository.com/artifact/org.jetbrains.xodus/xodus-openAPI)
依賴項:
<dependency>
<groupId>org.jetbrains.xodus</groupId>
<artifactId>xodus-openAPI</artifactId>
<version>2.0.1</version>
</dependency>
3.2. save()
現在,讓我們建立一個具有保存邏輯的儲存庫類別:
public class TaskEnvironmentRepository {
private static final String DB_PATH = "db\\.myAppData";
private static final String TASK_STORE = "TaskStore";
public void save(String taskId, String taskDescription) {
try (Environment environment = openEnvironmentExclusively()) {
Transaction writeTransaction = environment.beginExclusiveTransaction();
try {
Store taskStore = environment.openStore(TASK_STORE,
StoreConfig.WITHOUT_DUPLICATES, writeTransaction);
ArrayByteIterable id = StringBinding.stringToEntry(taskId);
ArrayByteIterable value = StringBinding.stringToEntry(taskDescription);
taskStore.put(writeTransaction, id, value);
} catch (Exception e) {
writeTransaction.abort();
} finally {
if (!writeTransaction.isFinished()) {
writeTransaction.commit();
}
}
}
}
private Environment openEnvironmentExclusively() {
return Environments.newInstance(DB_PATH);
}
}
在這裡,我們指定了資料庫檔案目錄的路徑 - 所有檔案都將自動建立。然後,我們打開環境並建立獨佔轉換 - 所有事務都應在資料處理後提交或中止。
之後,我們創建了儲存空間來操作資料。使用商店,我們將 ID 和值放入資料庫中。將所有資料轉換為ArrayByteIterable
非常重要。
3.3. findOne()
現在,讓我們將findOne()
方法加入到我們的儲存庫中:
public String findOne(String taskId) {
try (Environment environment = openEnvironmentExclusively()) {
Transaction readonlyTransaction = environment.beginReadonlyTransaction();
try {
Store taskStore = environment.openStore(TASK_STORE,
StoreConfig.WITHOUT_DUPLICATES, readonlyTransaction);
ArrayByteIterable id = StringBinding.stringToEntry(taskId);
ByteIterable result = taskStore.get(readonlyTransaction, id);
return result == null ? null : StringBinding.entryToString(result);
} finally {
readonlyTransaction.abort();
}
}
}
在這裡,我們同樣地建立環境實例。由於我們正在實作讀取操作,因此我們將在此處使用唯讀事務。我們呼叫store實例的get()
方法透過ID取得任務描述。讀取操作後,我們沒有任何可提交的內容,因此我們將中止事務。
3.4. findAll()
要迭代存儲,我們需要使用[Cursors](https://github.com/JetBrains/xodus/wiki/Environments#cursors) .
讓我們使用遊標來實作findAll()
方法:
public Map<String, String> findAll() {
try (Environment environment = openEnvironmentExclusively()) {
Transaction readonlyTransaction = environment.beginReadonlyTransaction();
try {
Store taskStore = environment.openStore(TASK_STORE,
StoreConfig.WITHOUT_DUPLICATES, readonlyTransaction);
Map<String, String> result = new HashMap<>();
try (Cursor cursor = taskStore.openCursor(readonlyTransaction)) {
while (cursor.getNext()) {
result.put(StringBinding.entryToString(cursor.getKey()),
StringBinding.entryToString(cursor.getValue()));
}
}
return result;
} finally {
readonlyTransaction.abort();
}
}
}
在唯讀事務中,我們打開儲存並建立遊標,沒有任何條件。 然後,迭代存儲,我們用 ID 和任務描述的所有組合填充地圖。處理後關閉遊標很重要。
3.5. deleteAll()
現在,我們將deleteAll()
方法加入到我們的儲存庫中:
public void deleteAll() {
try (Environment environment = openEnvironmentExclusively()) {
Transaction exclusiveTransaction = environment.beginExclusiveTransaction();
try {
Store taskStore = environment.openStore(TASK_STORE,
StoreConfig.WITHOUT_DUPLICATES, exclusiveTransaction);
try (Cursor cursor = taskStore.openCursor(exclusiveTransaction)) {
while (cursor.getNext()) {
taskStore.delete(exclusiveTransaction, cursor.getKey());
}
}
} finally {
exclusiveTransaction.commit();
}
}
}
在此實作中,我們採用與遊標相同的方法來迭代所有專案。對於每個項目鍵,我們呼叫store's delete()
方法。最後,我們提交所有更改。
4.實體商店
在實體儲存層中,我們將資料作為具有屬性和連結的實體進行存取。我們使用實體儲存 API,它提供了更豐富的資料查詢選項和更高層級的抽象。
4.1.依賴關係
要開始使用實體存儲,我們需要新增以下xodus-entity-store
相依性:
<dependency>
<groupId>org.jetbrains.xodus</groupId>
<artifactId>xodus-entity-store</artifactId>
<version>2.0.1</version>
</dependency>
4.2. save()
現在,讓我們加入對保存邏輯的支援。首先,我們將建立模型類別:
public class TaskEntity {
private final String description;
private final String labels;
public TaskEntity(String description, String labels) {
this.description = description;
this.labels = labels;
}
// getters
}
我們創建了一個具有一些屬性的TaskEntity
。現在,我們將建立一個儲存庫類,其中包含保存它的邏輯:
public class TaskEntityStoreRepository {
private static final String DB_PATH = "db\\.myAppData";
private static final String ENTITY_TYPE = "Task";
public EntityId save(TaskEntity taskEntity) {
try (PersistentEntityStore entityStore = openStore()) {
AtomicReference<EntityId> idHolder = new AtomicReference<>();
entityStore.executeInTransaction(txn -> {
final Entity message = txn.newEntity(ENTITY_TYPE);
message.setProperty("description", taskEntity.getDescription());
message.setProperty("labels", taskEntity.getLabels());
idHolder.set(message.getId());
});
return idHolder.get();
}
}
private PersistentEntityStore openStore() {
return PersistentEntityStores.newInstance(DB_PATH);
}
}
在這裡,我們開啟了一個PersistentEntityStore
的實例,然後啟動了一個獨佔事務並建立了一個jetbrains.exodus.entitystore.Entity
的實例,將TaskEntity
中的所有屬性對應到它。 EntityStore
實體僅存在於交易中,因此我們需要將其對應到 DTO 中才能在儲存庫外部使用它。最後,我們從 save 方法回傳 EntityId。此 EntityId 包含實體類型和唯一產生的 ID。
4.3. findOne()
現在,讓我們將findOne()
方法加入TaskEntityStoreRepository
:
public TaskEntity findOne(EntityId taskId) {
try (PersistentEntityStore entityStore = openStore()) {
AtomicReference<TaskEntity> taskEntity = new AtomicReference<>();
entityStore.executeInReadonlyTransaction(
txn -> taskEntity.set(mapToTaskEntity(txn.getEntity(taskId))));
return taskEntity.get();
}
}
在這裡,我們訪問只讀事務中的實體並將其映射到我們的TaskEntity
。在映射方法中,我們實作了以下邏輯:
private TaskEntity mapToTaskEntity(Entity entity) {
return new TaskEntity(entity.getProperty("description").toString(),
entity.getProperty("labels").toString());
}
我們已經獲得了實體屬性並使用它們建立了TaskEntity
實例。
4.4. findAll()
讓我們加入findAll()
方法:
public List<TaskEntity> findAll() {
try (PersistentEntityStore entityStore = openStore()) {
List<TaskEntity> result = new ArrayList<>();
entityStore.executeInReadonlyTransaction(txn -> txn.getAll(ENTITY_TYPE)
.forEach(entity -> result.add(mapToTaskEntity(entity))));
return result;
}
}
正如我們所看到的,環境模擬中的實作要短得多。 我們呼叫了實體儲存事務getAll()
方法,並將結果中的每個項目對應到TaskEntity
。
4.5. deleteAll()
現在,讓我們將*deleteAll()*方法加入TaskEntityStoreRepository
:
public void deleteAll() {
try (PersistentEntityStore entityStore = openStore()) {
entityStore.clear();
}
}
在這裡,我們必須呼叫PersistentEntityStore
的clear()
方法,所有項目都將被刪除。
5. 結論
在本文中,我們探索了 JetBrains Xodus。我們檢查了該資料庫的主要 API 並演示瞭如何使用它來執行基本操作。該資料庫可以成為嵌入式資料庫集的寶貴補充。
與往常一樣,程式碼可以在 GitHub 上取得。