Введение
Qt SQL Module (QSqlDatabase, QSqlQuery) спроектирован специально для реляционных баз данных и не поддерживает NoSQL-хранилища типа MongoDB напрямую. Если приложению нужен доступ к MongoDB (например, для хранения документов с гибкой, заранее не фиксированной структурой), нужно использовать отдельный официальный MongoDB C++ Driver, интегрированный с остальным Qt-кодом вручную. В статье разберём базовое подключение и операции с MongoDB из Qt-приложения.
Концепция
MongoDB C++ Driver (mongocxx/bsoncxx) — отдельная, не связанная с Qt библиотека со своей моделью данных на основе BSON (бинарного JSON-подобного формата). Интеграция с Qt сводится к написанию собственного, тонкого repository-слоя (аналогично паттерну из статьи 231, но без QSqlQuery), который конвертирует между BSON-документами MongoDB и QJsonObject/собственными C++ структурами Qt-приложения — благодаря схожести JSON и BSON эта конвертация относительно прямолинейна.
Пример кода: Подключение NoSQL (MongoDB)
# CMakeLists.txt — подключение MongoDB C++ Driver
find_package(mongocxx REQUIRED)
find_package(bsoncxx REQUIRED)
target_link_libraries(MyApp PRIVATE
Qt6::Core
mongo::mongocxx_shared
mongo::bsoncxx_shared
)
// mongoeventrepository.h — repository-слой поверх MongoDB для логов событий
#pragma once
#include <mongocxx/client.hpp>
#include <mongocxx/instance.hpp>
#include <bsoncxx/builder/stream/document.hpp>
#include <bsoncxx/json.hpp>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
class MongoEventRepository
{
public:
explicit MongoEventRepository(const std::string &uri, const std::string &dbName)
: m_client(mongocxx::uri(uri))
{
m_database = m_client[dbName];
}
bool logEvent(const QString &eventType, const QJsonObject &payload)
{
using bsoncxx::builder::stream::document;
using bsoncxx::builder::stream::finalize;
// Конвертируем QJsonObject в JSON-строку, а затем в формат BSON MongoDB
const QByteArray jsonBytes = QJsonDocument(payload).toJson(QJsonDocument::Compact);
const auto payloadBson = bsoncxx::from_json(jsonBytes.toStdString());
document builder{};
builder << "eventType" << eventType.toStdString()
<< "timestamp" << bsoncxx::types::b_date(
std::chrono::system_clock::now())
<< "payload" << payloadBson.view();
auto collection = m_database["events"];
const auto result = collection.insert_one(builder.view());
return result.has_value();
}
QVector<QJsonObject> findEventsByType(const QString &eventType, int limit = 50)
{
QVector<QJsonObject> results;
using bsoncxx::builder::stream::document;
using bsoncxx::builder::stream::finalize;
document filter{};
filter << "eventType" << eventType.toStdString();
auto collection = m_database["events"];
mongocxx::options::find options;
options.limit(limit);
auto cursor = collection.find(filter.view(), options);
for (const auto &doc : cursor) {
const std::string jsonString = bsoncxx::to_json(doc);
const QJsonDocument parsedDoc = QJsonDocument::fromJson(
QByteArray::fromStdString(jsonString));
results.append(parsedDoc.object());
}
return results;
}
private:
mongocxx::client m_client;
mongocxx::database m_database;
};
// main.cpp — обязательная единоразовая инициализация драйвера
#include <mongocxx/instance.hpp>
int main(int argc, char *argv[])
{
mongocxx::instance instance{}; // должен существовать ровно один экземпляр на всё время работы программы
QCoreApplication app(argc, argv);
MongoEventRepository repository("mongodb://localhost:27017", "myapp_logs");
repository.logEvent("user_login", QJsonObject{{"userId", 42}, {"ip", "192.168.1.5"}});
return app.exec();
}
Пояснения к коду
MongoEventRepository::logEvent() показывает ключевую идею интеграции — QJsonObject (привычный из основного цикла статей про JSON) сначала сериализуется в QByteArray через QJsonDocument::toJson(), а затем конвертируется в формат BSON через bsoncxx::from_json() — это позволяет приложению продолжать работать с привычными Qt JSON-классами в остальном коде, выполняя конвертацию в формат MongoDB только в самом тонком, граничном слое repository.
findEventsByType() выполняет обратную конвертацию — каждый найденный BSON-документ из курсора результатов конвертируется обратно в JSON-строку через bsoncxx::to_json(), а затем разбирается уже знакомым QJsonDocument::fromJson(). mongocxx::instance instance{} в main() — обязательная деталь, легко забываемая новичками в работе с этим драйвером: согласно требованиям самой библиотеки MongoDB C++ Driver, должен существовать ровно один экземпляр mongocxx::instance на всё время жизни процесса, созданный до любого использования других классов драйвера.
Подводные камни
- Забытое создание единственного
mongocxx::instanceили попытка создать несколько экземпляров — согласно документации драйвера, это либо приводит к неопределённому поведению, либо к явному исключению, зависящему от конкретной версии драйвера; этот объект должен жить дольше, чем любое использованиеmongocxx::client, и обычно создаётся первой строкой вmain(). - Потеря типов данных при двойной конвертации JSON → BSON → JSON. Хотя BSON и JSON концептуально близки, BSON поддерживает дополнительные типы, не имеющие прямого аналога в стандартном JSON (например, специфичные типы дат, как
b_dateв примере) — при обратной конвертации вQJsonObjectтакие специфичные BSON-типы могут быть представлены не так, как ожидается, и для критичных полей (особенно дат) стоит явно проверять, как именно конкретная используемая версияbsoncxx::to_json()сериализует эти типы. - Отсутствие схемы (schema-less природа MongoDB) перекладывает всю ответственность за консистентность структуры документов на код приложения. В отличие от реляционной базы данных с фиксированной схемой таблиц, MongoDB не запрещает вставить документ с произвольно отличающейся структурой — без дисциплинированного, явного контроля на уровне repository-слоя (например, через валидацию JSON Schema, разобранную в основном цикле статей про JSON, статья 147) база данных постепенно может накопить документы с несовместимой между собой структурой, что усложняет последующую обработку.
- Линковка с
mongocxx/bsoncxxтребует, чтобы соответствующие библиотеки и заголовки были установлены в системе или подключены через vcpkg/Conan (основной цикл статей про CMake/Boost, статьи 33, 40) — это полноценная отдельная зависимость со своими требованиями к версии компилятора и системным библиотекам (включая сам MongoDB C Driver, от которого зависит C++ драйвер), и интеграция в существующий CMake-проект требует дополнительной настройки по сравнению с встроенным в Qt модулем QtSql.