高效SQLite操作:基于C++模板元编程的自动化封装

在传统的SQLite操作中,开发者常常面临以下几个痛点:

  1. 手动编写CREATE TABLE语句:不仅繁琐,还容易因类型映射错误导致问题;
  2. 手动绑定参数:容易出现类型不匹配或参数索引错误;
  3. 频繁单条插入性能低下:大量磁盘I/O严重影响效率;
  4. 资源管理复杂:数据库连接、事务等需手动管理,容易发生资源泄露。

为解决上述问题,我提供了一种自动化方法,能够自动生成表创建语句、插入语句,实现类型映射与缓存管理。尽管Python代码生成或C++模板元编程均可实现该功能,本文选择基于C++模板元编程与变长参数机制,以兼顾性能与使用便捷性。

类型映射

通过模板将c++类型转换成SQLite类型。

cpp 复制代码
template<typename T>
struct SQLiteType {
    static const char *value;
};

template<> const char *SQLiteType<int64_t>::value = "INTEGER";
template<> const char *SQLiteType<int32_t>::value = "INTEGER";
template<> const char *SQLiteType<int8_t>::value = "INTEGER";
template<> const char *SQLiteType<float>::value = "REAL";
template<> const char *SQLiteType<double>::value = "REAL";
template<> const char *SQLiteType<const char *>::value = "TEXT";
template<> const char *SQLiteType<std::string>::value = "TEXT";
template<> const char *SQLiteType<bool>::value = "INTEGER";

生成表创建和数据插入语句

利用变长参数模板递归生成SQL语句,避免手动编写。

cpp 复制代码
    template<typename Last>
    static void generate_sql_var_type(std::ostringstream& oss, const std::pair<const char *, Last>& field) {
        oss << field.first << " " << SQLiteType<Last>::value << " NOT NULL";
    }

    template<typename T, typename... Args>
    static void generate_sql_var_type(std::ostringstream& oss,
                                      const std::pair<const char *, T>& first,
                                      const std::pair<const char *, Args>& ... rest) {
        oss << first.first << " " << SQLiteType<T>::value << " NOT NULL";
        if (sizeof...(rest) > 0) {
            oss << ", ";
            generate_sql_var_type(oss, rest...); // 递归处理剩余参数
        }
    }

    std::string generate_init_table_statement(const std::ostringstream& var_type_statement) {
        return "create table if not exists " + table_name + " (" + var_type_statement.str() + ");";
    }

用同样的方法生成数据插入语句。

cpp 复制代码
    template<typename Last>
    static void generate_sql_insert(std::ostringstream& oss_var, std::ostringstream& oss_val,
                                    const std::pair<const char *, Last>& field) {
        oss_var << field.first;
        oss_val << "?";
    }

    template<typename T, typename... Args>
    static void generate_sql_insert(std::ostringstream& oss_var,
                                    std::ostringstream& oss_val,
                                    const std::pair<const char *, T>& first,
                                    const std::pair<const char *, Args>& ... rest) {
        oss_var << first.first;
        oss_val << "?";
        if (sizeof...(rest) > 0) {
            oss_var << ", ";
            oss_val << ", ";
            generate_sql_insert(oss_var, oss_val, rest...); // 递归处理剩余参数
        }
    }

    std::string generate_insert_table_statement(const std::ostringstream& var, const std::ostringstream& val) {
        return "insert into " + table_name + " (" + var.str() + ") values (" + val.str() + ");";
    }

缓存

使用std::vector和std::tuple作为数据缓存,并在达到指定条数时自动触发批量写入。

cpp 复制代码
std::vector<std::tuple<First, Rest...>> caches{};

绑定参数时展开tuple,实现自动参数绑定。

cpp 复制代码
    template<int Index = 0, typename... Ts>
    void bind_values_impl(SQLite::Statement& query, const std::tuple<Ts...>& t) {
        if constexpr (Index < sizeof...(Ts)) {
            query.bind(Index + 1, std::get<Index>(t));
            bind_values_impl<Index + 1>(query, t);
        }
    }

完整实现

