MySQL++是一个针对MySQL C API的C++封装。它的目的是提供一个类似STL容易一样简单易用的接口,帮助你有效的避免在代码中使用复杂的SQL语句。

3.12.处理带条件的结果行

有些时候,你从数据库中获取的数据比你实际所需要的数据多出很多。SQL的WHERE语句很强大,但是依然没有C++强大。我们没必要将全部数据进行存储再全部数据取出。此时你可以使用 Query::store_if() ,下面的例子告诉我们如何使用它。 例子 examples/store_if.cpp 代码如下:

#include "cmdline.h"
#include "printdata.h"
#include "stock.h"
#include <mysql++.h>
#include <iostream>
#include <math.h>


// 定义一个我们进行测试限制的结构
struct is_prime
{
    // 根据Num 进行取舍,只留下质数行有效
    bool operator()(const stock& s)
    {
        if ((s.num == 2) || (s.num == 3))
        {
            return true;
        }
        else if ((s.num < 2) || ((s.num % 2) == 0))
        {
            return false;
        }
        else
        {
            for (int i = 3; i <= sqrt(double(s.num)); i += 2)
            {
                if ((s.num % i) == 0)
                {
                    return false;
                }
            }
            return true;
        }
    }
};


int main(int argc, char *argv[])
{
    // 从命令行会获取数据库基本信息
    const char* db = 0, *server = 0, *user = 0, *pass = "";
    if (!parse_command_line(argc, argv, &db, &server, &user, &pass))
    {
        return 1;
    }

    try {
        // 建立和数据库的连接
        mysqlpp::Connection con(db, server, user, pass);

        // 设置查询的限制
        std::vector<stock> results;
        mysqlpp::Query query = con.query();
        query.store_if(results, stock(), is_prime());

        // 显示结果
        print_stock_header(results.size());
        std::vector<stock>::const_iterator it;
        for (it = results.begin(); it != results.end(); ++it)
        {
            print_stock_row(it->item.c_str(), it->num, it->weight,
                it->price, it->sdate);
        }
    }
    catch (const mysqlpp::BadQuery& e)
    {
        std::cerr << "Query failed: " << e.what() << std::endl;
        return 1;
    }
    catch (const mysqlpp::Exception& er)
    {
        std::cerr << "Error: " << er.what() << std::endl;
        return 1;
    }

    return 0;
}

我猜未必有人真的需要获取一个表内的质数行数据。我举这个例子只是告诉 Query::store_if() 的用法。而这个限制,你使用SQL的WHERE限制可能无法做到。在我们这个库里你将获得许多有用的高效方式。

3.13. 在一个结果表中为每行执行代码

SQL已经不再是个纯粹的数据库查询语言。现在的数据库引擎已经能够处理更多的判断和逻辑操作,但是将所有逻辑交由数据库去处理依旧不是一个最好的方法,如果你需要对查询结果做一些处理,MySQL++的 Query::for_each() 函数将提供你更多的帮助。 例子可参考 example/for_each.cpp ,代码如下

#include "cmdline.h"
#include "printdata.h"
#include "stock.h"
#include <mysql++.h>
#include <iostream>
#include <math.h>

// 定义一个函数进行结果表的统计处理
class gather_stock_stats
{
public:
    gather_stock_stats() :
      items_(0),
          weight_(0),
          cost_(0)
      {
      }

      void operator()(const stock& s)
      {
          items_  += s.num;
          weight_ += (s.num * s.weight);
          cost_   += (s.num * s.price);
      }

private:
    mysqlpp::sql_bigint items_;
    mysqlpp::sql_double weight_, cost_;
    friend std::ostream& operator<<(std::ostream& os,
        const gather_stock_stats& ss);
};

// 将数据结果输出为一个流。
std::ostream& operator<<(std::ostream& os, const gather_stock_stats& ss)
{
    os << ss.items_ << " items " <<
        "weighing " << ss.weight_ << " stone and " <<
        "costing " << ss.cost_ << " cowrie shells";
    return os;
}


int main(int argc, char *argv[])
{
    // 从命令行获取数据库基本信息
    const char* db = 0, *server = 0, *user = 0, *pass = "";
    if (!parse_command_line(argc, argv, &db, &server, &user, &pass))
    {
        return 1;
    }

    try {
        // 尝试创建数据库服务器连接
        mysqlpp::Connection con(db, server, user, pass);

        // 统计结果并输出
        mysqlpp::Query query = con.query();
        std::cout << "There are " << query.for_each(stock(),
            gather_stock_stats()) << '.' << std::endl;
    }
    catch (const mysqlpp::BadQuery& e)
    {
        std::cerr << "Query failed: " << e.what() << std::endl;
        return 1;
    }
    catch (const mysqlpp::Exception& er)
    {
        std::cerr << "Error: " << er.what() << std::endl;
        return 1;
    }

    return 0;
}

