经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
c++ 11 是如何简化你的数据库访问接口的
来源:cnblogs  作者:goodcitizen  时间:2021/3/24 9:12:36  对本文有异议

之前写过一篇文章专门分析了 c++ 模板编译过程中报的一个错误:《fatal error C1045: 编译器限制 : 链接规范嵌套太深 》,其中涉及到了 qtl —— 一个使用 c++ 11 构建的数据库访问库,当时限于篇幅,没有深入研究它是如何借助 c++ 11 来简化数据库访问接口的,本文现在就来探讨一下这方面的内容。

没有 c++ 11 之前,苦逼的程序员对于 sql 操作的输入输出,只好一行行敲代码,例如在调用数据库接口前设置绑定参数;在调用成功后,循环遍历查询的记录。很多时候数据库表对应在程序中就是一个结构体,程序员需要花费大量的精力将数据库表字段对应到结构体成员上、或反之,完全没有体现出来程序员应有的价值。而 qtl 这种 c++ 11 库的出现,可以极大的简化上面的程序编写,下面还是用之前文章中提到的例子作为演示,让大家感受一下:

插入单条数据

  1. 1 uint64_t test_insert_single(qtl::sqlite::database &db)
  2. 2 {
  3. 3 time_t now = time(0);
  4. 4 int tmp = rand() % 1000;
  5. 5 uint64_t id = db.insert_direct("insert into popbox_msg(msgid, msgtype, appname, uid, status, count, msgbody, stamp) values(?, ?, ?, ?, ?, ?, ?, ?)",
  6. 6 std::to_string(tmp), 108, "GDraw", "1923374929399", 1, 0, "this is msgbody", now);
  7. 7
  8. 8 printf("insert record with msgid %d return %d\n", tmp, (int)id);
  9. 9 return id;
  10. 10 }

 

插入操作需要输入数据,将数据编入 sql 是一种思路,但更好的方法是使用占位符 (?) 和数据绑定 (binding) 来防止 sql 注入问题,而这会给接口带来不定数量的输入参数,幸好 c++ 11 的可变模板参数特性允许用户提供不限数量与类型的输入数据,是不是很方便?下面是 qtl 提供的插入单条数据接口:

  1. 1 uint64_t qtl::base_database<T, Command>::insert<Params>(const std::string & query_text, const Params & params);
  2. 2 uint64_t qtl::base_database<T, Command>::insert<Params>(const char * query_text, const Params & params);
  3. 3 uint64_t qtl::base_database<T, Command>::insert<Params>(const char * query_text, size_t text_length, const Params & params);
  4. 4
  5. 5 uint64_t qtl::base_database<T, Command>::insert_direct<...Params>(const std::string & query_text, const Params & ...params);
  6. 6 uint64_t qtl::base_database<T, Command>::insert_direct<...Params>(const char * query_text, const Params & ...params);
  7. 7 uint64_t qtl::base_database<T, Command>::insert_direct<...Params>(const char * query_text, size_t text_length, const Params & ...params);

 

其中主要分两组:insert 与 insert_direct,前者只提供一个输入绑定参数,后者可以提供多个。而且这些接口会很贴心的将新插入记录的 rowid 返回,方便后续操作这条记录。

更新单条数据

  1. 1 void test_update_single(qtl::sqlite::database &db, uint64_t rowid)
  2. 2 {
  3. 3 time_t now = time(0);
  4. 4 uint64_t affected = 0;
  5. 5 db.execute_direct("update popbox_msg set status=?, count=?, stamp=? where rowid=?", &affected, 0, 3, now, (int)rowid);
  6. 6 printf("update record with rowid %d affected %d records\n", (int)rowid, (int)affected);
  7. 7 }

 

