Berkeley DB STL接口

哈Ha 不久前,对于我的一个项目,我需要一个嵌入式数据库来存储键值元素,提供事务支持以及加密数据(可选)。 经过短暂搜索,我遇到了一个Berkeley DB项目。 除了我需要的功能之外,该数据库还提供了一个与STL兼容的接口,使您可以像使用常规(几乎普通)的STL容器一样使用该数据库。 实际上,下面将讨论此接口。


伯克利分贝


Berkeley DB是一个嵌入式,可扩展,高性能的开源数据库。 它可免费用于开放源代码项目,但对于专有项目则有很多限制。 支持的功能:


  • 交易
  • 故障转移日志
  • AES数据加密
  • 复制
  • 指标
  • 多线程应用程序的同步工具
  • 访问策略-一位作家,很多读者
  • 快取

以及许多其他。


初始化系统后,用户可以指定要使用的子系统。 这样可以消除不必要的资源浪费,例如事务,日志,锁等操作。


可以选择存储结构和数据访问:


  • Btree-排序平衡树的实现
  • 哈希 -线性哈希实现
  • -使用按逻辑分页的堆文件进行存储。 每个条目均由页面及其内的偏移量标识。 存储的组织方式使得删除记录不需要压缩。 这使您可以在缺乏物理空间的情况下使用它。
  • 队列 -一种队列,用于存储固定长度的记录,并以逻辑数字作为键。 它设计用于在末尾快速插入,并支持一种特殊操作,该操作可在一次调用中从队列的开头删除并返回一个条目。
  • Recno-允许您使用逻辑数字作为键来保存固定长度和可变长度的记录。 通过其索引提供对元素的访问。

为了避免歧义,有必要定义几个用于描述Berkeley DB工作的概念。


数据库是键值数据存储。 一个表可以与其他DBMS中 Berkeley DB数据库类似。


数据库环境是一个或多个数据库的包装。 定义所有数据库的常规设置,例如缓存大小,文件存储路径,阻塞的使用和配置,事务,日志记录子系统。


在典型的用例中,创建并配置了一个环境 ,该环境具有一个或多个数据库


STL接口


Berkeley DB是用C编写的库 它具有诸如PerlJavaPHP等语言的活页夹C ++的接口是带有对象和继承的C代码的包装。 为了能够像使用STL容器进行操作一样访问数据库,有一个STL接口作为C ++的附加组件。 在图形形式中,界面层如下所示:



因此, STL接口类似于std::mapstd::vector容器,使您可以通过键(对于BtreeHash )或通过索引(对于Recno )从数据库中检索元素,并通过标准std::find_if在数据库中查找元素,通过foreach遍历整个数据库。 Berkeley DB STL接口的所有类和功能都在dbstl命名空间中,简而言之, dbstl也表示STL接口。


安装方式


该数据库支持大多数Linux 平台WindowsAndroidApple iOS等。


对于Ubuntu 18.04,只需安装以下软件包:


  • libdb5.3-stl-dev
  • libdb5.3 ++-开发

要从Linux源码构建,您需要安装autoconflibtool 。 最新的源代码可以在这里找到。


例如,我下载的归档文件的版本 18.1.32-db-18.1.32.zip。 您需要解压缩存档并转到源文件夹:


 unzip db-18.1.32.zip cd db-18.1.32 

接下来,我们移至build_unix目录并运行组装和安装:


 cd build_unix ../dist/configure --enable-stl --prefix=/home/user/libraries/berkeley-db make make install 

添加到cmake项目


BerkeleyDBSamples项目用于说明Berkeley DB的示例。


该项目的结构如下:


 +-- CMakeLists.txt +-- sample-usage | +-- CMakeLists.txt | +-- sample-map-usage.cpp | +-- submodules | +-- cmake | | +-- FindBerkeleyDB 

CMakeLists.txt描述了项目的常规参数。 样本源文件在sample-usage中sample-usage / CMakeLists.txt搜索库,定义示例的汇编。


在示例中,使用FindBerkeleyDB将库连接到cmake项目。 它作为git子模块添加到submodules / cmake中 。 在组装期间,您可能需要指定BerkeleyDB_ROOT_DIR 。 例如,对于从源安装的上述库,必须指定标志cmake -DBerkeleyDB_ROOT_DIR=/home/user/libraries/berkeley-db


