加载中...
sqlite-orm-迅速上手.上
第1节:libfswatch-文件变动监控
第2节:libiconv-字符集编码转换
第3节:CLI11-命令行参数解析
第4节:nlohmann/json-自然的JSON库
第5节:libb64-理解并玩转base64编码
第6节:libSnappy-快速压缩工具
第7节:spdlog-首选的C++日志库
第8节:libUSB-脱掉USB的外套
第9节:libxlsxwriter-让数据说话
第10节:sqlite-orm-迅速上手.上
第11节:sqlite-orm-迅速上手.下
第12节:libExiv2-读写图像元数据
第13节:webview-让浏览器颤抖的力量
第14节:pystring-躺平的C++字符串工具库
第15节:cpr-自制Deepseek心理陪聊师
第16节:ExprTk-计算的力量
课文封面

借助ORM(对象与记录的映射), 迅速用上 sqlite3。

  1. 简介、安装、快速感受
  2. ORM:建立映射
  3. 查询:get、get_all、iterate、select
  4. 条件、排序、限行、偏移
  5. CRUD 全套
  6. 事务

使用数据库,但不使用 ORM —— 很正常。
使用数据库,但从没用过 sqlite3 ? —— 你有点奇怪。
使用 sqlite3 但不使用 ORM?—— 陷入深思……

我们分上下两节讲解 sqlite3-orm :

  • 上半部分
  1. 简介、安装、快速感受
  2. ORM:建立映射
  3. 查询:get、get_all、iterate、select
  4. CRUD 全套
  5. 事务
  • 下半部分
  1. 分组与聚合
  2. 联合查询
  3. 预处理语句
  4. 建库的更多配置
  5. 一点思考

1. 快速感受

  • 安装

在 msys2 的 UCRT64 子环境下,安装指令为:mingw-w64-ucrt-x86_64-sqlite-orm

sqlite-orm 采用单一头文件提供,但自身依赖 sqlite3 库, msys2 可自动处理依赖关系。

  • It’s time to study sqlite-orm
void its_time() { auto storage = sqlite_orm::make_storage(""); storage.sync_schema(); auto now = storage.select(sqlite_orm::datetime("now", "localtime")).front(); std::cout << "现在是 " << now << ", 是时候学习 sqlite_orm 了!" << std::endl; }

2. 建立映射

一个数据库,通常对应到 C++ 代码中的一整套 struct / class 的设计(大型项目可考虑为这些类型定义统一namespace);而一个结构体(或类),则对应到数据库中的一张表或一个视图,结构或类的成员数据,对应到表或视图中的字段。

映射关系

更多讲解,请观看视频:

  • 创建映射示例代码
// 家庭成员 struct FamilyMember { int id = -1; // 主键,在表中自增长 std::string role; // 角色:爷爷、奶奶……, 不可重复 std::optional<std::string> tel; // 电话号码,可能没有 std::string birthday; // 出生日期,在表中不可为空 std::string avatar = "0.png"; // 头像文件名,默认为 0.png bool is_admin = false; // 是否为管理员,默认为 false }; namespace orm = sqlite_orm; // 命名空间别名 // 建立映射 auto create_storage(std::string db_file) { return orm::make_storage(db_file, orm::make_table("family_members" , orm::make_column("id", &FamilyMember::id, orm::primary_key().autoincrement()) , orm::make_column("role", &FamilyMember::role, orm::unique()) , orm::make_column("tel", &FamilyMember::tel, orm::null()) , orm::make_column("birthday", &FamilyMember::birthday, orm::not_null()) , orm::make_column("avatar", &FamilyMember::avatar, orm::default_value("0.png")) , orm::make_column("is_admin", &FamilyMember::is_admin, orm::default_value(false)) ) ); }

3. 四种查询

get: 通过主键值,查询指定记录,并转换为对象,查询结果可能是 1 条,也可能是查无结果,后者将触发异常——可通过变体:get_pointerget_optional 解决;

get_all: 查询满足条件的多条记录,结果集使用 std::vector<对象类型> 存储(如未指定条件,将返回全部记录,下同);

iterate: 查询满足条件的多条记录,并逐条获取,通常配合循环语句使用;