更新操作和插入操作类似,输入数据是必不可少的,但它有时也需要更新符合条件的记录,而这会带来另一坨不定数量的输入参数,不过好在二者都是输入参数,可以合二为一使用一个维度的可变模板参数,依次将更新参数与条件参数罗列在 qtl 接口提供的参数列表中即可:

  1. 1 void qtl::base_database<T, Command>::execute<Params>(const std::string & query_text, const Params & params, uint64_t * affected = NULL);
  2. 2 void qtl::base_database<T, Command>::execute<Params>(const char * query_text, const Params & params, uint64_t * affected = NULL);
  3. 3 void qtl::base_database<T, Command>::execute<Params>(const char * query_text, size_t text_length, const Params & params, uint64_t * affected = NULL);
  4. 4
  5. 5 void qtl::base_database<T, Command>::execute_direct<...Params>(const std::string & query_text, uint64_t * affected, const Params & ...params);
  6. 6 void qtl::base_database<T, Command>::execute_direct<...Params>(const char * query_text, uint64_t * affected, const Params & ...params);
  7. 7 void qtl::base_database<T, Command>::execute_direct<...Params>(const char * query_text, size_t text_length, uint64_t * affected, const Params & ...params);

 

主要也是两组接口:execute 与 execute_direct,前者只提供一个输入绑定参数,后者可以提供多个。由于是插入多条数据,这里没有办法返回某一条记录的 rowid,代之以的是更新的行数 affected,如果这个参数为空,则不返回。

插入多条数据

  1. void test_insert_multi(qtl::sqlite::database &db)
  2. {
  3. uint64_t affected = 0;
  4. int tmp[3] = { 0 };
  5. for (int i=0; i<3; ++i)
  6. tmp[i] = rand() % 1000;
  7. auto stmt = db.open_command("insert into popbox_msg(msgid, msgtype, appname, uid, status, count, msgbody, stamp) "
  8. "values(?, 108, 'GDraw', '1923374929399', 1, 0, 'this is msgbody', strftime('%s','now'))");
  9. qtl::execute(stmt, &affected, std::to_string(tmp[0]), std::to_string(tmp[1]), std::to_string(tmp[2]));
  10. printf("insert %d record\n", (int)affected);
  11. }

 

插入多条数据时,可变模板参数列表的每一个参数表示一个输入绑定参数、针对一条新记录,这样一来就不太够用了。例如上面这个例子中,相当于插入了三条不同的 popbox_msg 记录,每个输入参数对应记录的 msgid 字段,如果一条记录有多个字段需要输入就不适用了,那种场景下就需要写个循环多次调用插入单条数据的操作了(其实插入多条的接口底层也是递归为插入单条来执行的,所以这样做性能没有太大损失)。

更新多条数据

  1. 1 void test_update_multi(qtl::sqlite::database &db)
  2. 2 {
  3. 3 uint64_t affected = 0;
  4. 4 int id[3] = { 19, 20, 21 };
  5. 5
  6. 6 auto stmt = db.open_command("update popbox_msg set status=0, count=2, stamp=strftime('%s','now') where rowid=? ");
  7. 7 qtl::execute(stmt, &affected, id[0], id[1], id[2]);
  8. 8 printf("update %d record\n", (int)affected);
  9. 9 }

 

其实和插入多条数据非常相似,每条记录只能允许一个输入绑定参数。

删除数据

  1. 1 void test_delete(qtl::sqlite::database &db)
  2. 2 {
  3. 3 uint64_t affected = 0;
  4. 4 db.execute_direct("delete from popbox_msg where msgtype=? and appname=? and uid=?", &affected, 108, "GDraw", "1923374929399");
  5. 5 printf("delete record affected %d rows\n", (int)affected);
  6. 6 }

 

删除数据时由于只需要提供删除条件的输入绑定参数,而实际结果可能删除一条、也可能删除多条,所以不在数量上做区分。这里使用的是和更新数据一样的接口:execute 和 execute_direct,同样的,前者只能允许一个输入绑定参数,适合较简单的 sql 语句;后者可以允许多个输入绑定参数,适合较复杂的 sql。最后,删除的行数由 affected 参数返回给调用者。

