使用数据库,但不使用 ORM —— 很正常。
使用数据库,但从没用过 sqlite3 ? —— 你有点奇怪。
使用 sqlite3 但不使用 ORM?—— 陷入深思……
我们分上下两节讲解 sqlite3-orm :
- 上半部分
- 简介、安装、快速感受
- ORM:建立映射
- 查询:get、get_all、iterate、select
- CRUD 全套
- 事务
- 下半部分
- 分组与聚合
- 联合查询
- 预处理语句
- 建库的更多配置
- 一点思考
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_pointer 或 get_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
));
-
- 使用 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;
}
}