在根文件CMakeLists.txt中 ,将FindBerkeleyDB模块的路径添加到CMAKE_MODULE_PATH


 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/submodules/cmake/FindBerkeleyDB") 

之后, sample-usage / CMakeLists.txt以标准方式执行库搜索:


 find_package(BerkeleyDB REQUIRED) 

接下来,添加可执行文件并将其链接到Oracle :: BerkeleyDB库:


 add_executable(sample-map-usage "sample-map-usage.cpp") target_link_libraries(sample-map-usage PRIVATE Oracle::BerkeleyDB ${CMAKE_THREAD_LIBS_INIT} stdc++fs) 

实际例子


为了演示dbstl的用法,让我们检查来自sample-map-usage.cpp文件的一个简单示例。 此应用程序演示了dbstl::db_map在单线程程序中使用dbstl::db_map 。 容器本身类似于std::map并将数据存储为键/值对。 基础数据库结构可以是BtreeHash 。 与std::map不同,对于dbstl::db_map<std::string, TestElement>实际值类型是dbstl::ElementRef<TestElement> 。 例如,对于dbstl::db_map<std::string, TestElement>::operator[]返回此类型。 它定义了在数据库中存储TestElement类型的对象的方法。 一种这样的方法是operator=


在示例中,使用数据库的方法如下:


  • 应用程序调用Berkeley DB方法来访问数据
  • 这些方法访问缓存以进行读取或写入
  • 如有必要,直接访问数据文件

该过程以图形方式显示在图中:



为了降低示例的复杂性,它不使用异常处理。 发生错误时,某些dbstl容器方法可能会引发异常。


代码解析


要使用Berkeley DB,您需要连接两个头文件:


 #include <db_cxx.h> #include <dbstl_map.h> 

第一个添加了C ++接口原语,第二个定义了用于数据库,关联容器以及许多实用程序方法的类和函数。 STL接口位于dbstl命名空间中。


对于存储,使用Btree结构, std::string充当键,值是用户结构TestElement


 struct TestElement{ std::string id; std::string name; }; 

main函数中,通过调用dbstl::dbstl_startup()初始化库。 它必须位于第一次使用STL接口原语之前。


之后,我们在由ENV_FOLDER变量设置的目录中初始化并打开数据库环境


 auto penv = dbstl::open_env(ENV_FOLDER, 0u, DB_INIT_MPOOL | DB_CREATE); 

DB_INIT_MPOOL标志负责初始化缓存子系统DB_CREATE创建环境所需的所有文件。 该团队还将这个对象注册到资源管理器中。 他负责关闭所有已注册的对象(数据库对象,游标,事务等也已在其中注册)并清除动态内存。 如果您已经有一个数据库环境对象,并且只需要在资源管理器中注册它,则可以使用dbstl::register_db_env函数。


对数据库执行类似的操作:


 auto db = dbstl::open_db(penv, "sample-map-usage.db", DB_BTREE, DB_CREATE, 0u); 

磁盘上的数据将被写入sample-map-usage.db文件 ,该文件将在ENV_FOLDER目录中不存在时(由于DB_CREATE标志)而ENV_FOLDER 。 一棵树用于存储( DB_BTREE参数)。


Berkeley DB中,键和值存储为字节数组。 要使用自定义类型(在我们的示例中为TestElement ),必须为以下功能定义函数:


  • 接收用于存储对象的字节数;
  • 将对象封送为字节数组;
  • 解组。

在示例中,此功能由TestMarshaller类的静态方法执行。 它在内存中TestElement对象,如下所示:


  • id字段的长度被复制到缓冲区的开头
  • 下一个字节放置id字段的内容
  • 之后,复制name字段的大小
  • 然后将内容本身放置在name字段中


我们描述了TestMarshaller的功能:


  • TestMarshaller::restore TestElement用缓冲区中的数据填充TestElement对象
  • TestMarshaller::size返回保存指定对象所需的缓冲区的大小。
  • TestMarshaller::store将对象保存在缓冲区中。