查询单条数据

  1. 1 void test_query_single(qtl::sqlite::database &db, uint64_t rowid)
  2. 2 {
  3. 3 std::string msg;
  4. 4 db.query_first("select msgbody from popbox_msg where rowid=?", (int)rowid, msg);
  5. 5 printf("row %d: %s\n", (int)rowid, msg.c_str());
  6. 6 }

 

查询单条数据时可以直接将查询到的数据以输出参数方式回传,而查询条件往往又需要输入绑定参数,那 qtl 是如何区分可变模板参数列表中哪些是入参、哪些是出参呢?答案是区分不了。因此在接口设计上,qtl 的查询单条数据接口最多允许一个入参:

  1. 1 void qtl::base_database<T, Command>::query_first<Values>(const std::string & query_text, Values && values);
  2. 2 void qtl::base_database<T, Command>::query_first<Values>(const char * query_text, Values && values);
  3. 3 void qtl::base_database<T, Command>::query_first<Values>(const char * query_text, size_t text_length, Values && values);
  4. 4
  5. 5 void qtl::base_database<T, Command>::query_first<Params, Values>(const std::string & query_text, const Params & params, Values && values);
  6. 6 void qtl::base_database<T, Command>::query_first<Params, Values>(const char * query_text, const Params & params, Values && values);
  7. 7 void qtl::base_database<T, Command>::query_first<Params, Values>(const char * query_text, size_t text_length, const Params & params, Values && values);

 

主要分为两组:只带一个出参的 query_first;带一个出参和一个入参的 query_first。这个接口只针对特别简单的 sql 语句,如果想要返回一条记录的多个字段时,就必需使用另一组接口:query_first_direct

  1. 1 void qtl::base_database<T, Command>::query_first_direct<...Values>(const std::string & query_text, Values & ...values);
  2. 2 void qtl::base_database<T, Command>::query_first_direct<...Values>(const char * query_text, Values & ...values);
  3. 3 void qtl::base_database<T, Command>::query_first_direct<...Values>(const char * query_text, size_t text_length, Values & ...values);

 

遗憾的是这个接口虽然能提供多个出参,却无法提供任何入参,所有入参必需事先构建在 sql 语句中,这十分不优雅,但没有办法。下面是使用的例子:

  1. 1 void test_query_single_ex(qtl::sqlite::database &db, uint64_t rowid)
  2. 2 {
  3. 3 time_t stamp = 0;
  4. 4 int status = 0, count = 0;
  5. 5
  6. 6 std::ostringstream oss;
  7. 7 oss << "select status, count, stamp from popbox_msg where rowid=" << rowid;
  8. 8 db.query_first_direct(oss.str (), status, count, stamp);
  9. 9 printf("row %d: status %d, count %d, stamp %d\n", (int)rowid, status, count, (int)stamp);
  10. 10 }

 

从这个实际例子看,以后 c++ 可变模板参数列表可能需要支持两个参数列,一列是输入参数,一列是输出参数了。但是转念一想,这样好像也不对,因为出参与入参在调用点并无任何区别,编译器如何知道哪个是出参哪个是入参呢?所以这个问题可能还真是无解了。

查询多条数据

  1. 1 void test_query_multi(qtl::sqlite::database &db)
  2. 2 {
  3. 3 int cnt = 0;
  4. 4 db.query("select status, count, stamp from popbox_msg where appname=?", "GDraw",
  5. 5 [&cnt](int status, int count, time_t stamp){
  6. 6 printf("%d, %d, %d\n", status, count, (int)stamp);
  7. 7 cnt++;
  8. 8 });
  9. 9
  10. 10 printf("query %d records\n", cnt);
  11. 11 }

 

