You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Boost C++从SQL获取泛型对象?自定义ORM开发遇阻

如何基于Boost C++实现SQL到泛型对象的ORM映射?

我目前在基于SQLite和C实现数据库操作,已经完成了数据表的创建、删除以及对象的增删功能,但在查询时无法将数据库记录映射为内存中的C对象,只能返回打印摘要。我想实现类ORM的映射功能,远期目标是支持代码生成,当前需要解决未知结构表的查询与对象编辑问题,同时不确定现有设计是否合理。

已知前提:所有数据库对象至少包含整数类型的id、字符串类型的name,以及任意数量的其他类型字段,希望直接返回对象本身而非字符串表示。

现有代码如下:

头文件:SelectOperation.h

#pragma once
#include "../SqlOperation.h"
#include "../../Exceptions/DbException.h"
#include "ITableRecord.h"
#include <sqlite3.h>
#include <vector>
#include <string>

class SelectOperation : public SqlOperation
{
    public:
        SelectOperation(const std::string& tableName);
        void prepareStatement(sqlite3_stmt** stmt, sqlite3* db) override;
};

实现文件:selectOperation.cpp

#include "SelectOperation.h"

SelectOperation::SelectOperation(const std::string& tableName) 
{
    sql_statement = "SELECT * FROM " + tableName;
}

void
SelectOperation::prepareStatement(sqlite3_stmt** stmt, sqlite3* db) 
{
    int rc = sqlite3_prepare_v2(db, sql_statement.c_str(), -1, stmt, nullptr);
    if (rc != SQLITE_OK) {
        throw DbException("Failed to prepare select statement: " + std::string(sqlite3_errmsg(db)));
    }
}

当前prepareStatement仅能预处理SQL语句,无法将查询结果映射为泛型对象,核心问题是不知道如何处理结构未知的表记录。


解决方案

1. 定义泛型记录基类,支持动态字段存储

扩展ITableRecord,强制实现核心字段id/name的访问接口,同时用boost::any实现任意类型字段的动态存储:

#include <boost/any.hpp>
#include <unordered_map>

class ITableRecord {
public:
    virtual ~ITableRecord() = default;

    // 核心字段强制接口
    virtual int getId() const = 0;
    virtual void setId(int id) = 0;
    virtual const std::string& getName() const = 0;
    virtual void setName(const std::string& name) = 0;

    // 动态字段读写模板
    template<typename T>
    T getField(const std::string& fieldName) const {
        auto it = m_fields.find(fieldName);
        if (it == m_fields.end()) {
            throw DbException("Field not found: " + fieldName);
        }
        return boost::any_cast<T>(it->second);
    }

    template<typename T>
    void setField(const std::string& fieldName, const T& value) {
        m_fields[fieldName] = value;
    }

protected:
    std::unordered_map<std::string, boost::any> m_fields;
};

2. 实现通用记录类

基于基类实现通用的记录实例,用于存储任意表的查询结果:

class GenericTableRecord : public ITableRecord {
private:
    int m_id;
    std::string m_name;
public:
    int getId() const override { return m_id; }
    void setId(int id) override { m_id = id; }
    const std::string& getName() const override { return m_name; }
    void setName(const std::string& name) override { m_name = name; }
};

3. 修改SelectOperation,添加结果映射逻辑

拆分预处理与执行逻辑,新增execute方法负责查询并返回泛型对象列表:

// 在SelectOperation.h中新增方法声明
std::vector<std::unique_ptr<ITableRecord>> execute(sqlite3* db);

// 在selectOperation.cpp中实现execute方法
std::vector<std::unique_ptr<ITableRecord>> SelectOperation::execute(sqlite3* db) {
    sqlite3_stmt* stmt = nullptr;
    prepareStatement(&stmt, db);

    std::vector<std::unique_ptr<ITableRecord>> records;
    int rc;

    while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
        auto record = std::make_unique<GenericTableRecord>();

        // 读取核心字段
        int idColIdx = sqlite3_column_index(stmt, "id");
        record->setId(sqlite3_column_int(stmt, idColIdx));
        
        int nameColIdx = sqlite3_column_index(stmt, "name");
        const char* namePtr = reinterpret_cast<const char*>(sqlite3_column_text(stmt, nameColIdx));
        record->setName(namePtr ? std::string(namePtr) : "");

        // 读取动态字段
        int colCount = sqlite3_column_count(stmt);
        for (int i = 0; i < colCount; ++i) {
            const char* colName = sqlite3_column_name(stmt, i);
            std::string fieldName(colName);
            if (fieldName == "id" || fieldName == "name") continue;

            switch (sqlite3_column_type(stmt, i)) {
                case SQLITE_INTEGER:
                    record->setField<int>(fieldName, sqlite3_column_int(stmt, i));
                    break;
                case SQLITE_TEXT: {
                    const char* textPtr = reinterpret_cast<const char*>(sqlite3_column_text(stmt, i));
                    record->setField<std::string>(fieldName, textPtr ? std::string(textPtr) : "");
                    break;
                }
                case SQLITE_FLOAT:
                    record->setField<double>(fieldName, sqlite3_column_double(stmt, i));
                    break;
                case SQLITE_BLOB: {
                    int blobSize = sqlite3_column_bytes(stmt, i);
                    const uint8_t* blobData = static_cast<const uint8_t*>(sqlite3_column_blob(stmt, i));
                    record->setField<std::vector<uint8_t>>(fieldName, std::vector<uint8_t>(blobData, blobData + blobSize));
                    break;
                }
                case SQLITE_NULL:
                    // 可根据需求处理空值,比如存储默认值或留空
                    break;
                default:
                    throw DbException("Unsupported column type: " + std::to_string(sqlite3_column_type(stmt, i)));
            }
        }

        records.push_back(std::move(record));
    }

    if (rc != SQLITE_DONE) {
        throw DbException("Select execution failed: " + std::string(sqlite3_errmsg(db)));
    }

    sqlite3_finalize(stmt);
    return records;
}

4. 现有设计优化建议

  • 单一职责拆分:将SQL语句构建、预处理、执行逻辑拆分为独立方法,避免一个方法承担过多职责。
  • 引入工厂模式:为远期代码生成做铺垫,可实现TableRecordFactory,根据表名创建对应实体类(而非仅通用类)。
  • 封装类型映射:将SQLite类型与C++类型的映射逻辑封装为工具类,方便后续扩展和维护。
  • 事务支持:在SqlOperation基类中添加事务管理接口,保证增删改操作的原子性。

内容的提问来源于stack exchange,提问作者user284985

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 21:08:13