您的位置:首页 > 编程语言 > C语言/C++

sqlite之我见--C/C++ API接口示例 --回调 事务

2016-03-25 15:03 495 查看
http://blog.csdn.net/wzzfeitian/article/details/7993686

在之前的两篇博文中,分别介绍了SQLITE的基础知识和操作,C/C++ 的一些常用API

sqlite之我见--简单介绍与基本操作

sqlite之我见--C/C++ API接口介绍

本文中,我会给大家用几个小程序示例SQLITE C/C++ API的使用。


1.我们看下最简单的sqlite程序,通过sqlite3_open, sqlite3_exec, sqlite3_close来实现一个简单的数据库操作。

[cpp] view
plain copy

/*

* File: sqlite_test.cpp

* Author: Carl

*

* Created on September 20, 2012, 3:28 PM

*/

#include <cstdlib>

#include <cstdio>

#include <sqlite3.h>

static int _sql_callback(void *notused, int argc, char **argv, char **szColName)

{

int i = 0;

printf("notused:0x%x, argc:%d\n", notused, argc);

for (i = 0; i < argc; i++)

{

printf("%s = %s\n", szColName[i], argv[i] == 0 ? "NULL" : argv[i]);

}

printf("\n");

return 0;

}

/*

*

*/

int main(int argc, char** argv)

{

const char *sSQL1 = "create table users(userid varchar(20) PRIMARY KEY, age int, birthday datetime);";

const char *sSQL2 = "insert into users values('wang', 20, '1989-5-4');";

const char *sSQL3 = "select * from users;";

sqlite3 *db = 0;

char *pErrMsg = 0;

int ret = 0;

//连接数据库

ret = sqlite3_open("./test.db", &db);

if (ret != SQLITE_OK)

{

fprintf(stderr, "无法打开数据库:%s\n", sqlite3_errmsg(db));

sqlite3_close(db);

return 1;

}

printf("数据库连接成功\n");

//执行建表SQL

ret = sqlite3_exec(db, sSQL1, _sql_callback, 0, &pErrMsg);

if (ret != SQLITE_OK)

{

fprintf(stderr, "SQL create error: %s\n", pErrMsg);

sqlite3_free(pErrMsg); //这个要的哦,要不然会内存泄露的哦!!!

sqlite3_close(db);

return 1;

}

printf("数据库建表成功!!\n");

//执行插入数据

ret = sqlite3_exec(db, sSQL2, _sql_callback, 0, &pErrMsg);

if (ret != SQLITE_OK)

{

fprintf(stderr, "SQL insert error: %s\n", pErrMsg);

sqlite3_free(pErrMsg); //这个要的哦,要不然会内存泄露的哦!!!

sqlite3_close(db);

return 1;

}

printf("数据库插入数据成功!\n");

//执行查询操作

ret = sqlite3_exec(db, sSQL3, _sql_callback, 0, &pErrMsg);

if (ret != SQLITE_OK)

{

fprintf(stderr, "SQL error: %s\n", pErrMsg);

sqlite3_free(pErrMsg);

sqlite3_close(db);

return 1;

}

printf("数据库查询成功!!\n");

//关闭数据库

sqlite3_close(db);

db = 0;

return 0;

}

运行结果如下结果:

[plain] view
plain copy

[carl@Fedora sqlite]$ g++ sqlite_test.cpp -lsqlite3

[carl@Fedora sqlite]$ ./a.out

数据库连接成功

数据库建表成功!!

数据库插入数据成功!

notused:0x0, argc:3

userid = wang

age = 20

birthday = 1989-5-4

数据库查询成功!!

[carl@Fedora sqlite]$


2. 我们再看一个在sqlite上是有事务来实现原子操作的的例子,

代码如下:

[cpp] view
plain copy

/*

* File: sqlite_test.cpp

* Author: Carl

*

* Created on 2012年9月22日, 上午7:50

*/

#include <cstdlib>

#include <cstdio>

#include <sqlite3.h>

static int _sql_callback(void *notused, int argc, char **argv, char **szColName)

{

int i = 0;

printf("notused:0x%x, argc:%d\n", notused, argc);

for (i = 0; i < argc; i++)

{

printf("%s = %s\n", szColName[i], argv[i] == 0 ? "NULL" : argv[i]);

}

printf("\n");

return 0;

}

/*

*

*/

int main(int argc, char** argv)

{

const char *sSQL1 = "create table test_for_cpp (id int, name varchar(10), age int);";

char sql[100] = {0};

sqlite3 *db = NULL;

char *pErrMsg = NULL;

int ret = 0;

bool is_success = true;

const char *sSQL3 = "select * from test_for_cpp;";

ret = sqlite3_open("./test.db", &db);

if (SQLITE_OK != ret)

{

fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));

sqlite3_close(db);

return 1;

}

printf("数据库连接成功\n");

ret = sqlite3_exec(db, sSQL1, NULL, 0, &pErrMsg);