因为可能返回多条数据,这里使用回调函数(一般为 lambda 表达式)来接收读取的记录。回调函数参数列表必需与 select 选择的数据库表列相匹配。

  1. 1 void qtl::base_database<T, Command>::query<ValueProc>(const std::string & query_text, ValueProc && proc);
  2. 2 void qtl::base_database<T, Command>::query<ValueProc>(const char * query_text, ValueProc && proc);
  3. 3 void qtl::base_database<T, Command>::query<ValueProc>(const char * query_text, size_t text_length, ValueProc && proc);
  4. 4
  5. 5 void qtl::base_database<T, Command>::query<Params, ValueProc>(const std::string & query_text, const Params & params, ValueProc && proc);
  6. 6 void qtl::base_database<T, Command>::query<Params, ValueProc>(const char * query_text, const Params & params, ValueProc && proc);
  7. 7 void qtl::base_database<T, Command>::query<Params, ValueProc>(const char * query_text, size_t text_length, const Params & params, ValueProc && proc);

 

query 接口分为两组:一组只提供一个回调函数用于接收数据;另一组还提供一个额外的输入绑定参数。对于复杂的 sql 查询,这个还是不太够用,我不清楚为什么不能在 ValueProc proc 参数后面加一个可变模板参数列表,这样就不可以接收多个输入绑定参数了么?此处存疑。不过这个好歹比 query_first 要么只返回一个字段、要么返回多个字段但不接收输入参数要强一点。除了优点,这个接口也有一个不惹人注意的 bug,请看下面这段代码:

  1. 1 void test_query_multi(qtl::sqlite::database &db)
  2. 2 {
  3. 3 int cnt = 0;
  4. 4 db.query("select msgid, msgtype, appname, uid, status, count, msgbody, stamp from popbox_msg where appname=?", "GDraw",
  5. 5 [&cnt](std::string const& msgid, int msgtype, std::string const& appname, std::string const& uid, int status, int count, std::string const& msgbody, time_t stamp){
  6. 6 printf("%s, %d, %s, %s, %d, %d, %s, %d\n", msgid.c_str(), msgtype, appname.c_str(), uid.c_str(), status, count, msgbody.c_str(), (int)stamp);
  7. 7 cnt++;
  8. 8 });
  9. 9
  10. 10 printf("query %d records\n", cnt);
  11. 11 }

 

我增加了从数据库表中选取的字段,并相应的增加了 lambda 表达式的参数列表,当数量达到一个阈值时(亲测为8),VS2013 编译器将报错退出:

  1. e:\code\qtl\include\qtl\apply_tuple.h(17): fatal error C1045: 编译器限制 : 链接规范嵌套太深

 

具体分析请参考我的另一篇文章:《fatal error C1045: 编译器限制 : 链接规范嵌套太深》。这里我着重想说明的是,使用这种方式传递的字段在某些编译器上是有上限的,所以可移植性不太好。相信聪明的你已经猜到了,由于 query_first_direct 使用了和 query 相同的底层机制,query_first_direct 在 VS2013 上也存在相同的问题。幸好 qtl 还有另外一种方法,可以解决上面的问题,这就是结构体成员绑定:

  1. 1 class popbox_msg_t
  2. 2 {
  3. 3 public:
  4. 4 void dump(char const* prompt) const;
  5. 5
  6. 6 int msgtype = 0; // 108 or 402
  7. 7 int status = 0; // send to server result, (1:ok; 0:fail)
  8. 8 int count = 0; // retry times, if exceed POPBOX_MSG_RETRY_MAX, stop retry
  9. 9 time_t stamp = 0; // receive time
  10. 10 std::string msgid;
  11. 11 std::string msgbody;
  12. 12 std::string appname;
  13. 13 std::string uid;
  14. 14 };
  15. 15
  16. 16
  17. 17 void popbox_msg_t::dump(char const* prompt) const
  18. 18 {
  19. 19 tm* t = localtime(&stamp);
  20. 20 printf("%s : %s,%s,%s,%d,%d,%d, %04d-%02d-%02d %02d:%02d:%02d, %s\n",
  21. 21 prompt,
  22. 22 appname.c_str(),
  23. 23 uid.c_str(),
  24. 24 msgid.c_str(),
  25. 25 msgtype,
  26. 26 status,
  27. 27 count,
  28. 28 t->tm_year + 1900,
  29. 29 t->tm_mon + 1,
  30. 30 t->tm_mday + 1,
  31. 31 t->tm_hour,
  32. 32 t->tm_min,
  33. 33 t->tm_sec,
  34. 34 msgbody.c_str());
  35. 35 }
  36. 36
  37. 37 namespace qtl
  38. 38 {
  39. 39 template<>
  40. 40 inline void bind_record<qtl::sqlite::statement, popbox_msg_t>(qtl::sqlite::statement& command, popbox_msg_t&& v)
  41. 41 {
  42. 42 int n = 0;
  43. 43 qtl::bind_field(command, n++, v.msgid);
  44. 44 qtl::bind_field(command, n++, v.msgtype);
  45. 45 qtl::bind_field(command, n++, v.appname);
  46. 46 qtl::bind_field(command, n++, v.uid);
  47. 47 qtl::bind_field(command, n++, v.status);
  48. 48 qtl::bind_field(command, n++, v.count);
  49. 49 qtl::bind_field(command, n++, v.msgbody);
  50. 50 qtl::bind_field(command, n++, v.stamp);
  51. 51 }
  52. 52 }
  53. 53
  54. 54 void test_query_multi_ex(qtl::sqlite::database &db)
  55. 55 {
  56. 56 int cnt = 0;
  57. 57 db.query("select msgid, msgtype, appname, uid, status, count, msgbody, stamp from popbox_msg where appname=?", "GDraw",
  58. 58 [&cnt](popbox_msg_t const& pm){
  59. 59 pm.dump("msg");
  60. 60 cnt++;
  61. 61 });
  62. 62
  63. 63 printf("query %d records\n", cnt);
  64. 64 }

 