我们查看代码可知我们为 Query::for_each()提供了一个结果处理函数。For_each()实现了一个“select * from TABLE”的查询。然后再底层,该函数对每行结果调用了一次 gather_stock_stats() 函数。当然,这个例子并不恰当,你可以将统计功能交由SQL处理,但是我这里只是作为例子讲述该接口的使用。 正如 store_if() 一样,for_each() 也有重载函数允许你使用自己的结果表记录这些结果。

3.14. 连接选项

MySQL有一大列的连接选项来控制如何和数据库服务器连接。虽然默认值已经足够大部分情况使用,但是不排除你会需要一些特殊的连接方式,下面就给出简单的例子来说明一个常用的连接选项。 例子 examples/multiquery.cpp 代码如下:

#include "cmdline.h"
#include "printdata.h"
#include <mysql++.h>
#include <iostream>
#include <iomanip>
#include <vector>

using namespace std;
using namespace mysqlpp;
typedef vector<size_t> IntVectorType;


static void print_header(IntVectorType& widths, StoreQueryResult& res)
{
 cout << "  |" << setfill(' ');
 for (size_t i = 0; i < res.field_names()->size(); i++) {
  cout << " " << setw(widths.at(i)) << res.field_name(int(i)) << " |";
 }
 cout << endl;
}


static void print_row(IntVectorType& widths, Row& row)
{
 cout << "  |" << setfill(' ');
 for (size_t i = 0; i < row.size(); ++i) {
  cout << " " << setw(widths.at(i)) << row[int(i)] << " |";
 }
 cout << endl;
}


static void print_row_separator(IntVectorType& widths)
{
 cout << "  +" << setfill('-');
 for (size_t i = 0; i < widths.size(); i++) {
  cout << "-" << setw(widths.at(i)) << '-' << "-+";
 }
 cout << endl;
}


static void print_result(StoreQueryResult& res, int index)
{
 // Show how many rows are in result, if any
 StoreQueryResult::size_type num_results = res.size();
 if (res && (num_results > 0)) {
  cout << "Result set " << index << " has " << num_results <<
    " row" << (num_results == 1 ? "" : "s") << ':' << endl;
 }
 else {
  cout << "Result set " << index << " is empty." << endl;
  return;
 }

 // Figure out the widths of the result set's columns
 IntVectorType widths;
 size_t size = res.num_fields();
 for (size_t i = 0; i < size; i++) {
  widths.push_back(max(
    res.field(i).max_length(),
    res.field_name(i).size()));
 }

 // Print result set header
 print_row_separator(widths);
 print_header(widths, res);
 print_row_separator(widths);

 // Display the result set contents
 for (StoreQueryResult::size_type i = 0; i < num_results; ++i) {
  print_row(widths, res[i]);
 }

 // Print result set footer
 print_row_separator(widths);
}


static void print_multiple_results(Query& query)
{
    // 执行查询并输出结果表
 StoreQueryResult res = query.store();
 print_result(res, 0);
 for (int i = 1; query.more_results(); ++i) {
  res = query.store_next();
  print_result(res, i);
 }
}