cpp 复制代码
#ifndef SQLITE_WRITER_HPP
#define SQLITE_WRITER_HPP

#include <iostream>
#include <string>
#include <utility>
#include <sstream>
#include <vector>
#include <tuple>
#include "SQLiteCpp/SQLiteCpp.h"

#define SQLITE_WRITER_SUCCESS           0
#define SQLITE_WRITER_NOT_INIT          (-1)
#define SQLITE_WRITER_NOT_ENABLE        (-2)
#define SQLITE_WRITER_CACHE_NOT_ENOUGH  (-3)

// 类型映射:C++ 类型 → SQLite 类型
template<typename T>
struct SQLiteType {
    static const char *value;
};

template<> const char *SQLiteType<int64_t>::value = "INTEGER";
template<> const char *SQLiteType<int32_t>::value = "INTEGER";
template<> const char *SQLiteType<int8_t>::value = "INTEGER";
template<> const char *SQLiteType<float>::value = "REAL";
template<> const char *SQLiteType<double>::value = "REAL";
template<> const char *SQLiteType<const char *>::value = "TEXT";
template<> const char *SQLiteType<std::string>::value = "TEXT";
template<> const char *SQLiteType<bool>::value = "INTEGER";

template<typename First, typename... Rest>
class SqliteWriter {
public:
    SqliteWriter() = delete;

    explicit SqliteWriter(const bool enable,
                          const std::string& db_path,
                          const std::string& table_name,
                          const uint32_t cache_count,
                          const std::pair<const char *, First>& first_field,
                          const std::pair<const char *, Rest>& ... rest_field) :
            enable(enable), db_path(db_path), table_name(table_name), cache_count(cache_count) {
        std::ostringstream oss0{};
        generate_sql_var_type(oss0, first_field, rest_field...);
        init_table_statement = generate_init_table_statement(oss0);

        std::ostringstream oss1{};
        std::ostringstream oss2{};
        generate_sql_insert(oss1, oss2, first_field, rest_field...);
        insert_table_statement = generate_insert_table_statement(oss1, oss2);
    }

    ~SqliteWriter() {
        if (enable) {
            write_immediately();
        }
    }