同样是 query 接口,同样是 lambda 表达式作为回调函数,不同的是,我提前声明了一个结构体 popbox_msg_t,并提供了  qtl::bind_record 模板函数的一个特化、来将数据库表的列与结构体成员二者关联起来,这样我的 lambda 表达式只要接收结构体就够了,qtl 在底层会自动根据 bind_record 将读取的数据初始化到结构体中供我们使用。因为这种方式避免了罗列各个输出参数,所以可以很好的避免上述问题。 另外关于 bind_record 补充一点,最新版本的 qtl 可以在 bind_record 模板特化中使用一个 bind_fields 来指定所有成员的对应关系了(我使用的旧版没有这个接口),类似于这样:

  1. qtl::bind_fields(command, v.msgid, v.msgtype, v.appname, v.uid, v.status, v.count, v.msgbody, v.stamp);

 

是不是更简单了呢?有了结构体绑定,还可以玩出许多花样,例如直接用结构体的成员函数来代替 lambda 表达式:

  1. 1 class popbox_msg_t
  2. 2 {
  3. 3 public:
  4. 4 void dump(char const* prompt) const;
  5. 5 void print();
  6. 6
  7. 7 int msgtype = 0; // 108 or 402
  8. 8 int status = 0; // send to server result, (1:ok; 0:fail)
  9. 9 int count = 0; // retry times, if exceed POPBOX_MSG_RETRY_MAX, stop retry
  10. 10 time_t stamp = 0; // receive time
  11. 11 std::string msgid;
  12. 12 std::string msgbody;
  13. 13 std::string appname;
  14. 14 std::string uid;
  15. 15 };
  16. 16
  17. 17
  18. 18 void popbox_msg_t::dump(char const* prompt) const
  19. 19 {
  20. 20 tm* t = localtime(&stamp);
  21. 21 printf("%s : %s,%s,%s,%d,%d,%d, %04d-%02d-%02d %02d:%02d:%02d, %s\n",
  22. 22 prompt,
  23. 23 appname.c_str(),
  24. 24 uid.c_str(),
  25. 25 msgid.c_str(),
  26. 26 msgtype,
  27. 27 status,
  28. 28 count,
  29. 29 t->tm_year + 1900,
  30. 30 t->tm_mon + 1,
  31. 31 t->tm_mday + 1,
  32. 32 t->tm_hour,
  33. 33 t->tm_min,
  34. 34 t->tm_sec,
  35. 35 msgbody.c_str());
  36. 36 }
  37. 37
  38. 38 void popbox_msg_t::print ()
  39. 39 {
  40. 40 dump("msg");
  41. 41 }
  42. 42
  43. 43 namespace qtl
  44. 44 {
  45. 45 template<>
  46. 46 inline void bind_record<qtl::sqlite::statement, popbox_msg_t>(qtl::sqlite::statement& command, popbox_msg_t&& v)
  47. 47 {
  48. 48 int n = 0;
  49. 49 qtl::bind_field(command, n++, v.msgid);
  50. 50 qtl::bind_field(command, n++, v.msgtype);
  51. 51 qtl::bind_field(command, n++, v.appname);
  52. 52 qtl::bind_field(command, n++, v.uid);
  53. 53 qtl::bind_field(command, n++, v.status);
  54. 54 qtl::bind_field(command, n++, v.count);
  55. 55 qtl::bind_field(command, n++, v.msgbody);
  56. 56 qtl::bind_field(command, n++, v.stamp);
  57. 57 }
  58. 58 }
  59. 59
  60. 60 void test_query_multi_ex(qtl::sqlite::database &db)
  61. 61 {
  62. 62 int cnt = 0;
  63. 63 db.query("select msgid, msgtype, appname, uid, status, count, msgbody, stamp from popbox_msg where appname=?", "GDraw",
  64. 64 &popbox_msg_t::print);
  65. 65
  66. 66 printf("query %d records\n", cnt);
  67. 67 }

 

代码高亮的部分就是两个版本的差异,这里使用了 popbox_msg_t 的一个成员函数  print 来充当 lambda 表达式的作用,这样做可以将代码集中到结构体中进行维护。不过缺点也是明显的,就是不能自由的选取外部输入参数了,例如对遍历记录数的统计,在新版本中就没办法做到了。除了上面的方式,还有一种新花样:

  1. 1 void test_query_multi_ul(qtl::sqlite::database &db)
  2. 2 {
  3. 3 int cnt = 0;
  4. 4 for(auto& pm : db.result<popbox_msg_t>("select msgid, msgtype, appname, uid, status, count, msgbody, stamp from popbox_msg where appname=?", "GDraw"))
  5. 5 {
  6. 6 pm.dump("msg");
  7. 7 cnt++;
  8. 8 }
  9. 9
  10. 10 printf("query %d records\n", cnt);
  11. 11 }

 

这种方式已经脱离了 query 接口,使用的是 result 接口,虽然壳变了,但是底层机制和 query 是一致的,都是通过 bind_record 将查询到的数据填充到结构体中,下面是 result 的接口定义:

  1. 1 query_result<Command,Record> qtl::base_database<T, Command>::result<Record>(const std::string & query_text);
  2. 2 query_result<Command,Record> qtl::base_database<T, Command>::result<Record>(const char * query_text);
  3. 3 query_result<Command,Record> qtl::base_database<T, Command>::result<Record>(const char * query_text, size_t text_length);
  4. 4
  5. 5 query_result<Command,Record> qtl::base_database<T, Command>::result<Record, Params>(const std::string & query_text, const Params & params);
  6. 6 query_result<Command,Record> qtl::base_database<T, Command>::result<Record, Params>(const char * query_text, const Params & params);
  7. 7 query_result<Command,Record> qtl::base_database<T, Command>::result<Record, Params>(const char * query_text, size_t text_length, const Params & params);

 

主要分为两组:一组只接收 sql 输入;另一组还可以接收一个额外的输入绑定参数。除了返回类型,与 query 接口几乎一模一样,可以理解成是将 query 的回调函数转化成了 result 返回的 query_result  集合。像上面例子那样写代码,几乎找到了之前 c 语言操作数据库的感觉,特别是不用把需要的外部变量在 lambda 表达式里一一捕获了,在循环里就可以直接用它们,就是一个字:爽!