if (SQLITE_OK != ret)

{

fprintf(stderr, "SQL create error: %s\n", pErrMsg);

sqlite3_free(pErrMsg);

sqlite3_close(db);

return 1;

}

printf("数据库建表成功!!\n");

sqlite3_exec(db, "begin;", _sql_callback, 0, &pErrMsg); //开启事务

if (SQLITE_OK != ret)

{

fprintf(stderr, "SQL begin error: %s\n", pErrMsg);

sqlite3_free(pErrMsg);

sqlite3_close(db);

return 1;

}

printf("数据库开启事务成功!!\n");

for (int i = 0; i < 10; i++)

{

sprintf(sql, "insert into test_for_cpp(id, name, age) values(%d, \"%s\", %d);", i, "Carl", i);

ret = sqlite3_exec(db, sql, _sql_callback, 0, &pErrMsg);

if (SQLITE_OK != ret)

{

is_success = false;

fprintf(stderr, "for %d time error: %s\n", i, pErrMsg);

sqlite3_free(pErrMsg);

break;

}

}

if (is_success)

{

sqlite3_exec(db, "commit;", 0, 0, 0);

printf("数据库插入数据成功!\n");

}

else

{

sqlite3_exec(db, "rollback;", 0, 0, 0);

printf("数据库插入数据失败!\n");

}

ret = sqlite3_exec(db, sSQL3, _sql_callback, 0, &pErrMsg);

if (SQLITE_OK != ret)

{

fprintf(stderr, "SQL ERROR: %s\n", pErrMsg);

sqlite3_free(pErrMsg);

sqlite3_close(db);

return 1;

}

printf("数据库查询成功!!\n");

sqlite3_close(db);

db = 0;

return 0;

}

运行结果:

[plain] view
plain copy

[carl@Fedora sqlite]$ g++ sqlite_test.cpp -lsqlite3

[carl@Fedora sqlite]$ ./a.out

数据库连接成功

数据库建表成功!!

数据库开启事务成功!!

数据库插入数据成功!

notused:0x0, argc:3

id = 0

name = Carl

age = 0

notused:0x0, argc:3

id = 1

name = Carl

age = 1

notused:0x0, argc:3

id = 2

name = Carl

age = 2

notused:0x0, argc:3

id = 3

name = Carl

age = 3

notused:0x0, argc:3

id = 4

name = Carl

age = 4

notused:0x0, argc:3

id = 5

name = Carl

age = 5

notused:0x0, argc:3

id = 6

name = Carl

age = 6

notused:0x0, argc:3

id = 7

name = Carl

age = 7

notused:0x0, argc:3

id = 8

name = Carl

age = 8

notused:0x0, argc:3

id = 9

name = Carl

age = 9

数据库查询成功!!

[carl@Fedora sqlite]$


3. 我们接着看一下如何能够更好的使用语句参数来操作sqlite数据库,用sqlite3_prepare_v2, sqlite3_bind_*, sqlite3_step, sqlite3_column_*等接口来实现对数据库的操作。

代码如下:里面的注释,有兴趣的可以试着打开试一下,但要记得注释掉相关的重复功能的语句哦。

[cpp] view
plain copy

/*

* File: sqlite_test2.cpp

* Author: Carl

*

* Created on September 21, 2012, 3:12 PM

*/

#include <cstdlib>

#include <cstdio>

#include <cstring>

#include <sqlite3.h>

static int _sql_callback(void *notused, int argc, char **argv, char **szColName)

{

int i = 0;

printf("notused:0x%x, argc:%d\n", notused, argc);

for (i = 0; i < argc; i++)

{

printf("%s = %s\n", szColName[i], argv[i] == 0 ? "NULL" : argv[i]);

}

printf("\n");

return 0;

}

/*

*

*/

int main(int argc, char** argv)