int main(int argc, char *argv[])
{
 mysqlpp::examples::CommandLine cmdline(argc, argv);
 if (!cmdline) {
  return 1;
 }

 try
    {
        // 设置连接属性,如果你熟悉SQL C API
        // 你将明白MySQL++ 仅仅是将这些连接方式做了一个抽象封装
        // 具体可参见options.h 内定义。常用包括NamedPipeOption MultiStatementsOption MultiResultsOption 等
  Connection con;
  con.set_option(new MultiStatementsOption(true));

  // 连接数据库
  if (!con.connect(mysqlpp::examples::db_name, cmdline.server(),
    cmdline.user(), cmdline.pass())) {
   return 1;
  }

        // 设置多重查询
  Query query = con.query();
  query << "DROP TABLE IF EXISTS test_table; " <<
    "CREATE TABLE test_table(id INT); " <<
    "INSERT INTO test_table VALUES(10); " <<
    "UPDATE test_table SET id=20 WHERE id=10; " <<
    "SELECT * FROM test_table; " <<
    "DROP TABLE test_table";
  cout << "Multi-query: " << endl << query << endl;

        // 执行查询并显示查询结果
  print_multiple_results(query);

#if MYSQL_VERSION_ID >= 50000
  // If it's MySQL v5.0 or higher, also test stored procedures, which
  // return their results the same way multi-queries do.
  query << "DROP PROCEDURE IF EXISTS get_stock; " <<
    "CREATE PROCEDURE get_stock" <<
    "( i_item varchar(20) ) " <<
    "BEGIN " <<
    "SET i_item = concat('%', i_item, '%'); " <<
    "SELECT * FROM stock WHERE lower(item) like lower(i_item); " <<
    "END;";
  cout << "Stored procedure query: " << endl << query << endl;

  // Create the stored procedure.
  print_multiple_results(query);

  // Call the stored procedure and display its results.
  query << "CALL get_stock('relish')";
  cout << "Query: " << query << endl;
  print_multiple_results(query);
#endif

  return 0;
 }
 catch (const BadOption& err)
    {
  cerr << err.what() << endl;
  cerr << "This example requires MySQL 4.1.1 or later." << endl;
  return 1;
 }
 catch (const ConnectionFailed& err)
    {
  cerr << "Failed to connect to database server: " <<
    err.what() << endl;
  return 1;
 }
 catch (const Exception& er)
    {
  cerr << "Error: " << er.what() << endl;
  return 1;
 }
}

这是一个简单的进行多重查询的代码。多重查询是MySQL的一个新特性,因为这是一种新特性,它直接变换了客户端和服务器的连接,你必须在连接时候指定一个特殊的连接属性。我们创建了一个 MultiStatementsOption 对象来标识 Connection::set_option() 。当然这类标识还有许多其他的,你可以在 options.h 里查找到。 不过,大部分连接属性只能在建立连接前就设定好,只有极少数连接属性可以在连接建立成功后进行修改。 我们通常是创建一个没有连接的 Connection 对象,设置其连接属性后再进行实际的连接。 如果你熟悉MySQL C API,你将会发现MySQL++更加简单易用,C API 提供了一大堆的设定来控制连接方式,MySQL++则进行了封装,封装为几类连接,这种方式,更方便也更安全。

3.15. 处理连接超时

默认情况下,MySQL服务器如果闲置8小时没有执行查询,则会自动设置已有连接为连接超时。但这不是一个坏事,因为8小时自动连接超时的设定,对数据库而言是个好事,可以节省一些资源。 大部分程序不会间隔如此长时间执行查询,但是如果周末或者晚上,或许没有用户需要执行查询请求,所以经常有人抱怨说:过了个周末,连接就会断掉,而且查看DB数据库发现它仍在运行并没有需要重启,这一定是MySQL++的BUG。其实这是数据库的闲置连接作用,超过足够的闲置时间,它会自动关闭和客户端的连接。 对于这种情况,你调用 Connection::connected() 是无法获得正确状态结果的。它的返回值仅仅意味着在开始 connect() 行为时时候成功,并不意味着当前链接一定有效,你需要执行一个查询以确认当前的网络状态。 还有一种方式解决这个问题,修改MySQL的闲置时间,这个闲置时间是以分钟为单位的,意味着默认时间是8小时也就是28800分钟。如果你想设置更长的时间,例如4天,那么就设置闲置时间为345600分钟即可。 修改这个闲置时间,你可以在MySQL++建立链接之前,通过 ConnectTimeoutOption 连接参数进行设置。 还有一种方式,则是在你的项目I/O端口闲置时候,定期的调用 Connection::ping() ,当然我不建议频繁的进行ping操作,如果你的数据库闲置时间为8小时,我建议你每4-7个小时ping一次会比较合适。调用ping()将使数据库进行一个简单的数据操作,可以刷新它记录的闲置时间保证它不会迅速休眠。如果ping()函数返回true,说明和数据库服务器的连接依旧顺利,如果返回false,则意味着你需要和数据库服务器重新创建链接了。周期性的进行ping比你在项目中使用 异步I/O,多线程 或者某种 空的事件循环 来说相对比较容易,建议你使用。 这种ping方式还有一个变形的方式,就是在你每次调用查询之前先进行一次ping操作,或者在每组查询之前进行ping操作。但是这种方式并不推荐,当你客户端和多个服务器链接并频繁查询时,将消耗大量代码在ping操作上。

3.16.一个连接上的并发查询

MySQL C API有一个明显的限制,而基于其上的MySQL++同样存在着这样的一个限制就是:你在一个进程中对数据库的一个连接中,只能每次执行一个查询。如果上一个查询尚未完成之前,你进行下一次查询,你将从底层C API中得到一个错误“Commands out of sync”。从MySQL++中你也会在 Connection::error() 得到一个这样的异常错误码和错误信息。 这里有许多办法来处理这个限制:

  • 最简单的方法就是创建一个多线程项目,让多个线程分别有自己的链接,分别执行查询。除非你同步进行了极大量的查询,通常不会出现“Commands out of sync”错误。
  • 你或许很不喜欢每个线程有自己的 Connection 连接对象。那么你可以使用一个 Connection 连接对象,共享它,然后只要你能完美的将查询结果数据共享给另外一个线程即可。想详细了解这里,请查看 7.4 章节“共享MySQL++数据结构”。
  • 还有一个方法解决这个问题就是避免使用”use()”函数来查询。如前文提及,如果你不需要一次性获取全部数据行,那么你可以使用use(),use()是逐行获取数据的,意味着,use()是持续的,这很容易引发同步问题,例如:

UseQueryResult r1 = query.use(“select garbage from plink where foobie=‘tamagotchi’”); UseQueryResult r2 = query.use(“select blah from bonk where bletch=‘smurf’”);

这段代码中,第二个use()将调用失败,因为第一个use()结果可能还未执行完毕。 - 解决这个问题还有一个办法就是使用MySQL++的多重查询特性。这个特性支持你在一次调用就可以执行多个查询,然后逐个取得查询结果。如果你使用 Query::store() 获取结果,那么当你调用 store() 接口时只能取得第一个结果,然后你调用 store_next() 接口可以获取剩余的查询结果。MySQL++有个接口 Query::more_results() 方面你知道是否还有更多查询结果,如果还有,请继续调用 store_next(),直到你调用获取全部查询结果之前,不要执行本连接的其他查询。

3.17.获取字段源数据

下面的代码告诉我们如何从一个结果组中获取字段信息,例子为 examples/fieldinf.cpp 代码如下:

#include "cmdline.h"
#include "printdata.h"
#include <iostream>
#include <iomanip>

using namespace std;

int main(int argc, char *argv[])
{
 // 从命令行中获取数据库参数
 mysqlpp::examples::CommandLine cmdline(argc, argv);
 if (!cmdline)
    {
  return 1;
 }

 try
    {
  // 建立与数据库的连接
  mysqlpp::Connection con(mysqlpp::examples::db_name,
    cmdline.server(), cmdline.user(), cmdline.pass());

        // 获取表内信息
  mysqlpp::Query query = con.query("select * from stock");
  mysqlpp::StoreQueryResult res = query.store();

        // 显示表中每一字段信息
  char widths[] = { 12, 22, 46 };
  cout.setf(ios::left);
  cout << setw(widths[0]) << "Field" <<
    setw(widths[1]) << "SQL Type" <<
    setw(widths[2]) << "Equivalent C++ Type" <<
    endl;
  for (size_t i = 0; i < sizeof(widths) / sizeof(widths[0]); ++i)
        {
   cout << string(widths[i] - 1, '=') << ' ';
  }
  cout << endl;
  
  for (size_t i = 0; i < res.field_names()->size(); i++)
        {
   const char* cname = res.field_type(int(i)).name();
   mysqlpp::FieldTypes::value_type ft = res.field_type(int(i));
   ostringstream os;
   os << ft.sql_name() << " (" << ft.id() << ')';
   cout << setw(widths[0]) << res.field_name(int(i)).c_str() <<
     setw(widths[1]) << os.str() <<
     setw(widths[2]) << cname <<
     endl;
  }
  cout << endl;

  // 简单的类型检查
  if (res.field_type(0) == typeid(string))
        {
   cout << "SQL type of 'item' field most closely resembles "
     "the C++ string type." << endl;
  }
  if (res.field_type(5) == typeid(string))
        {
   cout << "Should not happen! Type check failure." << endl;
  }
  else if (res.field_type(5) == typeid(mysqlpp::sql_blob_null))
        {
   cout << "SQL type of 'description' field resembles "
     "a nullable variant of the C++ string type." << endl;
  }
  else
        {
   cout << "Weird: fifth field's type is now " <<
     res.field_type(5).name() << endl;
   cout << "Did something recently change in resetdb?" << endl;
  }
 }
 catch (const mysqlpp::BadQuery& er)
    {
  cerr << "Query error: " << er.what() << endl;
  return -1;
 }
 catch (const mysqlpp::Exception& er)
    {
  cerr << "Error: " << er.what() << endl;
  return -1;
 }

 return 0;
}