如果有多个操作都从一个表中查询,可能只是选取的字段不同,那么这种情况下一个结构体就不够了,必需为每个查询定义一个独一无二的结构体并提供相应的 bind_record 函数(即使这些结构体拥有近似的成员)。这样简直是重复造轮子,难道不能定义一个包含所有字段的“超集”结构体,让它来包打所有这个表的查询吗?有的人可能会想,你把 sql 语句改造一下,每次选取所有字段、多余的不要用就好了呀!但是这样肯定不是一个优雅的解决方案,qtl 最新版本中包含了关于这方面的解决方案,那就是自定义绑定,请看下面这个例子:

  1. 1 void my_bind(popbox_msg_t&& v, qtl::sqlite::statement& command)
  2. 2 {
  3. 3 int n = 0;
  4. 4 qtl::bind_field(command, n++, v.status);
  5. 5 qtl::bind_field(command, n++, v.count);
  6. 6 qtl::bind_field(command, n++, v.stamp);
  7. 7 }
  8. 8
  9. 9 void test_query_multi_custom(qtl::sqlite::database &db)
  10. 10 {
  11. 11 int cnt = 0;
  12. 12 db.query_explicit("select status, count, stamp from popbox_msg where appname=?", "GDraw",
  13. 13 qtl::custom_bind(popbox_msg_t(), my_bind),
  14. 14 [&cnt](popbox_msg_t const& pm){
  15. 15 printf("msg: %d, %d, %d\n", pm.status, pm.count, pm.stamp);
  16. 16 cnt++;
  17. 17 });
  18. 18
  19. 19 printf("query %d records\n", cnt);
  20. 20 }

 

这个例子可以和前面的 popbox_msg_t 定义及其默认 bind_record 函数放在一起,由于这里我们使用 query_explicit 接口明确指定了使用的绑定函数是 my_bind,之前定义的默认绑定函数就不再起作用啦。这个查询只要表中的三个字段,因此在查询结束后也只有三个字段可用。我在下载了最新版本的 qtl 并尝试编译这代码时,编译器报错说没有找到 custom_bind 的定义,我全文搜索了一下也确实没有,但是这个例子可是我照着官网写的啊,难不成作者后来修改了代码忘记同步文档了吗?不得而知。

最后,对于数据库应用来说,视图 (view) 和过程 (procedure) 也是数据库经常接触到的概念,有的数据库过程会调用多个 select 语句查询结果,此时我们的接口又该怎么接收这些数据呢?答案就是 query_multi 和 query_multi_with_params,它们允许用户提供多个回调函数,一般就是写多个 lambda 表达式啦,这样就可以按过程中调用 select 语句的顺序来接收对应的查询结果了:

  1. 1 void qtl::base_database<T, Command>::query_multi<...ValueProc>(const std::string & query_text, ValueProc && ...proc);
  2. 2 void qtl::base_database<T, Command>::query_multi<...ValueProc>(const char * query_text, ValueProc && ...proc);
  3. 3 void qtl::base_database<T, Command>::query_multi<...ValueProc>(const char * query_text, size_t text_length, ValueProc && ...proc);
  4. 4
  5. 5 void qtl::base_database<T, Command>::query_multi_with_params<Params, ...ValueProc>(const std::string & query_text, const Params & params, ValueProc && ...proc);
  6. 6 void qtl::base_database<T, Command>::query_multi_with_params<Params, ...ValueProc>(const char * query_text, const Params & params, ValueProc && ...proc);
  7. 7 void qtl::base_database<T, Command>::query_multi_with_params<Params, ...ValueProc>(const char * query_text, size_t text_length, const Params & params, ValueProc && ...proc);

 

query_multi_with_params 顾名思义,就是在 query_multi 的基础上,允许一个额外的输入绑定参数。当然这个功能比较偏门,我没有专门写 demo 去验证。

下载

