线程安全问题
SQLite3 在编译的时候可以通过添加定义来控制整个库的线程安全问题。
当 SQLITE_THREADSAFE 这个宏为 0 时,整个库是线程不安全的。
当 SQLITE_THREADSAFE 这个宏为 1 时,整个库是线程安全的。
当 SQLITE_THREADSAFE 这个宏为 2 时,整个库是线程安全的,但是同一个 sqlite3* db 不能被多个线程同时使用。
当编译的时候 SQLITE_THREADSAFE 为 1 或者 2 时,可以通过 sqlite3_config() 这个接口来更改线程安全模式:
SQLITE_CONFIG_SINGLETHREAD 单线程
SQLITE_CONFIG_MULTITHREAD 多线程
SQLITE_CONFIG_SERIALIZED 串行化
面向对象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <functional> #include <string> #include <string_view> #include <unordered_map>
class SQLite3 { public: using TableRow = std::unordered_map<std::string, std::string>; using ExecCallback = std::function<bool(TableRow)>;
public: SQLite3(const std::string_view& db_name); ~SQLite3();
int close();
void exec(const std::string_view& sql); void exec(const std::string_view& sql, ExecCallback callback);
private: std::string db_name_; sqlite3* db_{nullptr}; };
|
我设置了两个成员变量,一个是数据库连接,另一个是打开的数据库名称。
1 2
| std::string db_name_; sqlite3* db_{nullptr};
|
数据库的开启
首先是类的构造函数,主要是打开数据库连接,并在出错时抛出异常信息。
1 2 3 4 5 6
| SQLite3::SQLite3(const std::string_view& db_name) : db_name_(db_name) { auto errcode = sqlite3_open(db_name.data(), &db_); if (errcode != SQLITE_OK) { } }
|
数据库的关闭
然后是析构和关闭数据库连接。
析构函数因为不能抛出异常,所以我使用了 sqlite3_close_v2 来关闭连接。
主动 close 则是使用 sqlite3_close 接口,并返回错误代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| SQLite3::~SQLite3() { if (db_) { sqlite3_close_v2(db_); db_ = nullptr; } }
int SQLite3::close() { auto errcode = sqlite3_close(db_); if (errcode == SQLITE_OK) { db_ = nullptr; } return errcode; }
|
执行 SQL 语句
无回调函数
无回调函数的操作,只需要执行 SQL 语句,并抛出或者处理执行失败的情况即可。
1 2 3 4 5 6 7 8 9 10 11
| void SQLite3::exec(const std::string_view& sql) { char* errstr = nullptr; int errcode = sqlite3_exec(db_, sql.data(), nullptr, nullptr, &errstr);
if (errcode != SQLITE_OK) { std::string msg = errstr ? errstr : "Unknown error"; sqlite3_free(errstr); throw std::runtime_error( std::format("Failed to execute SQL: {}\nError SQL: {}", msg, sql)); } }
|
有回调函数
当执行一些带有结果的 SQL 语句时,就需要回调函数去处理结果。
我定义了两个类型用来封装 sqlite3_exec 中的回调函数。
ExecCallback 对象的返回值为 bool 类型,用来指示回调函数是否成功执行。
并且 ExecCallback 对象拥有一个参数 TableRow,这是一个哈希表,存储该行的每一个列数据的 name 和 value 的键值对。
1 2
| using TableRow = std::unordered_map<std::string, std::string>; using ExecCallback = std::function<bool(TableRow)>;
|
我通过 void* context 传递 ExecCallback 对象。
当回调函数触发时,填充 TableRow row 对象的数据,并以此为参数调用 ExecCallback 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void SQLite3::exec(const std::string_view& sql, ExecCallback callback) { auto c_callback = [](void* context, int col_size, char** col_values, char** col_names) -> int { auto* cb = static_cast<ExecCallback*>(context); TableRow row; for (int i = 0; i < col_size; i++) { std::string col_name = col_names[i]; std::string value = col_values[i] ? col_values[i] : ""; row[col_name] = value; } bool success = (*cb)(row); return success ? 0 : 1; };
char* errstr = nullptr; int errcode = sqlite3_exec(db_, sql.data(), c_callback, &callback, &errstr); if (errcode != SQLITE_OK) { std::string msg = errstr ? errstr : "Unknown error"; sqlite3_free(errstr); throw std::runtime_error(std::format( "Failed to execute SQL with callback: {}\nError SQL: {}", msg, sql)); } }
|
测试一下这个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int main() { SQLite3 db("test.db");
try { db.exec("create table if not exists users (id integer primary key autoincrement, name text);"); db.exec("insert into users (id, name) values (1, 'Alice');"); db.exec("insert into users (id, name) values (2, 'Bob');"); db.exec("insert into users (name) values ('Charlie');"); db.exec("select * from users;", [](SQLite3::TableRow row) { std::cout << "User ID: " << row["id"] << ", Name: " << row["name"] << '\n'; return true; }); } catch (const std::exception& e) { std::cerr << e.what() << '\n'; }
return 0; }
|
运行上述 main 函数后,获得输出如下:
1 2 3
| User ID: 1, Name: Alice User ID: 2, Name: Bob User ID: 3, Name: Charlie
|
再次运行该代码,输出如下:
1 2
| Failed to execute SQL: UNIQUE constraint failed: users.id Error SQL: insert into users (id, name) values (1, 'Alice');
|
测试完毕,这样一个初步的面向对象封装就完成了。