{

sqlite3 *conn = NULL;

sqlite3_stmt *stmt = NULL;

char *err_msg = NULL;

int ret = 0;

char col_types[][10] = {"", "Interger", "Float", "Text", "Blob", "NULL"};

ret = sqlite3_open("./test.db", &conn);

if (SQLITE_OK != ret)

{

fprintf(stderr, "sqlite open err, %d\n", ret);

return 1;

}

printf("打开数据库成功!!!\n");

// ret = sqlite3_prepare_v2(conn, "SELECT * FROM [test_for_cpp] WHERE [name]==:name", -1, &stmt, (const char **)&err_msg);

ret = sqlite3_prepare_v2(conn, "SELECT * FROM [test_for_cpp] WHERE [name]==?2", -1, &stmt, (const char **)&err_msg);

if (SQLITE_OK != ret)

{

fprintf(stderr, "sqlite prepare error: %s\n", err_msg);

sqlite3_free(err_msg);

sqlite3_close(conn);

return 1;

}

// printf("数据库语句对象编译成功!!!%d\n", sqlite3_bind_parameter_index(stmt, ":name"));

printf("数据库语句对象编译成功!!!\n");

ret = sqlite3_bind_text(stmt, 2, "Carl", 4, SQLITE_STATIC);

if (SQLITE_OK != ret)

{

fprintf(stderr, "sqlite bind error: %d\n", ret);

sqlite3_close(conn);

return 1;

}

printf("数据库语句对象bind成功!!!\n");

while (ret = sqlite3_step(stmt), ret == SQLITE_ROW)

{

int col_count = sqlite3_column_count(stmt); //结果集中列的数量

printf("列数:%d\t", col_count);

const char *col_0_name = sqlite3_column_name(stmt, 0); //获取列名

printf("列名:%s\t", col_0_name);

int id = sqlite3_column_int(stmt, 0);

printf("id值:%d\t", id);

int id_type = sqlite3_column_type(stmt, 0); //获取列数据类型

printf("id类型:%d\t", id_type);

const char *col_2_name = sqlite3_column_name(stmt, 2);

int age = sqlite3_column_int(stmt, 2);

int age_type = sqlite3_column_type(stmt, 2);

const char *col_1_name = sqlite3_column_name(stmt, 1);

char name[80];

strncpy(name, (const char *)sqlite3_column_text(stmt, 1), 80);

int name_type = sqlite3_column_type(stmt, 1);

//打印结果

printf("col_count: %d, %s = %d(%s), %s = %s(%s), %s = %d(%s)\n",

col_count, col_0_name, id, col_types[id_type], col_1_name, name,

col_types[name_type], col_2_name, age, col_types[age_type]);

}

fprintf(stderr, "sqlite step exit with %d\n", ret);

sqlite3_finalize(stmt);

sqlite3_close(conn);

return 0;

}

运行结果如下:

[plain] view
plain copy

[carl@Fedora sqlite]$ g++ sqlite_test2.cpp -lsqlite3

[carl@Fedora sqlite]$ ./a.out

打开数据库成功!!!

数据库语句对象编译成功!!!

数据库语句对象bind成功!!!

列数:3 列名:id id值:0 id类型:1 col_count: 3, id = 0(Interger), name = Carl(Text), age = 0(Interger)

列数:3 列名:id id值:1 id类型:1 col_count: 3, id = 1(Interger), name = Carl(Text), age = 1(Interger)

列数:3 列名:id id值:2 id类型:1 col_count: 3, id = 2(Interger), name = Carl(Text), age = 2(Interger)

列数:3 列名:id id值:3 id类型:1 col_count: 3, id = 3(Interger), name = Carl(Text), age = 3(Interger)

列数:3 列名:id id值:4 id类型:1 col_count: 3, id = 4(Interger), name = Carl(Text), age = 4(Interger)

列数:3 列名:id id值:5 id类型:1 col_count: 3, id = 5(Interger), name = Carl(Text), age = 5(Interger)

列数:3 列名:id id值:6 id类型:1 col_count: 3, id = 6(Interger), name = Carl(Text), age = 6(Interger)

列数:3 列名:id id值:7 id类型:1 col_count: 3, id = 7(Interger), name = Carl(Text), age = 7(Interger)

列数:3 列名:id id值:8 id类型:1 col_count: 3, id = 8(Interger), name = Carl(Text), age = 8(Interger)

列数:3 列名:id id值:9 id类型:1 col_count: 3, id = 9(Interger), name = Carl(Text), age = 9(Interger)

sqlite step exit with 101 //101意思为SQLITE_DONE

[carl@Fedora sqlite]$

下面解释下为什么要学会这种能够细致控制sqlite的方法(即使用语句参数),主要有以下几个优点:

(1) 使用“语句参数”方式,具有更高的安全性,可以有效防止“SQL注入攻击”。 “SQL注入攻击”要想达到目的,就必须让attack value随着SQL命令字符串一起传送进SQL解析器。黑客如果在一条SQL命令字符串被送入到sqlite3_prepare函数之前,利用c字符串处理函数等途径将attack value注入其中,而在sqlite3_prepare函数之中进行解析(parse),就可以达到攻击目的。而使用“语句参数”方式,被传送到sqlite3_prepare函数的只是SQL命令字符串中的参数符号(如:“?”),而不是具体的值。在sqlite3_prepare函数执行之后,才会使用bind函数给参数符号绑定具体的值,这就可以避免attack
value随着SQL命令字符串一起在sqlite3_prepare函数中被解析,从而有效躲避“SQL注入攻击”。

(2)使用“语句参数”方式,可以更快的完成值替换。

(3)使用“语句参数”方式,更节省内存。原因是使用如snprintf函数,需要一个SQL命令模板,一块足够大的输出缓存,而且字符串处理函数需要工作内存(working memory),除此之外对于整形,浮点型,特别是BLOBs,经常会占用更多的空间。

水平有限,如果有朋友发现错误,欢迎留言交流。

转载请保留本文链接,如果觉得我的文章能帮到您,请顶一下。,谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: