acl 的 C++ 版本库(lib_acl_cpp.a)的 db 模块主要与数据库编程相关,经过这些模块库,开发者能够快速地写出支持数据库链接池的数据库应用程序,目前该 db 模块支持 mysql、sqlite 数据库。本文将以 mysql 应用为例讲述如何使用这些 API 接口编程数据库应用。mysql
在 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 过程与数据库过程结合)。sql
1、数据库操做句柄数据库
下图显示了数据库句柄的类继承关系: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 两个类为数据库查询结果类):后端
下面给出了一个简单的数据库查询示例:数组
//////////////////////////////////////////////////////////////////////////////// /** * 从数据库中查询表数据 * @param db {acl::db_handle&} acl 中的数据库链接句柄引用 */ static void tbl_select(acl::db_handle& db) { // 建立 sql 查询语句 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(); } //////////////////////////////////////////////////////////////////////////////// /** * 打开 mysql 数据库链接句柄 * @return {acl::db_handle*} 返回值为 NULL 表示链接数据库失败 */ 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; } /** * 打开 sqlite 数据库句柄 * @return {acl::db_handle*} 返回值为 NULL 表示链接数据库失败 */ 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; // 操做 mysql 数据库过程 db = open_mysql(); if (db) { tbl_select(*db); delete db; } // 操做 sqlite 数据库过程 db = open_sqlite(); if (db) { tbl_select(*db); delete db; } }
从上面的例子能够看出,虽然操做的数据库不一样,但数据库查询方式倒是彻底同样的,由于 acl 类内部屏蔽了数据库操做的差别性。下面还有几点须要注意:服务器
1)对于数据库查询结果集有多种操做方式,开发者能够根据须要进行选择;网络
2)其中生成的 sql 查询语句比较简单,因此没有作特殊字符转义,真正生产环境中开发者应注意对 sql 中的一些变化查询字段进行转义(可使用 acl::db_handle 类中的 escape_string 方法),以防止 sql 注入攻击;svn
3)若是查询的数据库结果集非空,则在处理结果完毕毕竟调用 acl::db_handle 类中的 free_result() 方法释放中间动态分配的内存;函数
4)在使用 acl 数据库类编写代码时不须要包含 mysql 和 sqlite 的头文件,但在程序链接阶段必须将 mysql/sqlite 的静态库加上。
2、数据库链接池
为了不创建数据库链接开销对数据形成冲击,通常的数据库链接都建议使用链接池方式(尤为是在JAVA、PHP等应用中);链接池在保持与数据库的长链接过程当中,必需要处理链接中断的重连状况,使上层使用者忽略链接中断的状况。
下图为 acl 的数据库链接池中各种的继承关系及链接池基础类的函数接口:
从 mysql_pool.hpp/sqlite_pool.hpp 头文件中能够看出,两者的主要区别是构造函数略有不一样:
db_pool 类为数据库链接池基类,其中主要有两个方法:
/** * 从数据库中链接池得到一个数据库链接,该函数返回的数据库 * 链接对象用完后必须调用 db_pool->put(db_handle*) 将链接 * 归还至数据库链接池,由该函数得到的链接句柄不能 delete, * 不然会形成链接池的内部计数器出错 * @return {db_handle*} 返回空,则表示出错 */ db_handle* peek(); /** * 将数据库链接放回至链接池中,当从数据库链接池中得到链接 * 句柄用完后应该经过该函数放回,不能直接 delete,由于那样 * 会致使链接池的内部记数发生错误 * @param conn {db_handle*} 数据库链接句柄,该链接句柄能够 * 是由 peek 建立的,也能够单独动态建立的 * @param keep {bool} 归还给链接池的数据库链接句柄是否继续 * 保持链接,若是否,则内部会自动删除该链接句柄 */ void put(db_handle* conn, bool keep = true);
mysql 数据库链接池的构造函数以下:
/** * 采用 mysql 数据库时的构造函数 * @param dbaddr {const char*} mysql 服务器地址,格式:IP:PORT, * 在 UNIX 平台下能够为 UNIX 域套接口 * @param dbname {const char*} 数据库名 * @param dbuser {const char*} 数据库用户 * @param dbpass {const char*} 数据库用户密码 * @param dblimit {int} 数据库链接池的最大链接数限制 * @param dbflags {unsigned long} mysql 标记位 * @param auto_commit {bool} 是否自动提交 * @param conn_timeout {int} 链接数据库超时时间(秒) * @param rw_timeout {int} 与数据库通讯时的IO时间(秒) */ 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 为例写一个简单的使用链接池的函数:
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); // 建立 mysql 链接池 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 中的数据库链接池仍是比较简单易用的,不过须要注意如下几点:
1)在建立数据库链接池对象时并不马上链接后端的数据库,数据库的链接过程通常发生在 acl::db_pool::peek() 过程,但在调用 peek 时若是链接池有可用链接则直接使用之;
2)在使用数据库链接操做数据库时,若是由于网络意外致使链接断开,内部会根据数据库链接的返回错误号决定是否须要重试该数据库操做;
3)在用完数据库链接后须要调用 acl::db_pool::put() 过程归还数据库连;
4)在编译 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。