select: 查询满足条件的记录,仅返回指定的字段数据,结果集使用 std::vector<std::tuple<...>> 存储。

更多讲解,请观看视频:

4. 条件、排序……

4.1 适用与不适用

  • 不适用方法(语句)
    ✗ get
    ✗ update
    ✗ remove

  • 适用方法(语句)
    ✓ get_all
    ✓ iterate
    ✓ select
    ✓ update_all
    ✓ remove_all

在 sqlite-orm 的设计中,get、update、remove 都需依赖主键来精准定位到一条记录(以及对应的对象);因此不能在这三者中构建 sqlite_orm::where() 对象。

如果你这么做了,有些地方会直接编译出错(万幸),有些则将千万运行时异常(惨),有些则只是出现错误的(往往是你意料之外的)结果——包括误删除、误修改……

4.2 条件表达

常用的,可用于构建条件的(C++)操作符,对及 sqlite_orm 提供的函数有:

操作符 对应函数 说明
== is_equal 相等判断
!= is_not_equal 不等判断
> greater_than 大于判断
< less_than 小于判断
>= greater_or_equal 大于或等于判断
<= less_or_equal 小于或等于判断

另外还有 !、&&、|| 以及对应的 C++ “代替操作符” not、and、or,可用于组合多个条件,后者因与 SQL 原生逻辑操作符相同,因而更推荐在 ORM 中使用。

注意! 当使用(C++)操作符,并且操作数中包含(至少)一个字段时,需使用 sqlite_orm::c(成员数据偏移) 来包装。没有用到操作符,仅用到函数时,则不需要。

比如,判断某 FamilyMember 的 id 是否大于 5 ,可以使用操作符构建 where 子句,也可以使用对应的函数构建:

using namespace sqlite_orm; // 使用操作符: where(c(&FamilyMember.id) > 5); // 需要 c(...) // 使用函数: where(greater_than(&FamilyMember, 5); // 不需要,也不能加 c(...)

更多条件应用,以及 order_by 与 limit 、offset 的用法,见视频:

5. CRUD 全套示例

CRUD 指数据库的 create、read、update、delete 等日常操作,同时也用于暗指程序员枯燥无味的日常编程工作。

5.1 insert 的 3 种写法

  • 1 插入整个对象
// 方法一:使用对象,插入对象除主键外的所有已映射字段 FamilyMember g; g.role = "爷爷"; g.tel = "19988882222"; g.birthday = "1950-10-01"; g.avatar = "老骥伏枥志在千里.png"; storage.insert(g);
  • 2 插入对象的指定字段
// 方法二:使用对象,但明确仅插入指定字段的值 FamilyMember u; u.role = "叔叔"; u.birthday = "1988-01-21"; // 只插入 role 和 birthday 字段,其余字段由数据库设定默认值(包括置 NULL) storage.insert(u, sqlite_orm::columns ( &FamilyMember::role , &FamilyMember::birthday ));
    1. 使用 tuple 插入指定字段
// 方法三:脱离对象,插入指定字段和值 storage.insert(sqlite_orm::into<FamilyMember>() // 使用到 into<> // 字段 , sqlite_orm::columns( &FamilyMember::role , &FamilyMember::birthday , &FamilyMember::tel) // 值 , sqlite_orm::values(std::make_tuple("小姨" , "1986-12-13", "13309091122")) );

5.2 update/update_all

  • update

update 方法用于精准修改指定主键的数据记录,通常该记录(对象)来自前面的查询操作,比如 get 。

namespace orm = sqlite_orm; auto records = storage.get_all<FamilyMember>(orm::where(orm::c(&FamilyMember::role)=="奶奶")); assert(! records.empty()); auto grandma = records.front(); grandma.avatar = "花开富贵.png"; grandma.tel = "19988887771"; grandma.is_admin = true; storage.update<FamilyMember>(grandma);
  • update_all

update_all 则用于修改满足给定条件的所有记录。