在 query.parse() 之前我们创建模板,然后我们对模板查询设置参数,最后我们才真正的执行查询。 我们接下来讲的详细一些。

4.1. 创建模板查询

你只需要使用类似C的 sprintf() 函数的占位符一样创建一个模板查询,之后你可以简单的为这个查询对象进行修改。创建完毕模板查询语句后,你调用这个查询对象的 parse() 函数解析一下。

query << “select (%2:field1, %3:field2) from stock where %1:wheref = %0q:what”; query.parse();

占位符的格式是:

%###(modifier)(:name)(:)

其中”###”是一个三位的数字,它是 SQLQueryParams 对象定义的从0开始的数字。 其中的”modifier”修饰词可以是下面的任意一个 - % 一个普通的百分比符号 - “” Don’t quote or escape no matter what. - q This will escape the item using the MySQL C API function mysql-escape-string and add single quotes around it as necessary, depending on the type of the value you use - Q Quote but don’t escape based on the same rules as for “q”. This can save a bit of processing time if you know the strings will never need quoting.(译者注: 这里难以翻译故暂时保留原文,似乎意思是保持一个引号符) 其中”:name”在 SQLQueryParams 中是可选的。Name可以保存任何数字文字和下划线标识参数的意义,但这仅仅可以加强可读性而已。如果你愿意,可以在”:name”后加两个冒号。用以表示”name”这个参数的描述结束。

4.2.执行时设置模板查询参数

当你真正使用Query::store( const SQLString& param0 ….. )或者 storein(), use(). Execute() 之前你需要指定模板查询的参数。你最多可以指定25个参数。例如:

select (%1:field1, %3:field2) from stock where %2:wheref = %0q:what StoreQueryResult res = query.store(“Dinner Rolls”, “item”, “item”, “price”)

使用模板查询解析后,意义将如下

select (item, price) from stock where item = “Dinner Rolls”

4.3. 默认参数

模板查询允许你设置默认参数值,你可以通过 Query::template_defaults 数组指定该查询的默认参数,你可以通过下标位置或者名称进行设置。

query.template_defaults[2] = “item”; query.template_defaults[“wheref”] = “item”;

做的是一样的事情。 这样设计有点像C++函数默认参数,如果你设置了一个查询的默认参数,那么你将可以免去设置查询的部分参数,例如,如果查询有四个参数,而你为最后的三个设置了默认参数,你将可以只设置第一个参数,就可以顺利执行这个模板查询。 这个特性将在查询时有许多不常更变参数时很意义,例如:

query.template_defaults[“field1”] = “item”; query.template_defaults[“field2”] = “price”; StoreQueryResult res1 = query.store(“Hamburger Buns”, “item”); StoreQueryResult res2 = query.store(1.25, “price”);

这两句模板查询解析后讲相当于

select (item, price) from stock where item = “Hamburger Buns” select (item, price) from stock where price = 1.25

这两句查询语句。在这个例子里,我们两句SQL语句的第2,3个参数是完全一致的,只有0,1这俩参数不同,默认参数则发挥了其作用。 当然,你也可以这么做:

query.template_defaults[“what”] = “Hamburger Buns”; query.template_defaults[“wheref”] = “item”; query.template_defaults[“field1”] = “item”; query.template_defaults[“field2”] = “price”; StoreQueryResult res1 = query.store();

虽然这样可以顺利执行,但是不推荐。因为这样使用默认参数则失去了设计的原有意图。

4.4. 错误处理

如果你忘记定义模板查询的参数,并且它们无法从 Query::templat_defaults 中获取到默认参数,这个查询对象执行时将抛出一个 BadParamCount 对象。此时你可以捕获这个错误,并且通过 BadParamCount::what() 中查看为什么,例如

query.template_defaults[“field1”] = “item”; query.template_defaults[“field2”] = “price”; StoreQueryResult res = query.store(1.25);

这个模板查询将抛出一个 BadParamCount 错误,因为参数中的 wheref 没有传入合法参数。理论上来说,这个错误在MySQL++底层不会触发,如果出现了,很可能是在你的程序项目中出现了一个逻辑错误。