转自:https://mp.weixin.qq.com/s/K2k8LpC8l5zcijaiiEPhPg
1.前言
从本文开始介绍怎样去使用SQlite的编程接口。当然在文章中会涉及到一些SQL语句的知识,默认各位都是了解并且熟练的,如不熟悉,还请各位朋友自行百度学习,或者参考其他的文章,本系列文章不再赘述,旨在学习SQlite的设计和实现。
2.快速使用SQlite编程
下面是一个简单的C程序,演示了如何使用SQLite 的C / C ++接口。数据库的名称由第一个参数给出,第二个参数是要对数据库执行的一个或多个SQL语句。主要函数调用注释如下:
sqlite3_open()打开数据库
sqlite3_exec()是对数据库执行SQL命令
sqlite3_close()关闭数据库链接。
有关几十个SQLite接口函数的介绍,请看下面章节。
01 #include <stdio.h>
02 #include <sqlite3.h>
03
04 static int callback(void *NotUsed, int argc, char **argv, char **azColName){
05 int i;
06 for(i=0; i<argc; i++){
07 printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
08 }
09 printf("\n");
10 return 0;
11 }
12
13 int main(int argc, char **argv){
14 sqlite3 *db;
15 char *zErrMsg = 0;
16 int rc;
17
18 if( argc!=3 ){
19 fprintf(stderr, "Usage: %s DATABASE SQL-STATEMENT\n", argv[0]);
20 return(1);
21 }
22 rc = sqlite3_open(argv[1], &db);
23 if( rc ){
24 fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
25 sqlite3_close(db);
26 return(1);
27 }
28 rc = sqlite3_exec(db, argv[2], callback, 0, &zErrMsg);
29 if( rc!=SQLITE_OK ){
30 fprintf(stderr, "SQL error: %s\n", zErrMsg);
31 sqlite3_free(zErrMsg);
32 }
33 sqlite3_close(db);
34 return 0;
35 }
3.SQLite C/C++接口简介
以下两个对象和八个方法构成了SQLite接口的基本元素:
-
sqlite3 →数据库连接对象。通过sqlite3_open()创建 ,并通过 sqlite3_close() 释放。
-
sqlite3_stmt →准备好的语句对象。通过sqlite3_prepare()创建 ,并通过 sqlite3_finalize()释放 。
-
sqlite3_open() →打开SQLite数据库的连接。sqlite3的构造函数。
-
sqlite3_prepare() →将SQL文本编译为字节代码,用于执行查询或更新数据库。sqlite3_stmt的构造函数。
-
sqlite3_bind() →将应用程序数据存储到 原始SQL的参数中。
-
sqlite3_step() →将 sqlite3_stmt前进到下一个结果行或完成。
-
sqlite3_column() →当前结果行用于在列值 sqlite3_stmt。
-
sqlite3_finalize() → sqlite3_stmt的析构函数。
-
sqlite3_close() → sqlite3的析构函数。
-
sqlite3_exec() →包装函数,它为一个或多个SQL语句的字符串执行 sqlite3_prepare(), sqlite3_step(), sqlite3_column()和sqlite3_finalize()。
4.核心接口和对象
SQL数据库引擎的主要任务是执行SQL语句。为此,开发人员需要两个对象:
-
该数据库连接对象:sqlite3的
-
将准备好的语句对象:sqlite3_stmt
严格地说,准备好的声明中不需要的对象,因为便利的包装接口,sqlite3_exec或 sqlite3_get_table,可以使用这些方便的包装封装和隐藏准备语句对象。然而需要充分理解准备好的语句才能充分利用SQLite的数据库,以下是核心接口的功能说明:
-
qlite3_open()
上面已经提到过这个函数了,它是操作SQLite数据库的入口函数。该函数返回的database_connection对象是很多其他SQLite APIs的句柄参数。注意,我们通过该函数既可以打开已经存在的数据库文件,也可以创建新的数据库文件。对于该函数返回的database_connection对象,我们可以在多个线程之间共享该对象的指针,以便完成和数据库相关的任意操作。然而在多线程情况下,我们更为推荐的使用方式是,为每个线程创建独立的database_connection对象。对于该函数还有一点也需要额外说明,我们没有必要为了访问多个数据库而创建多个数据库连接对象,因为通过SQLite自带的ATTACH命令可以在一个连接中方便的访问多个数据库。
-
sqlite3_prepare()
该函数将SQL文本转换为prepared_statement对象,并在函数执行后返回该对象的指针。事实上,该函数并不会评估参数指定SQL语句,它仅仅是将SQL文本初始化为待执行的状态。将每个SQL语句都视为一个小型计算机程序。sqlite3_prepare()的目的是将该程序编译为目标代码。在准备语句是目标代码。然后sqlite3_step()接口运行目标代码以获得结果。新应用程序应始终调用sqlite3_prepare_v2()而不是sqlite3_prepare()。保留较旧的sqlite3_prepare()以实现向后兼容性。但是sqlite3_prepare_v2()提供了更好的使用。
-
sqlite3_step()
该函数用于评估sqlite3_prepare函数返回的prepared_statement对象,在执行完该函数之后,prepared_statement对象的内部指针将指向其返回的结果集的第一行。如果打算进一步迭代其后的数据行,就需要不断的调用该函数,直到所有的数据行都遍历完毕。然而对于INSERT、UPDATE和DELETE等DML语句,该函数执行一次即可完成。
-
sqlite3_column()
该函数是从sqlite3_step()正在评估的预准备语句的结果集的当前行返回单个列。每次sqlite3_step()以新的结果集行停止时,可以多次调用此函数以查找该行中所有列的值。
如上所述,SQLite API中确实没有“sqlite3_column()”函数。相反,我们在这里所谓的“sqlite3_column()”是整个函数族的占位符,它从各种数据类型的结果集中返回一个值。此系列中还有一些函数返回结果的大小(如果是字符串或BLOB)和结果集中的列数。
sqlite3_column_blob()
sqlite3_column_bytes()
sqlite3_column_bytes16()
sqlite3_column_count()
sqlite3_column_double()
sqlite3_column_int()
sqlite3_column_int64()
sqlite3_column_text()
sqlite3_column_text16()
sqlite3_column_type()
sqlite3_column_value()
下面是使用sqlite3_step和sqlite3_column函数迭代结果集中每行数据的代码,注意这里作为示例代码简化了对字段类型的判断:
int rc = sqlite3_prepare_v2(pDb, sql, -1, &pStmt, NULL);
//获取列数目
int n_columns = sqlite3_column_count(pStmt);
do{
ret = sqlite3_step(stmt);
if (ret == SQLITE_ROW)
{
//处理每一列
for (i = 0; i < n_columns; i++)
{
/*获取列存储类型*/
type = sqlite3_column_type(stmt,i);
switch(type)
{
case SQLITE_INTEGER:
/*处理整型*/
sqlite3_column_int(stmt,i);
break;
case SQLITE_FLOAT:
/*处理浮点数*/
sqlite3_column_double(stmt,i);
break;
case SQLITE_TEXT:
/*处理字符串*/
sqlite3_column_text(stmt,i);
break;
case SQLITE_BLOB:
/*处理二进制*/
sqlite3_column_blob(stmt, i));
break;
case SQLITE_NULL:
/*处理空*/
}
}
}
else if (ret == SQLITE_DONE) //结束
{
break;
}
}while(true);
-
sqlite3_finalize()
此函数会释放先前调用sqlite3_prepare()创建的预准备语句。必须使用对此函数的调用来销毁每个预准备语句,以避免内存泄漏。
-
sqlite3_close()
此函数关闭先前通过调用sqlite3_open()打开的数据库连接。在关闭连接之前,应最终确定与连接关联的所有预准备语句已经被销毁。
-
sqlite3_bind()
和大多数关系型数据库一样,SQLite的SQL文本也支持变量绑定,以便减少SQL语句被动态解析的次数,从而提高数据查询和数据操作的效率。
SQLite通过prepare接口可以支持参数化的SQL语句,即带问号的SQL语句。比如查询语句select * from t where id=?,或者插入语句 insert into t(a,b,c) values(?,?,?)。通过参数化SQL,可以实现一次编译多次执行的目的,由于问号是没有意义的,因此需要调用sqlite3_bind_xxx接口来绑定具体的参数。主要有以下几类:
sqlite3_bind_int
sqlite3_bind_int64
sqlite3_bind_double
sqlite3_bind_text
sqlite3_bind_blob
sqlite3_bind_null
关于绑定参数这里提一点,对于sqlite3_bind_text和sqlite3_bind_blob接口,绑定参数占据的存储空间是否可以被SQLite重用。接口中通过最后一个参数指定,参数值可以为SQLITE_STATIC和SQLITE_TRANSIENT。
SQLITE_STATIC:通知bind函数,参数使用空间是常量,不会改变,sqlite内部无需拷贝副本。
SQLITE_TRANSIENT:通知bind函数,参数使用空间可能会改变,sqlite内部需要有自己的副本。
见下面例子:
//begin a transaction
if(sqlite3_exec(pdb, "begin", NULL, NULL, &errmsg) != SQLITE_OK)
{
错误处理
return ERROR;
}
sqlite3_prepare_v2(pdb, "insert into t1 values(?,?,?);", &stmt);
for (i = 0; i < n_rows; i++)
{
for (j = 0; j < n_columns; j++)
{
switch(type)
{
case SQLITE_INTEGER:
/*处理整型*/
sqlite3_bind_int()
break;
case SQLITE_FLOAT:
/*处理浮点型*/
sqlite3_bind_double()
break;
case SQLITE_TEXT:
/*处理字符串类型*/
sqlite3_bind_text()
break;
case SQLITE_BLOB:
/*处理二进制类型*/
sqlite3_bind_blob
break;
case SQLITE_NULL:
sqlite3_bind_null(stmt, index);
break;
}
}
sqlite3_step(stmt); //执行
sqlite3_reset(stmt); //将已编译的SQL语句恢复到初始状态,保留语句相关的资源
}
sqlite3_finalize(stmt); //结束语句,释放语句句柄
if(sqlite3_exec(pdb, "commit", NULL, NULL, &errmsg) != SQLITE_OK)
{
错误处理 return ERROR;
}
5.总结
好了,今天就写这么点东西吧。将最简单的数据库操作接口讲解了下。如果你只会这样使用SQLite,简直就是大材小用了,高级功能之后的文章再来讲。