storage.update_all ( orm::set(orm::c(&FamilyMember::avatar) = "admin.png" , orm::c(&FamilyMember::tel) = "19912345678")         , orm::where(&FamilyMember::is_admin);

注意其 set 函数中的 “赋值” 操作( = ),也是操作符,因此涉及的字段(左值),也需使用 sqlite_orm::c 函数来包装。

5.3 remove/remove_all

因为 “delete” 是 C++ 关键字,所以 sqlite_orm 使用 “remove”。

  • remove

虽然 remove 也是一个绑定对象的操作,但其入参仅需指定主键值(从而删除对应的记录)。

storage.remove<FamilyMember>(g.id);
  • remove_all
namespace orm = sqlite_orm; storage.remove_all<FamilyMember>(orm::where(orm::in(&FamilyMember::role, {"叔叔","姑姑"})));

6. 事务

6.1 关键点

  • 切记:同一个事务内,所有CRUD 操作要么都成功,要么都失败;
  • 切记:事务中的操作 指 CRUD 操作,修改表结构,删除表等 SQL 语句通常不支持事务;
  • 切记:sqlite3 不支持事务嵌套

6.2 事务写法的三种风格

  • C 风格
storage.begin_transcation(); // 开始事务 /* 此处进行一系列须共进共退的操作 */ if (xxx 出错) { storage.rollback();// 回滚 return false; } if (yyy 出错) { storage.rollback();// 回滚 return false; } try { ... } catch(...) { storage.rollback();// 回滚 return false; } storage.commit(); return true;
  • lambda 风格
storage.transcation ([&] () mutable { ... return true? false? } );

巧妙之处:将调用 commit() 或 rollback() 函数,转换为写一个函数 (lambda 相当于一个匿名函数)的 return 值。前者啰嗦、易忘,且得不到编译器检查,后者直观、简短、忘记写,则(大多数)编译器会给出警告。

  • 哨兵风格

在 C++ 代码中,返回出错码和抛出异常这两种错误处理方式,通常混杂存在,哨兵风格是 C++ 用于对付忘记在错误处理中调用必要操作(典型的如回收资源)的传统技能(惯用法)。sqlite_orm 也给出了良好的支持。

try { auto guard = storage.transcation_guard(); /* 各种操作,各种可能出错 */ guard.commit(); // 提交仍然需要手动处理,但通常只需一处 } catch(...) { }

更多事务案例,请看视频:

7. 完整代码

上半部分完整代码如下。

  • CMakeLists.txt
cmake_minimum_required(VERSION 3.15) project(helloSQLiteORM) SET(CMAKE_CXX_STANDARD 17) FIND_LIBRARY(LIBSQLite3 sqlite3 PATHS "C:/msys64/ucrt64/lib") ADD_EXECUTABLE(${PROJECT_NAME} main.cpp) TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${LIBSQLite3}) TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PRIVATE "C:/msys64/ucrt64/include")
  • main.cpp
