MySQL++ V3.1.0 用户手册4
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++底层不会触发,如果出现了,很可能是在你的程序项目中出现了一个逻辑错误。