acl 的 C++ 版本库(lib_acl_cpp.a)的 db 模块主要与数据库编程相关,通过这些模块库,开发者可以快速地写出支持数据库连接池的数据库应用程序,目前该 db 模块支持 mysql、sqlite 数据库。本文将以 mysql 应用为例讲述如何使用这些 API 接口编程数据库应用。
在 lib_acl_cpp/include/acl_cpp/db 目录下,可以看到主要分三个部分:数据库操作句柄类(db_handle,db_mysql,db_sqlite)、数据库连接池类(db_pool,mysql_pool,sqlite_pool)及数据库服务类(db_service,db_service_mysql,db_service_sqlite,这些类主要用在阻塞非阻塞结合的应用中,如:MFC界面过程与数据库过程的结合,非阻塞 IO 过程与数据库过程结合)。
一、数据库操作句柄
下图显示了数据库句柄的类继承关系:db_handle 为基础类,db_mysql/db_sqlite 类均继承于 db_handle 类。
在 db_mysql.hpp/db_sqlite.hpp 两个头文件中可以看出,这两个子类仅是实现了基础类 db_handle 的一些虚函数而已,大量关于数据的操作函数都集中于 db_handle.hpp 头文件中,下图为 db_handle 类的功能协作图(其中的 db_rows/db_row 两个类为数据库查询结果类):
下面给出了一个简单的数据库查询示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
|
static void tbl_select(acl::db_handle& db) { const char* sql = "select * from group_tbl where" " group_name='test_name' and uvip_tbl='test'"; if (db.sql_select(sql) == false) { printf("select sql: %s error\r\n", sql); return; } printf("\r\n---------------------------------------------------\r\n"); const acl::db_rows* result = db.get_result(); if (result) { const std::vector<acl::db_row*>& rows = result->get_rows(); for (size_t i = 0; i < rows.size(); i++) { const acl::db_row* row = rows[i]; for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } } for (size_t i = 0; i < db.length(); i++) { const acl::db_row* row = db[i];
const char* ptr = (*row)["group_name"]; if (ptr == NULL) { printf("error, no group name\r\n"); continue; } printf("group_name=%s: ", ptr); for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } const std::vector<acl::db_row*>* rows = db.get_rows(); if (rows) { std::vector<acl::db_row*>::const_iterator cit = rows->begin(); for (; cit != rows->end(); cit++) { const acl::db_row* row = *cit; for (size_t j = 0; j < row->length(); j++) printf("%s, ", (*row)[j]); printf("\r\n"); } } db.free_result(); }
static acl::db_handle* open_mysql(void) { const char* dbaddr = "127.0.0.1:3306"; const char* dbname = "acl_test_db"; const char* dbuser = "acl_user", *dbpass = "111111"; acl::db_handle* db = new acl::db_mysql(dbaddr, dbname, dbuser, dbpass);
if (db->open() == false) { printf("open mysql db error\r\n"); delete db; return NULL; } return db; }
static acl::db_handle* open_sqlite(void) { const char* dbfile = "test.db"; acl::db_handle* db = new acl::db_sqlite(dbfile);
if (db->open() == flase) { printf("open mysql db error\r\n"); delete db; return NULL; } return db; }
void db_demo(void) { acl::db_handle* db;
db = open_mysql(); if (db) { tbl_select(*db); delete db; }
db = open_sqlite(); if (db) { tbl_select(*db); delete db; } }
|
从上面的例子可以看出,虽然操作的数据库不同,但数据库查询方式却是完全一样的,因为 acl 类内部屏蔽了数据库操作的差异性。下面还有几点需要注意:
- 对于数据库查询结果集有多种操作方式,开发者可以根据需要进行选择;
- 其中生成的 sql 查询语句比较简单,所以没有做特殊字符转义,真正生产环境中开发者应注意对 sql 中的一些变化查询字段进行转义(可以使用 acl::db_handle 类中的 escape_string 方法),以防止 sql 注入攻击;
- 如果查询的数据库结果集非空,则在处理结果完毕毕竟调用 acl::db_handle 类中的 free_result() 方法释放中间动态分配的内存;
- 在使用 acl 数据库类编写代码时不需要包含 mysql 和 sqlite 的头文件,但在程序连接阶段必须将 mysql/sqlite 的静态库加上。
二、数据库连接池
为了避免建立数据库连接开销对数据造成冲击,一般的数据库连接都建议使用连接池方式(尤其是在JAVA、PHP等应用中);连接池在保持与数据库的长连接过程中,必须要处理连接中断的重连情况,使上层使用者忽略连接中断的情况。
下图为 acl 的数据库连接池中各类的继承关系及连接池基础类的函数接口:
从 mysql_pool.hpp/sqlite_pool.hpp 头文件中可以看出,二者的主要区别是构造函数略有不同:
db_pool 类为数据库连接池基类,其中主要有两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
db_handle* peek();
void put(db_handle* conn, bool keep = true);
|
mysql 数据库连接池的构造函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
mysql_pool(const char* dbaddr, const char* dbname, const char* dbuser, const char* dbpass, int dblimit = 64, unsigned long dbflags = 0, bool auto_commit = true, int conn_timeout = 60, int rw_timeout = 60);
|
下面以 mysql 为例写一个简单的使用连接池的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void dbpool_demo(void) { const char* dbaddr = "127.0.0.1:3306"; const char* dbname = "acl_test_db"; const char* dbuser = "acl_user", *dbpass = "111111"; acl::db_pool* dbp = new acl::mysql_pool(dbaddr, dbname, dbuser, dbpass); acl::db_handle* dbh = dbp->peek(); if (dbh == NULL) { printf("peek db connection error\r\n"); delete dbp; return; } tbl_select(*dbh); dbh->put(dbh); delete dbh; }
|
由上面示例可以看出 acl 中的数据库连接池还是比较简单易用的,不过需要注意以下几点:
- 在创建数据库连接池对象时并不立刻连接后端的数据库,数据库的连接过程一般发生在 acl::db_pool::peek() 过程,但在调用 peek 时如果连接池有可用连接则直接使用之;
- 在使用数据库连接操作数据库时,如果因为网络意外导致连接断开,内部会根据数据库连接的返回错误号决定是否需要重试该数据库操作;
- 在用完数据库连接后需要调用 acl::db_pool::put() 过程归还数据库连;
- 在编译 lib_acl_cpp 库时必须需要指定 Makefile.db 为工程文件(make -f Makefile.db),这样才能使 lib_acl_cpp.a 库内部的数据库功能生效;同时在编译自己的应用程序时必须指定 libmysqlclient_r.a 的链接位置。
好了,关于如何使用 acl 库编写数据库应用先写到此,欢迎读者批评指正。
其它有关数据库使用例子请参考:
- acl\lib_acl_cpp\samples\mysql
- acl\lib_acl_cpp\samples\sqlite