#include <cassert> #include <cstdlib> #include <iostream> #include <string> #include <optional> #include <exception> #include <sqlite_orm/sqlite_orm.h> // 家庭成员 struct FamilyMember { int id = -1; // 主键,在表中自增长 std::string role; // 角色:爷爷、奶奶…… 不可重复 std::optional<std::string> tel; // 电话号码,可为空 std::string birthday; // 出生日期,在表中不可为空 std::string avatar = "0.png"; // 头像文件名,默认 0.png bool is_admin = false; // 是否为管理员,默认为 false }; void line(int len = 80, char c = '-') { std::cout << "\n" << std::string(len, c) << std::endl; } void print(FamilyMember const& m) { std::cout << "#" << m.id << ' ' << std::setw(10) << std::left << m.role; std::cout << std::setw(16) << std::left << (!m.is_admin? "普通用户" : "管理员") << '\t'; std::cout << m.birthday << '\t' << std::setw(11) << (m.tel ? *m.tel : "~") << '\t' << m.avatar; line(); } namespace orm = sqlite_orm; // 命名空间别名 // 建立映射 auto create_storage(std::string db_file) { return orm::make_storage(db_file, orm::make_table("family_members" , orm::make_column("id", &FamilyMember::id, orm::primary_key().autoincrement()) , orm::make_column("role", &FamilyMember::role, orm::unique()) , orm::make_column("tel", &FamilyMember::tel, orm::null()) , orm::make_column("birthday", &FamilyMember::birthday, orm::not_null()) , orm::make_column("avatar", &FamilyMember::avatar, orm::default_value("0.png")) , orm::make_column("is_admin", &FamilyMember::is_admin, orm::default_value(false)) ) ); } using Storage = decltype(create_storage("")); // Storage 类型 // 插入初始数据 void insert_members(Storage& storage) { FamilyMember members [] = { {.role = "爸爸", .tel = "13900001111", .birthday = "1988-04-26"} , {.role = "妈妈", .tel = "13900002222" , .birthday = "1990-12-16", .is_admin = true} // 妈妈是管理员 , {.role = "大宝", .birthday = "2020-05-01", .avatar = "rabbit.png"} , {.role = "二宝", .birthday = "2024-02-08", .avatar = "baby.png"} , {.role = "二大爷", .birthday = "2025-09-02", .avatar = "robot.jpg"} }; for (auto& m : members) { m.id = storage.insert(m); } std::cout << "原始数据已插入:\n"; for (auto const& m : members) { std::cout << storage.dump(m) << std::endl; } } #define i_am_here std::cout << "\n[" << __FUNCTION__ << "]\n"; // 精准查询单一对象 void get_member(Storage& storage) { i_am_here; std::cout << "请输入成员ID:"; int id ; std::cin >> id; auto member = storage.get<FamilyMember>(id); print(member); } // 精准查询单一对象变体一:使用 optional void get_member_optional(Storage& storage) { i_am_here; std::cout << "请输入成员ID:"; int id; std::cin >> id; auto member = storage.get_optional<FamilyMember>(id); if (member) { print(*member); } else { std::cout << "没有找到 ID 为 " << id << " 的成员!" << std::endl; } } // 精准查询单一对象变体二:使用 unique_ptr void get_member_pointer(Storage& storage) { i_am_here; std::cout << "请输入成员ID:"; int id; std::cin >> id; auto member = storage.get_pointer<FamilyMember>(id); if (member) { print(*member); } else { std::cout << "没有找到 ID 为 " << id << " 的成员!" << std::endl; } } // 获取所有记录,并转为对象集合(vector) void get_all_members(Storage& storage) { i_am_here; auto members = storage.get_all<FamilyMember>(); for (auto const& m : members) { print(m); } } // 通过迭代,获取每一行记录并转为对象 void iterate_all_members(Storage& storage) { i_am_here; for (auto const &m : storage.iterate<FamilyMember>()) { print(m); } } // 选择部分(或全部)字段 void select_fields(Storage& storage) { i_am_here; auto rows = storage.select(orm::columns(&FamilyMember::id , &FamilyMember::role, &FamilyMember::tel)); std::cout << "ID\t角色\t电话号码\n"; for (auto const& row : rows) { std::string tel = std::get<2>(row) ? *std::get<2>(row) : "~"; std::cout << std::get<0>(row) << '\t' << std::get<1>(row) << '\t' << tel; line(40); } } // 选择单一字段 void select_one_field(Storage& storage) { i_am_here; auto roles = storage.select(&FamilyMember::role); for (auto const& role : roles) { std::cout << role << '\t'; } } // 带条件的 get_all void get_all_with_condition(Storage& storage) { i_am_here; auto conditions_desc = [] (char const* desc) { std::cout << "\n> " << desc; line(30, '='); }; auto print_list = [] (auto const& members) { for (auto const& m : members) print(m); }; { conditions_desc("管理员"); auto admins = storage.get_all<FamilyMember>(orm::where( orm::c(&FamilyMember::is_admin) == true)); print_list(admins); } { conditions_desc("普通用户"); auto users = storage.get_all<FamilyMember>(orm::where( not orm::c(&FamilyMember::is_admin))); print_list(users); } { conditions_desc("有电话的成员"); auto with_tels = storage.get_all<FamilyMember>(orm::where( orm::is_not_null(&FamilyMember::tel))); print_list(with_tels); } { conditions_desc("没有电话的成员"); auto without_tels = storage.get_all<FamilyMember>(orm::where( orm::is_null(&FamilyMember::tel))); print_list(without_tels); } { conditions_desc("年龄大于等于30岁的用户"); auto elder30 = storage.get_all<FamilyMember>( orm::where( // 以下 SQL 内容严重依赖于 sqlite 的功能特性 orm::c(&FamilyMember::birthday) <= orm::datetime("now", "-30 years") )); print_list(elder30); } { // 条件组合: conditions_desc("年龄大于等于30岁的普通用户"); auto elder30_no_admin = storage.get_all<FamilyMember>( orm::where( (orm::c(&FamilyMember::birthday) <= orm::datetime("now", "-30 years")) and (not orm::c(&FamilyMember::is_admin)) )); print_list(elder30_no_admin); } { // LIKE 模糊查询 conditions_desc("角色名称以 '宝' 字结尾的成员"); auto bao_members = storage.get_all<FamilyMember>( orm::where(orm::like(&FamilyMember::role, "%宝")) ); print_list(bao_members); } { conditions_desc("指定ID集合的成员"); auto some_members = storage.get_all<FamilyMember>( orm::where(orm::in(&FamilyMember::id, {1, 4, 5})) ); print_list(some_members); } } // 带条件的 select void select_fields_with_condition(Storage& storage) { i_am_here; auto members_base = storage.select(orm::columns(&FamilyMember::id , &FamilyMember::role, &FamilyMember::birthday) , orm::where(not orm::c(&FamilyMember::is_admin))); std::cout << "普通用户基本信息:\n"; for (auto const& mb : members_base) { std::cout << std::get<0>(mb) << '\t' << std::get<1>(mb) << '\t' << std::get<2>(mb) << "\n"; } } // order 查询结果集排序 void order_members(Storage& storage) { i_am_here; auto members = storage.get_all<FamilyMember>(orm::where( not orm::c(&FamilyMember::is_admin)) // end where , orm::order_by(&FamilyMember::birthday).desc() ); std::cout << "按生日降序排列:\n"; for(auto const& m : members) { print(m); } } // 偏移与限行 void limit_offset_members(Storage& storage) { i_am_here; int offset = 1; // 从 4 开始 int limit = 3; // 4,3,2 auto members = storage.get_all<FamilyMember>( orm::order_by(&FamilyMember::id).desc() , orm::limit(limit, orm::offset(offset))); // 等效 limit(offset, limit) std::cout << "按 ID 降序排列,跳过 " << offset << " 条,最多取 " << limit << " 条:\n"; for (auto const& m : members) { print(m); } } // 三种插入单一记录的 insert 用法 void insert_3_ways(Storage& storage, std::string grandpa_role , std::string uncle_role, std::string aunt_role) { // 方法一:使用整个对象 FamilyMember grandpa; grandpa.role = grandpa_role; grandpa.tel = "19988887777"; grandpa.birthday = "1950-10-01"; grandpa.avatar = "老骥伏枥志在千里.png"; storage.insert(grandpa); // 方法二:使用对象,但仅插入部分字段(未指定字段,使用表中的设定的默认值) FamilyMember uncle; uncle.role = uncle_role; uncle.birthday = "1988-01-21"; // 注意 :只能插入非主键数据 storage.insert(uncle, orm::columns(&FamilyMember::role, &FamilyMember::birthday)); // 方法三:脱离对象,仅指定字段和值 storage.insert(orm::into<FamilyMember>() , orm::columns(&FamilyMember::role , &FamilyMember::birthday, &FamilyMember::tel) //end columns , orm::values(std::make_tuple(aunt_role, "1984-12-12", "13999998888"))); auto members = storage.get_all<FamilyMember>(); std::cout << "添加三位成员之后:\n"; for(auto const& m : members) { print(m); } } // CRUD 一条龙服务 void CRUD_operations(Storage& storage) { i_am_here; insert_3_ways(storage, "爷爷", "叔叔", "姑姑"); auto r = storage.get_all<FamilyMember>(orm::where( orm::c(&FamilyMember::role) == "爷爷")); assert(!r.empty()); auto title = [](char const* title) { std::cout << "\n>" << title << ":\n"; }; title("爷爷成员修改之前"); auto g = r.front(); print(g); g.avatar = "雄鹰展翅.jpeg"; g.tel = "19900001111"; g.is_admin = true; // 爷爷成为管理员 // 开始提交修改: storage.update<FamilyMember>(g); // 再 get 回来: g = storage.get<FamilyMember>(g.id); title("爷爷成员修改之后"); print(g); // 批量 update: storage.update_all(orm::set(orm::c(&FamilyMember::avatar) = "admin.png") , orm::where(&FamilyMember::is_admin)); title("统一修改管理员头像之后"); auto admins = storage.get_all<FamilyMember>(orm::where(&FamilyMember::is_admin)); for (auto const& a : admins) { print(a); } // 删除“爷爷” storage.remove<FamilyMember> (g.id); r = storage.get_all<FamilyMember>(orm::where( orm::c(&FamilyMember::role) == "爷爷")); if (r.empty()) { std::cout << "爷爷已经走了……" << std::endl; } // 删除 叔叔和姑姑 storage.remove_all<FamilyMember>(orm::where( orm::in(&FamilyMember::role, {"叔叔", "姑姑"}))); title("删除爷爷、叔叔和姑姑之后"); auto members = storage.get_all<FamilyMember>(); for (auto const& m : members) { print(m); } } // 事务 - 使用 lambda void transcation_lambda(Storage& storage, bool commit) { i_am_here; std::cout << "示范事务" << (commit? "提交" : "回滚") << "的效果:\n"; bool commited = storage.transaction([&storage, commit]() mutable { // 事务内入的操作 insert_3_ways(storage, "外公", "舅舅", "姨妈"); /* 通常这里一堆其它操作,有可能成功,也有可能失败 */ return commit; }); std::cout << "事务已" << (commited? "提交" : "回滚") << "\n"; auto members = storage.get_all<FamilyMember>(); for (auto const& m : members) { print(m); } } // 事务 - 使用哨兵 void transcation_guard(Storage& storage) { i_am_here; try { auto guard = storage.transaction_guard(); insert_3_ways(storage, "奶奶", "伯伯", "小姨"); // 故意重复插入 “奶奶” : storage.insert(orm::into<FamilyMember>() , orm::columns(&FamilyMember::role, &FamilyMember::birthday) , orm::values(std::make_tuple("奶奶", "1958-09-01"))); guard.commit(); } catch(std::exception const& e) { std::cout << "操作过程发生异常:" << e.what() << std::endl; } std::cout << "> 因异常而自动回滚的成员列表:\n"; for (auto const& m : storage.iterate<FamilyMember>()) { print(m); } } void its_time() { auto storage = sqlite_orm::make_storage(""); storage.sync_schema(); auto now = storage.select(sqlite_orm::datetime("now", "localtime")).front(); std::cout << "现在是 " << now << ", 是时候学习 sqlite_orm 了!" << std::endl; } int main() { std::system("chcp 65001 >nul"); // windows 下使用 UTF-8 编码 its_time(); auto storage = create_storage(":memory:"); // 使用内存数据库 storage.sync_schema(); try { insert_members(storage); // 演示1: 精准查询单一对象 get_member(storage); // 演示2: 精准查询单一对象变体一:使用 optional get_member_optional(storage); // 演示3: 精准查询单一对象变体二:使用 unique_ptr get_member_pointer(storage); // 演示4 :get_all get_all_members(storage); // 演示5: iterate 返回 iterate_all_members(storage); // 演示6: select 部分字段 select_fields(storage); // 演示7: select 单一字段 select_one_field(storage); // 演示 8 : get_all 带条件 get_all_with_condition(storage); // 演示 9 : 带条件的 select select_fields_with_condition(storage); // 演示 10: 查询结果排序 order_members(storage); // 演示 11 : 结果集限行数与起始偏移 limit_offset_members(storage); // 演示 12: 增、改、查、删除一条龙: CRUD_operations(storage); // 演示 13 : 事务操作-lambda transcation_lambda(storage, false); transcation_lambda(storage, true); // 演示 14 : 事务操作-哨兵模式 transcation_guard(storage); } catch (std::exception& e) { std::cerr << "发生异常:" << e.what() << std::endl; } }