JetBrains Xodus 简介

在本文中,我们探索了 JetBrains Xodus。我们研究了此数据库的主要 API,并演示了如何使用它执行基本操作。此数据库可以成为嵌入式数据库集的宝贵补充。

Xodus是 JetBrains 构建的开源嵌入式数据库。我们可以将其用作关系数据库的替代品。使用 Xodus,我们可以获得高性能事务键值存储和面向对象的数据模型。此存储具有仅附加机制,可最大限度地减少随机 IO 开销,并默认提供快照隔离。

在 Xodus 中,我们拥有快照隔离,可保证事务内所有读取操作的整个数据库快照一致。对于每个已提交的事务,我们将获得数据库的新快照(版本),使后续事务可以引用此快照。

在本教程中,我们将介绍 Xodus 数据库概念的大部分功能。

我们将开始添加xodus-openAPI依赖项:

<dependency>
    <groupId>org.jetbrains.xodus</groupId>
    <artifactId>xodus-openAPI</artifactId>
    <version>2.0.1</version>
</dependency>

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);
    }
}

在这里,我们指定了数据库文件目录的路径——所有文件都将自动创建。然后,我们打开环境并创建独占转换——所有事务都应在数据处理后提交或中止。

之后,我们创建了 store 来操作数据。使用 store,我们将 ID 和值放入数据库。将所有数据转换为ArrayByteIterable很重要。

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();
        }
    }
}

在这里,我们同样创建环境实例。由于我们正在实现读取操作,因此我们将在这里使用只读事务。我们调用商店实例的get()方法通过 ID 获取任务描述。读取操作后,我们没有任何内容要提交,因此我们将中止事务。 

indAll()
要迭代存储,我们需要使用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 和任务描述的组合填充地图。处理后关闭游标很重要。

删除全部()
现在,我们将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();
        }
    }
}

在此实现中,我们采用相同的方法使用游标来迭代所有项目。对于每个项目键,我们调用 存储的 delete()方法。最后,我们提交所有更改。

实体存储层
在实体存储层中,我们将数据作为具有属性和链接的实体进行访问。我们使用实体存储 API,它提供了更丰富的数据查询选项和更高级别的抽象。

1. 依赖项
要开始使用实体存储,我们需要添加以下xodus-entity-store依赖项:

<dependency>
    <groupId>org.jetbrains.xodus</groupId>
    <artifactId>xodus-entity-store</artifactId>
    <version>2.0.1</version>
</dependency>

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。

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. 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;
    }
}

我们可以看到,在 Environments 模拟中,实现过程要短得多。我们调用了实体存储事务getAll()方法,并将结果中的每个项目映射到TaskEntity中。

5.删除全部()
现在,让我们将deleteAll()方法添加到我们的TaskEntityStoreRepository中:

public void deleteAll() {
    try (PersistentEntityStore entityStore = openStore()) {
        entityStore.clear();
    }
}

在这里,我们必须调用PersistentEntityStore的clear()方法,所有项目将被删除。