ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++ 项目里怎么优雅地接住 SQLite3?这份轻量数据库实战手记请收好

C++ 项目里怎么优雅地接住 SQLite3?这份轻量数据库实战手记请收好 C 项目里怎么优雅地接住 SQLite3这份轻量数据库实战手记请收好【免费下载链接】SQLiteCppSQLiteC (SQLiteCpp) is a smart and easy to use C SQLite3 wrapper.项目地址: https://gitcode.com/gh_mirrors/sq/SQLiteCpp如果你在写 C 小工具SQLiteCpp 这个 SQLite3 轻量封装库值得花十分钟认识一下它用现代 C 的 RAII 与异常体系把 SQLite3 的原生 C 接口重新包了一遍让你用几行代码就能完成建库、查询、事务和二进制存取。等等我的巡检数据该放哪假设你正在写一个机房巡检小工具每天凌晨要记录每台服务器的 CPU 温度、风扇转速、所在机柜和 IP偶尔还要存一张运维截图。数据量不大但结构固定、要反复查询你打算把它存在哪写进文本文件追加容易按条件过滤就得自己撸解析器。存成 JSON查一条记录要把整个文件读进来越写越慢。上 MySQL还得装服务、开端口、配账号为一个单机工具太铺张。你真正需要的是一个文件即数据库的方案数据落在一个本地文件里进程内直接读写零服务、零配置。这正是 SQLite3 的看家本领也是无数 C 桌面应用和嵌入式项目的默认答案。剩下的问题是怎么在 C 里优雅地调用它SQLiteCpp把 C 接口包成现代 C 的样子SQLite3 官方提供的是纯 C API你每次查询都要走打开句柄、prepare、step、finalize四部曲还要时刻惦记手动释放sqlite3_stmt*错误码满天飞。裸调一两次还行写多了全是样板代码。SQLiteCpp 把这套繁琐流程全部收进几个类里SQLite::Database管连接SQLite::Statement管预处理语句SQLite::Transaction管事务。句柄生命周期交给 RAII出错直接抛异常而不是返回错误码。它只有一个硬依赖——SQLite3 本身MIT 许可证对商业项目也友好头文件里有详尽的 Doxygen 注释仓库还自带全套单元测试用起来心里有底。三步完成编译接入从拉取源码到跑通第一个库第一步把代码拿下来仓库地址是 https://gitcode.com/gh_mirrors/sq/SQLiteCpp git clone https://gitcode.com/gh_mirrors/sq/SQLiteCpp.git第二步用 CMake 构建。项目支持add_subdirectory直接塞进你的工程也可以单独编出静态库再链接。构建测试与示例的命令如下cmake -DSQLITECPP_BUILD_TESTSON -DSQLITECPP_BUILD_EXAMPLESON -B build cmake --build build ctest --test-dir build --output-on-failure第三步接入你的工程。在 CMakeLists.txt 里加一行add_subdirectory(SQLiteCpp)然后把SQLiteCpp和sqlite3加进target_link_libraries即可。下面这个最小示例解决建一个库、建一张表的问题你可以直接编译运行#include SQLiteCpp/SQLiteCpp.h #include iostream int main() { try { // 以“不存在则创建、可读写”的模式打开机房台账数据库 SQLite::Database db(asset_room.db3, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); db.exec(CREATE TABLE IF NOT EXISTS devices ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, rack TEXT, ip TEXT, cpu_temp REAL)); std::cout 台账就绪文件位于 db.getFilename() std::endl; } catch (const std::exception e) { std::cerr 初始化失败: e.what() std::endl; } return 0; }这里要注意一个知识点db是栈上对象作用域结束自动关闭连接你不需要写任何close。整段代码包在 try-catch 里任何 SQLite 层错误都会变成异常错误信息直接来自底层。高频操作拆解一条命令跑通增删改查先来一套增删改查把台账盘活下面的代码解决插入设备并按机柜查询这个日常需求SQLite::Database db(asset_room.db3, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); // 写入一台新服务器 db.exec(INSERT INTO devices (name, rack, ip, cpu_temp) VALUES (web-node-03, B-12, 10.0.3.15, 52.5)); // 按机柜查询executeStep 每调用一次前进一行 SQLite::Statement q(db, SELECT name, ip, cpu_temp FROM devices WHERE rack ?); q.bind(1, B-12); while (q.executeStep()) { std::string name q.getColumn(0).getString(); std::string ip q.getColumn(1).getString(); double temp q.getColumn(2).getDouble(); std::cout name ip 温度 temp std::endl; }getColumn 返回的SQLite::Column可以按需转成 int、double、string 等类型取值这一下就把 C API 里繁琐的类型转换给省掉了。删除和更新同理换个 SQL 就行套路完全一致。预处理语句防 SQL 注入参数只当文本不当代码写 SQL 最容易翻车的地方是字符串拼接。比如把用户的输入直接拼进 SQL人家敲一句; DROP TABLE devices; --就能把表删了。预处理语句就是为了治这个病// 用户输入可能携带恶意 SQL绝不能用字符串拼接 std::string keyword getUserInput(); // 假设用户输入了恶意内容 SQLite::Statement stmt(db, SELECT name FROM devices WHERE name LIKE ?); stmt.bind(1, % keyword %); // 参数只被当作文本处理 while (stmt.executeStep()) { std::cout stmt.getColumn(0).getString() std::endl; }要点在于?占位符对应一个绑定参数SQLite 会把绑定值严格当数据对待永远不会把它解析成新的 SQL 指令。建议你把一切用户输入一律走 bind当成铁律这条习惯能帮你挡掉 99% 的注入攻击。批量写入与事务回滚把一千次写入打包成一次一次巡检可能产生几千条传感器读数。如果逐条写库每条都要经历一次磁盘同步慢且不说——万一写到一半程序崩了库里就留下一堆半截数据。事务的作用就像网购的统一打包、要么全成要么全退// 把一千条读数包进一个事务中途出错整体回滚 SQLite::Transaction tx(db); for (const auto rec : sensorReadings) { SQLite::Statement ins(db, INSERT INTO readings (device_id, value, ts) VALUES (?, ?, ?)); ins.bind(1, rec.deviceId); ins.bind(2, rec.value); ins.bind(3, rec.ts); ins.exec(); } tx.commit(); // 全部成功才落盘若中途抛异常析构自动回滚这里有个特别省心的设计Transaction对象在构造时自动开启事务析构时若还没 commit 会自动回滚。你只管在成功路径上调用commit()异常路径完全不用手动管。复杂类型存取BLOB 与一行变参绑定巡检截图这类二进制数据可以直接以 BLOB 存进 SQLite省得在磁盘上散落一堆小文件// 把运维截图塞进数据库绑定 void* 指针加字节数 std::vectorchar png loadFile(screenshot.png); SQLite::Statement ins(db, INSERT INTO snapshots (device_id, image) VALUES (?, ?)); ins.bind(1, 7); ins.bind(2, png.data(), static_castint(png.size())); ins.exec(); // 读取时用 getBlob/getBytes 取回原始数据 SQLite::Statement sel(db, SELECT image FROM snapshots WHERE device_id 7); if (sel.executeStep()) { const void* blob sel.getColumn(0).getBlob(); int size sel.getColumn(0).getBytes(); // 接下来转成 std::vectorchar 即可还原图片 }顺带一提如果你的编译器支持 C14还能用变参绑定把多个 bind 压缩成一行SQLite::Statement ins2(db, INSERT INTO devices (name, rack, ip) VALUES (?, ?, ?)); SQLite::bind(ins2, db-node-01, A-03, 10.0.1.9); // 自动从序号 1 开始避坑与进阶多线程与常见报错排查多线程是新手最容易踩的雷。SQLiteCpp 支持 SQLite 的多线程模式但有个硬约束同一个Database或Statement对象不能跨线程使用。正确的姿势是每个线程各自打开连接// 每个工作线程持有自己的连接互不共享对象 void workerThread(const std::string dbPath, int tid) { SQLite::Database db(dbPath, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); SQLite::Statement stmt(db, INSERT INTO readings (value, ts) VALUES (?, ?)); stmt.bind(1, tid); stmt.bind(2, static_castint(time(nullptr))); stmt.exec(); }实际踩坑中你还会常遇到这些报错给你几条排查思路database is locked多半是某个事务没提交或没回滚写锁一直被占着。检查所有Transaction是否都走到了commit()。no such table查一下建表语句和查询是否连的是同一个数据库文件路径写错很常见。查询结果莫名为空确认executeStep()返回 false 就退出循环是正常行为需要单个标量时可以用db.execAndGet(SELECT COUNT(*) FROM ...)。另外想知道底层 SQLite 版本时编译期常量SQLite::VERSION和运行时函数SQLite::getLibVersion()都能帮上忙排查兼容性问题时很有用。收尾现在就动手把日志换成数据库回过头看SQLiteCpp 的核心价值就一句话用 C 的方式写 SQLite。它把句柄管理、错误处理、类型转换这些脏活全包了让你把注意力放回业务本身。零外部依赖、跨平台、MIT 协议堪称 C 小项目接数据库的低摩擦方案。建议你现在就打开编辑器做三件事先把上面的最小示例跑通再把仓库里的examples/和tests/目录翻一遍——那里有现成的完整用例可以直接抄最后把你手头那个还在写文本文件的日志工具改成 SQLite 落盘试试。数据库并不神秘十分钟上手之后你大概率会回来把更多数据都迁进去。【免费下载链接】SQLiteCppSQLiteC (SQLiteCpp) is a smart and easy to use C SQLite3 wrapper.项目地址: https://gitcode.com/gh_mirrors/sq/SQLiteCpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表