要注册封送/ dbstl::DbstlElemTraits功能,请使用dbstl::DbstlElemTraits


 dbstl::DbstlElemTraits<TestElement>::instance()->set_size_function(&TestMarshaller::size); dbstl::DbstlElemTraits<TestElement>::instance()->set_copy_function(&TestMarshaller::store); dbstl::DbstlElemTraits<TestElement>::instance()->set_restore_function( &TestMarshaller::restore ); 

初始化容器:


 dbstl::db_map<std::string, TestElement> elementsMap(db, penv); 

这就是将元素从std::map复制到创建的容器的样子:


 std::copy( std::cbegin(inputValues), std::cend(inputValues), std::inserter(elementsMap, elementsMap.begin()) ); 

但是通过这种方式,您可以将数据库的内容打印到标准输出中:


 std::transform( elementsMap.begin(dbstl::ReadModifyWriteOption::no_read_modify_write(), true), elementsMap.end(), std::ostream_iterator<std::string>(std::cout, "\n"), [](const auto data) -> std::string { return data.first + "=> { id: " + data.second.id + ", name: " + data.second.name + "}"; }); 

在上面的示例中调用begin方法看起来有点不寻常: elementsMap.begin(dbstl::ReadModifyWriteOption::no_read_modify_write(), true)
此设计用于获取只读迭代器。 dbstl没有定义cbegin方法;相反,使用了begin方法中的readonly参数(第二个)。 您还可以使用对容器的常量引用来获取只读的迭代器。 这样的迭代器仅允许读取操作;在写入时,它将引发异常。


为什么在上面的代码中使用只读迭代器? 首先,它只是通过迭代器执行读取操作。 其次,文档说与常规版本相比,它具有更好的性能。


添加新的键/值对,或者,如果键已经存在,则更新值就像在std::map一样简单:


 elementsMap["added key 1"] = {"added id 1", "added name 1"}; 

如上所述, elementsMap["added key 1"]指令返回一个包装类,其operator=重新定义,其后续调用直接将对象保存在数据库中。


如果需要将项目插入容器中:


 auto [iter, res] = elementsMap.insert( std::make_pair(std::string("added key 2"), TestElement{"added id 2", "added name 2"}) ); 

调用elementsMap.insert返回std::pair<, > 。 如果无法插入对象,则成功标志将为false 。 否则, 成功标志包含true ,并且迭代器指向插入的对象。


通过键查找值的另一种方法是使用dbstl::db_map::find方法,类似于std::map::find


 auto findIter = elementsMap.find("test key 1"); 

通过获得的迭代器,可以将键findIter->first访问到TestElement元素的字段findIter->second.idfindIter->second.name 。 要提取 / 对,请使用解引用运算符auto iterPair = *findIter;


当将取消引用运算符( * )或对类成员的访问( -> )应用于迭代器时,将访问数据库并从中提取数据。 而且,即使先前提取的数据被修改,也将被擦除。 这意味着在下面的示例中,对迭代器所做的更改将被放弃,并且存储在数据库中的值将显示在控制台上。


 findIter->second.id = "skipped id"; findIter->second.name = "skipped name"; std::cout << "Found elem for key " << "test key 1" << ": id: " << findIter->second.id << ", name: " << findIter->second.name << std::endl; 

为了避免这种情况,您需要通过调用findIter->second从迭代器中获取存储对象的包装并将其保存在变量中。 接下来,对该包装器进行所有更改,然后通过调用包装器方法_DB_STL_StoreElement将结果写入数据库:


 auto ref = findIter->second; ref.id = "new test id 1"; ref.name = "new test name 1"; ref._DB_STL_StoreElement(); 

更新数据甚至更加容易-只需使用findIter->second条指令获取包装器,然后为其分配所需的TestElement对象,如示例中所示:


 if(auto findIter = elementsMap.find("test key 2"); findIter != elementsMap.end()){ findIter->second = {"new test id 2", "new test name 2"}; } 

在终止程序之前,必须调用dbstl::dbstl_exit(); 关闭并删除资源管理器中所有注册的对象。


总结


本文 dbstl::db_map在一个简单的单线程程序中提供了dbstl容器主要功能的简要概述。 这只是一个小介绍,没有涉及诸如事务性,锁定,资源管理,异常处理和多线程执行等功能。


我的目的不是详细描述方法及其参数,因此最好参考C ++接口STL接口上的相应文档。

Source: https://habr.com/ru/post/zh-CN459862/


All Articles