本文所有测试用例都是基于获取并打开 qtl::sqlite::database 对象的基础,那么这个对象又是如何打开的呢,请看下面框架:

  1. 1 int main(int argc, char* argv[])
  2. 2 {
  3. 3 int ret = 0;
  4. 4 srand(time(0));
  5. 5 uint64_t rowid = 0;
  6. 6 qtl::sqlite::database db(SQLITE_TIMEOUT);
  7. 7
  8. 8 try
  9. 9 {
  10. 10 // copy of gcm.db, and create following table:
  11. 11 // create table popmsg (msgid text not null, msgtype integer not null, cid text not null, uid text not null,
  12. 12 // status integer not null, count integer not null, msgbody text not null, stamp timestamp not null,
  13. 13 // primary key (msgid, msgtype, cid, uid));
  14. 14 db.open("../data/gcm.db", NULL);
  15. 15 printf("open db OK\n");
  16. 16
  17. 17 #if 0
  18. 18 rowid = test_insert_single(db);
  19. 19 #endif
  20. 20
  21. 21 #if 0
  22. 22 test_update_single(db, rowid);
  23. 23 #endif
  24. 24
  25. 25 #if 0
  26. 26 test_insert_multi(db);
  27. 27 #endif
  28. 28
  29. 29 #if 0
  30. 30 test_update_multi(db);
  31. 31 #endif
  32. 32
  33. 33 #if 0
  34. 34 test_delete(db);
  35. 35 #endif
  36. 36
  37. 37 #if 0
  38. 38 test_query_single(db, rowid);
  39. 39 #endif
  40. 40
  41. 41 #if 0
  42. 42 test_query_single_ex(db, rowid);
  43. 43 #endif
  44. 44
  45. 45 #if 0
  46. 46 test_query_multi(db);
  47. 47 #endif
  48. 48
  49. 49 #if 0
  50. 50 test_query_multi_ex(db);
  51. 51 #endif
  52. 52
  53. 53 #if 0
  54. 54 test_query_multi_ul(db);
  55. 55 #endif
  56. 56
  57. 57 //test_query_multi_custom(db);
  58. 58
  59. 59 db.close();
  60. 60 }
  61. 61 catch (qtl::sqlite::error &e)
  62. 62 {
  63. 63 printf("manipute db error %d: %s\n", e.code(), e.what());
  64. 64 db.close();
  65. 65 return -1;
  66. 66 }
  67. 67
  68. 68 return 0;
  69. 69 }

 

可以看到数据库的打开、关闭过程。因为 qtl 检测到底层数据库错误时,是通过抛出异常的方式来向上层报告的,所以所有用例都包含在 try_catch 结构中。可以通过编译开关来打开各个用例,多个用例之间可以组合起来使用,例如同时打开 test_insert_single 和 test_query_single 两个用例。所有相关的内容,包括 qtl、sqlite 头文件;sqlite lib 与 dll 和 so;sqlite 样例数据 db 文件;甚至编译好的可执行文件(Win10 x64 与 Linux x64),我都打包上传到博客园了,可以点击 这里下载

qtl 库最新版本不包含在里面 ,可以从这里获取:https://github.com/goodpaperman/qtl

结语

本文并不是 qtl 的使用指南,qtl  的许多内容(事务、语句对象、blob 类型、异步IO、indicator)都没有介绍。这里只是使用 qtl 这个典型的 c++11 库、以及数据库的“增删改查”四大操作、来说明新技术是如何"颠覆"用户调用接口的,以及在一些特定场景下(例如 query_first 既要不定输入参数,也要不定输出参数), c++ 新特性是否有可能去满足这种需求。从这里也能看出,c++ 的新需求新特性并不是凭空衍生的,而是从类似 qtl 这种模板库的实际需要产生的(如何写出用户调用更方便的接口),如果我们离开这些场景去学 c++ 新特性,会感到知识点纷繁复杂,而例子又全然不贴切,完全感觉不到新特性解决的痛点。当然  qtl 也不是尽善尽美,例如在使用回调函数处理输出数据的情况下,能不能给输入数据来个“不限量”参数列表?qtl 没有这样做是 c++ 不支持,还是 qtl 懒没有做到这一步,这就暂时不得而知了。

 

原文链接:http://www.cnblogs.com/goodcitizen/p/how_cpp11_simplify_your_database_access_interface.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号