RavenDB:简单的 NoSQL 数据库






4.76/5 (3投票s)
在本文中,我将引导您完成设置应用程序以与 RavenDB 数据库交互的步骤。我将直接介绍客户端 API,但结尾的附录将解释如何使用 CMake 脚本从其存储库中获取客户端和相关依赖项。
设置 C++ 应用程序以使用 RavenDB 数据库的快速指南
作为设计理念,Hibernating Rhinos 团队致力于让开发者的生活更轻松,我们的 C++ 客户端也不例外。
在本文中,我将引导您完成设置应用程序以与 RavenDB 数据库交互的步骤。我将直接介绍客户端 API,但结尾的 附录将解释如何使用 CMake 脚本从 其存储库中获取客户端和相关依赖项。
RavenDB 客户端 API
作为我们想要存储的数据示例,我定义了一个对象来保存“user”记录。该记录将作为 文档存储在数据库中(对于 SQL 爱好者来说,文档在某种程度上等同于表中的一行,只不过它不需要遵守模式,并且可以包含任意复杂的对象图)。
struct user { std::string id; std::string name; int age{}; std::vector<std::string> emails; };
CRUD 操作
让我们看一些代码,然后我将在下面进行详细分解。RavenDB 客户端的设计宗旨是具有相似的函数名称和用法模式,因此如果您有使用我们其他客户端的经验,您可能会觉得这个很熟悉。以下代码将我们的应用程序连接到 RavenDB 服务器,然后创建并存储我们的第一个 `user`。
auto doc_store = ravendb::client::documents::DocumentStore::create(); doc_store->set_urls({ "http://live-test.ravendb.net" }); doc_store->set_database("Demo"); doc_store->initialize(); { auto session = doc_store->open_session(); auto user_record = std::make_shared<user>(); user_record->name = "John Doe"; user_record->emails = {"john.doe@example.com","non_existing@example.com","bar@example.com"}; session.store(user_record); user_record->age = 35; session.save_changes(); //Commits the record to the database }
需要为我们的记录类定义序列化和反序列化。在 附录中,我将解释如何使用 nlohmann/json 库轻松完成此操作。
如果此时我们在 RavenDB Studio 中查看,我们会看到这个
我们可以为此文档指定 ID,但由于我们没有指定,RavenDB 会自动生成一个唯一的 ID:`users/1-A`。
下面我们可以看到该文档是 JSON 格式的,并且其结构与我们的 `user` 类的结构非常匹配。
刚才发生了什么?
上面的代码做了一些“魔法”。RavenDB C++ 客户端允许我们使用高级 API 和我们的原生应用程序对象与数据库进行交互。一旦定义了序列化,它就会自动处理。您还可能注意到,在调用 `store()` 之后,`user` 的 `age` 字段被修改了,但当您调用 `save_changes()` 时,此更改仍然会被持久化。
现在我们已经在 RavenDB 上存储了第一个文档,让我们看看如何读取、更新和删除。
{ auto session = store->open_session("TestDB"); //Load a document by its id const std::shared_ptr<user> user = session.load<user>("users/1-A"); //Modify one of its fields user->age = 19; //No trip to the server yet session.delete_document("users/2-A"); //No trip to the server yet session.save_changes(); //Send the server both commands in one trip }
`session` 对象允许我们在单个 ACID 事务中执行许多操作。我们不必每次进行删除或部分更新时都与服务器进行往返通信,所有这些操作都将在调用 `save_changes()` 时一次性提交。
到处都是查询!
RavenDB 提供了专用搜索引擎的查询功能。以下是一些示例查询:
//A ‘starts with’ query to fetch all users whose name starts with "John": const std::vector<std::shared_ptr<user>> usersNamedJohn = session.query<user>()->where_starts_with("name", "John") ->order_by_descending("name") ->to_list(); //A 'full-text search’ query on the field "name", using RQL: const std::vector<std::shared_ptr<user>> rawUsersNamedJohn = session.advanced().raw_query<user>("from users where search(name, $name_to_search)") ->add_parameter("name_to_search","john") ->to_list();
这段代码也相当“神奇”。在存储了我们的文档之后,我们只需指定我们感兴趣的数据,让 RavenDB 来弄清楚如何检索它。
上面的 `raw_query()` 使用 Raven 查询语言 (RQL)。这是我们设计的查询语言,用于公开我们所有的底层查询选项。如果您了解 SQL,或者即使不了解,它也非常容易学习。
如果我们想进行聚合怎么办?此查询计算有多少用户拥有相同的电子邮件。
std::string aggregation_rql_query = "from users " "group by emails[] " //since 'emails' is an array, treat each item as group key "select key() as email, count() as count"; //projection const auto rawCountEmailsPerUser = session.advanced().raw_query<email_count_result> (aggregation_rql_query)->to_list();
此聚合查询将结果投影到下面定义的另一个对象中。
struct email_count_result { std::string email; //key of the aggregation int count{}; //count of emails };
正如在 附录中所解释的,这个类也需要进行序列化。
现在让我们来看一个包含所有元素的查询,包括括号和逻辑运算符。
std::string complex_query = "from users " "where emails[] in ( $email_list ) or " "(age > 25 and endsWith(name, 'Doe'))"; const vector<shared_ptr<user>> complexQueryResult = session.advanced().raw_query<user>(complex_query) ->add_parameter("email_list", nlohmann::json::array( {"john.doe@example.com","jane.doe@example.com" })) ->to_list();
结束
如您所见,与 RavenDB 交互和查询 RavenDB 非常简单、直观,并且不需要大量的复杂“管道”代码。
您可以从 GitHub 下载本文的完整 演示源代码。它假定在本地运行了一个 RavenDB 实例,但您也可以将其指向我们的演示服务器:http://live-test.ravendb.net。
要开始使用 C++ 客户端,您可以从 GitHub 仓库下载它,并使用 CMake 进行编译,如下面的 附录所示。为了方便起见,它会编译静态链接和动态链接库。
我想了解更多关于 RavenDB 的信息!
没问题!您可以在 RavenDB 文档中找到详细信息,并在我们的 训练营中找到包含代码示例的分步指南。
请参阅 Inside RavenDB 4.0 的 第二章,了解有关下载和设置本地实例的帮助。
附录:设置序列化
在演示项目中,我们使用 `set_val_to_json()` 和 `get_val_from_json()` 来定义 `user` 记录的序列化和反序列化。这些是 RavenDB C++ 客户端中定义的实用函数,为了方便起见,它们简化了 nlohmann/json 库的某些功能。
inline void to_json(nlohmann::json& j, const user& u) { using ravendb::client::impl::utils::json_utils::set_val_to_json; set_val_to_json(j, "name", u.name); set_val_to_json(j, "age", u.age); set_val_to_json(j, "emails", u.emails); } inline void from_json(const nlohmann::json& j, user& u) { using ravendb::client::impl::utils::json_utils::get_val_from_json; get_val_from_json(j, "name", u.name); get_val_from_json(j, "age", u.age); get_val_from_json(j, "emails", u.emails); }
附录:设置依赖项
获取源代码
在根目录的 `CMakeLists.txt` 中,以下命令将获取 RavenDB 客户端源代码,并将其设置为编译目标,我们将对其进行静态链接到父应用程序。
set (RAVENCPP_SRC "${PROJECT_SOURCE_DIR}/libs/RavenDB" CACHE INTERNAL "RAVENCPP_SRC") #fetch the RavenDB C++ client from the repo using ‘git clone’ include(FetchContent) FetchContent_Declare( ravendb_client_cpp GIT_REPOSITORY https://github.com/ravendb/ravendb-cpp-client.git GIT_TAG master SOURCE_DIR ${RAVENCPP_SRC}/repository SUBBUILD_DIR ${RAVENCPP_SRC}/subbuild BINARY_DIR ${RAVENCPP_SRC}/binary ) FetchContent_GetProperties(ravendb_client_cpp) if(NOT ravendb_client_cpp_POPULATED) FetchContent_Populate(ravendb_client_cpp) #since we don't want to compile tests and tryouts, only the client set(BUILD_TRYOUTS OFF) set(BUILD_TESTS OFF) add_subdirectory("${RAVENCPP_SRC}/repository/" "${RAVENCPP_SRC}/binary") endif()
确保包含客户端的头文件文件夹。
list(APPEND CMAKE_INCLUDE_PATH ${RAVENCPP_SRC}/repository/Raven.CppClient) set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} CACHE INTERNAL "CMAKE_INCLUDE_PATH")
由于我们将 C++ 客户端编译为父应用程序的一部分,因此我们需要包含其依赖项(RavenDB 通过 vcpkg 管理这些依赖项)。
include_directories(${CMAKE_INCLUDE_PATH}) link_directories(${CMAKE_LIBRARY_PATH})
这将确保在下一步中,我们不仅可以链接到客户端,还可以链接到其所有依赖项。
注意:RavenDB 客户端在变量中添加了相关的头文件和库文件夹。
${CMAKE_INCLUDE_PATH}, ${CMAKE_LIBRARY_PATH}
RavenDB C++ 客户端使用 vcpkg。这是 Microsoft 创建的 C++ 包管理器,可以为 CMake 项目下载和编译依赖项。您可以在其 GitHub 仓库上阅读有关它的更多信息。
链接 (Linking)
现在我们已经获取了客户端源代码,我们需要将其链接到实际的项目(CMake 术语中的“子文件夹”),该项目将连接到 RavenDB。
在子文件夹的 `CMakeLists.txt` 中,添加以下 CMake 命令:
find_package(CURL REQUIRED) find_package(OpenSSL REQUIRED) find_package(CURL REQUIRED) find_package(Threads REQUIRED)
最后,我们链接客户端及其依赖项。
add_executable ([executable name] "[executable name].cpp" "[executable name].h") target_include_directories([executable name] PUBLIC ${CMAKE_INCLUDE_PATH}) target_link_libraries([executable name] Raven_CppClient_static #the RavenDB client OpenSSL::SSL OpenSSL::Crypto ${CURL_LIBRARIES} Threads::Threads)