    int32_t init() {
        if (!enable) {
            return SQLITE_WRITER_NOT_ENABLE;
        }
        db = std::make_unique<SQLite::Database>(db_path, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
        db->exec("PRAGMA journal_mode = WAL");
        db->exec("PRAGMA synchronous = OFF");
        db->exec(init_table_statement);
        caches.reserve(cache_count);
        return SQLITE_WRITER_SUCCESS;
    }

    template<typename... Values>
    int32_t write_cache(const Values& ... values) {
        if (!enable) {
            return SQLITE_WRITER_NOT_ENABLE;
        }
        caches.emplace_back(values...);
        if (caches.size() < cache_count) {
            return SQLITE_WRITER_CACHE_NOT_ENOUGH;
        }
        return write_immediately();;
    }

    template<typename... Values>
    int32_t write_cache(Values&& ... values) {
        if (!enable) {
            return SQLITE_WRITER_NOT_ENABLE;
        }
        caches.emplace_back(std::forward<Values>(values)...);
        if (caches.size() < cache_count) {
            return SQLITE_WRITER_CACHE_NOT_ENOUGH;
        }
        return write_immediately();
    }

    const std::string& get_init_table_statement() {
        return init_table_statement;
    }

    const std::string& get_insert_table_statement() {
        return insert_table_statement;
    }

protected:

    template<typename Last>
    static void generate_sql_var_type(std::ostringstream& oss, const std::pair<const char *, Last>& field) {
        oss << field.first << " " << SQLiteType<Last>::value << " NOT NULL";
    }

    template<typename T, typename... Args>
    static void generate_sql_var_type(std::ostringstream& oss,
                                      const std::pair<const char *, T>& first,
                                      const std::pair<const char *, Args>& ... rest) {
        oss << first.first << " " << SQLiteType<T>::value << " NOT NULL";
        if (sizeof...(rest) > 0) {
            oss << ", ";
            generate_sql_var_type(oss, rest...); // 递归处理剩余参数
        }
    }

    template<typename Last>
    static void generate_sql_insert(std::ostringstream& oss_var, std::ostringstream& oss_val,
                                    const std::pair<const char *, Last>& field) {
        oss_var << field.first;
        oss_val << "?";
    }

    template<typename T, typename... Args>
    static void generate_sql_insert(std::ostringstream& oss_var,
                                    std::ostringstream& oss_val,
                                    const std::pair<const char *, T>& first,
                                    const std::pair<const char *, Args>& ... rest) {
        oss_var << first.first;
        oss_val << "?";
        if (sizeof...(rest) > 0) {
            oss_var << ", ";
            oss_val << ", ";
            generate_sql_insert(oss_var, oss_val, rest...); // 递归处理剩余参数
        }
    }

    std::string generate_init_table_statement(const std::ostringstream& var_type_statement) {
        return "create table if not exists " + table_name + " (" + var_type_statement.str() + ");";
    }

    std::string generate_insert_table_statement(const std::ostringstream& var, const std::ostringstream& val) {
        return "insert into " + table_name + " (" + var.str() + ") values (" + val.str() + ");";
    }

    int32_t write_immediately() {
        if (db == nullptr) {
            return SQLITE_WRITER_NOT_INIT;
        }
        SQLite::Transaction transaction(*db);
        SQLite::Statement query(*db, insert_table_statement);
        for (const auto& cache: caches) {
            query.reset();
            bind_values(query, cache);
            query.exec();
        }
        transaction.commit();
        caches.clear();
        return SQLITE_WRITER_SUCCESS;
    }

    void bind_values(SQLite::Statement& query, const std::tuple<First, Rest...>& values) {
        bind_values_impl(query, values);
    }

    template<int Index = 0, typename... Ts>
    void bind_values_impl(SQLite::Statement& query, const std::tuple<Ts...>& t) {
        if constexpr (Index < sizeof...(Ts)) {
            query.bind(Index + 1, std::get<Index>(t));
            bind_values_impl<Index + 1>(query, t);
        }
    }

protected:
    std::vector<std::tuple<First, Rest...>> caches{};
    std::unique_ptr<SQLite::Database> db{nullptr};
    bool enable{true};
    std::string db_path{};
    std::string table_name{};
    uint32_t cache_count{};
    std::string init_table_statement{};
    std::string insert_table_statement{};
};

#endif //SQLITE_WRITER_HPP

总结

该封装库极大简化了SQLite操作流程,提升了开发效率与代码可靠性,同时保持了C++应用的性能优势。适用于需要高效、可靠数据库操作的各种C++应用场景。

相关推荐
ajassi20007 小时前
开源 C++ QT QML 开发(十一)通讯--TCP服务器端
c++·qt·开源
minji...7 小时前
Linux相关工具vim/gcc/g++/gdb/cgdb的使用详解
linux·运维·服务器·c++·git·自动化·vim
_OP_CHEN7 小时前
C++基础:(九)string类的使用与模拟实现
开发语言·c++·stl·string·string类·c++容器·stl模拟实现
爱编程的化学家7 小时前
代码随想录算法训练营第27天 -- 动态规划1 || 509.斐波那契数列 / 70.爬楼梯 / 746.使用最小花费爬楼梯
数据结构·c++·算法·leetcode·动态规划·代码随想录
数字化顾问8 小时前
C++分布式语音识别服务实践——架构设计与关键技术
c++
智能化咨询8 小时前
C++分布式语音识别服务实践——性能优化与实战部署
c++
ajassi20009 小时前
开源 C++ QT QML 开发(十四)进程用途
c++·qt·开源
闻缺陷则喜何志丹9 小时前
【C++贪心】P8769 [蓝桥杯 2021 国 C] 巧克力|普及+
c++·算法·蓝桥杯·洛谷
杨小码不BUG9 小时前
灯海寻踪:开灯问题的C++精妙解法(洛谷P1161)
c++·算法·数学建模·位运算·浮点数·信奥赛·csp-j/s