admin 管理员组

文章数量: 887053


2024年2月19日发(作者:critical care medicine官网)

MySQL++ V3.1.0 用户手册

【译者注】该文档用词较为隐晦,译者也非职业翻译,故有些用词不准确,欢迎来邮件提出批评意见。另,转载请注明出处。Word文档将提交到百度文科,请自行下载。

[MySQL++ v3.1.0 用户手册]

Kevin Atkinson

Sinisa Milivojevic

Monty Widenius

Warren Young

Copyright ? 1998-2001, 2005-2010 Kevin Atkinson (original author)MySQL ABEducational

Technology

Resources

June 03, 2010

译者:自由骑士笃志

时间:2011-8-5

E-mail:

目录

1.简介 3

1.1. MySQL++历史简要 3

1.2. 如果你有问题 3

2. 概述 3

2.1. 连接对象 4

2.2. 查询对象 4

2.3. 结果表 4

2.4 异常 5

3. 指导 5

3.1. 运行例子 5

3.2. 一个简单的例子 6

3.3. 一个相对复杂一些的例子 7

3.4. 异常 9

3.5. 引用和引号泄露 10

3.6. C++ vs. SQL数据类型 10

3.7. 处理SQL的NULL 11

3.8. MySQL++的特殊的String类型 12

3.9. 处理二进制数据 13

3.10. 使用事务 18

3.11. 使用哪种查询方式? 24

3.12.处理带条件的结果行 25

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

3.14. 连接选项 28

3.15. 处理连接超时 31

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

3.17.获取字段源数据 32

1.简介

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

MySQL的最新版本可以从MySQL++的官方网页查看。

如果你希望支持MySQL开发工作,可以访问它的邮件列表,邮件列表内有记录开发人员名单,同时告诉你如何进行捐助。

1.1. MySQL++历史简要

MySQL++是1998年Kevin Atkinson创建的。它起步是初于MySQL,但是早期版本的目的是希望设计为数据库无关的,所以早期它被称为SQL++,在早期也曾被称为”sqlplus”。Kevin

Atkinson完成了1.0之前的版本。

到了1999年,开始Monty Widenius做了一些对MySQL++的补全工作,完成了1.1,1.2版本,但是后来他去了另外一个MySQL公司。之后Sinisa一直对库进行维护,直到2001年中旬发布了1.7.9版本。此时,他们发现进行数据库无关的开发几乎是不可行的。

也就是这个1.7.9版本之后,MySQL++很久时间没有进行更新,这样持续了三年。期间内Sinira一直对MySQL++邮件列表的用户进行技术解答,有时会发布部分补丁,但是再未正式更新过。

在这个期间内还有个很糟糕的事情,2001年的时候,主流的C++编译器还无法支持标准C++。导致MySQL++使用了许多不标准的构造去适应老的编译器。直到MySQL++开始大量使用模板之后,这更增加了使用者的麻烦,他们在项目中使用MySQL++时会出现各种奇怪的警告和错误。

直到2004年八月,WarrenYoung实在无法忍受了,他将之前的许多零碎补丁整合起来,发布了1.7.10版本,这个版本使用GCC3.3编译后再没有任何警告。从那以后,MySQL++才逐渐的减少它的bug,开始变的有活力了。

1.2. 如果你有问题

如果针对这个库有什么问题,需要邮件咨询别人,我们很希望你可以发送邮件到MySQL++邮件列表。这个邮件列表有存档,或许你可以在里面搜索到别人已经提出同样的问题。

你可能会在MySQL++列表中发现一个私人邮箱,建议不要发送邮件给个人,因为有些开发者已经不再负责MySQL++的开发。

2.概述

MySQL++能够处理复杂的数据库操作,而事实上,它使用起来又比其他的数据库API简单,

它的大致用法如下:

1: 开启连接。

2: 组成执行查询。

3: 如果成功则返回结果。

4: 如果失败则处理错误。

每一步均对应MySQL++的一个类。

2.1. 连接对象

每一个连接对象负责管理一个MySQL服务器的连接。你起码需要一个连接对象进行数据库事务处理。

MySQL支持客户端和服务器有多种不同的数据连接:TCP/IP,Unix domain sockets,Windows命名管道。

MySQL++的普通连接对象 Connection 类可以支持上面的全部连接,只要在

Connection::connect() 时指定不同的参数便可。当然,如果开始你就知道连接种类,可以直接使用子类,例如直接使用 TCPConnection 类。

2.2. 查询对象

通常你可以使用 Connection 类对象创建一个SQL查询对象。

Query 查询对象使用方式类似一个C++输出流,所以你可以像使用 std::out 或者

std::ostringstream 一样写入数据。这就是MySQL++创建一个查询字符串时最类似C++的方法。这个库做了文件流处理,这样你可以很容易的创建出正确的SQL语句。

Query 查询对象还支持一个特性,我们称之为模板查询(Template Queries),它用起来就类似C的 printf() 函数。你可以用一些符号标志插入的变量部分。如果你要进行大量相似的查询,可以创建一个模板查询,然后更换其中的变量即可。

第三种创建 Query 查询对象的方式是使用SSQLS。这个特性允许你创建一个C++结构,你同样可以进行INSERT,REPLACE,UPDATE操作,同样也可以生成 SELECT * FROM

TableName 的查询,最终将查询的结果保存在一个类似STL容器的SSQLSecs内。

2.3. 结果表

结果表里的数据都保存在一个类似 std::string 的 String 对象中。这个 String 类中有大量简洁的函数能让你很方便的将其转换为C标准数据类型。另外一些MySQL++内定义的类型,例如 DateTime 类型,你也可以轻松的从MySQL的DATATIME 类型直接初始化,MySQL++会自动的进行转换。转换中如果出现错误,你可以设置一个警告或者抛出一个异常,如何处理这种错误取决于你如何设置本库。

虽然MySQL函数返回结果不同,但整体来说,MySQL++里查询返回类型有以下几个主要类型:

- 无数据返回的查询

并不是所有的SQL查询需要返回数据,例如 CREATE TABLE 。这种类型的查询,将返回

一个特殊的返回值类型:SimpleResult 这个返回值会简单的描述一些查询的信息,例如查询是否成功执行,结果有多少行等。

- 返回MySQL++结构类型的查询

大部分时候,接收一个结果表的方式是使用 Query::store() 函数。这个函数会返回一个

StoreQueryResult 对象,这个对象基于 std::vector,它是一个对Rows做了随机的存储的容器。每一个 mysqlpp::Row 里保存了一个 std::vector 的 String 对象,每一个 String 对象都是一个数据结果。所以,你可以将 StoreQueryResult 看做一个二维数组。例如,你想获取第5列第2行的数据,你可以这样写 result[1][4] ,你同样可以使用元素单元名进行访问,例如,result[2][“priceKey”]; 。

一个简单的执行查询的方式是使用 Query::use() ,它会返回一个 UseQueryResult 对象,这个类类似于一个STL的std::vector 输入迭代。你可以一次性处理结果表里的一行。当你不知道结果表内有多少结果的时候,可以迭代遍历到容器尾。这个特性允许我们获得更好的内存效能,因为查询出来的结果不用保存在RAM中,这对我们获得大容量的结果表时很有意义。

- 返回MySQL标准结构或自定义结构

使用MySQL++的数据结构来提取数据,的确比MySQL C API方便,但是你可以定一些自己的C++结构以承接数据库中的一些自定义数据。这样的话,就需要你在代码里加入一些原生的SQL代码。

如果你要用自己定义的C++结构,可以下面这样做:

vector v;

query << "SELECT * FROM stock";

n(v);

for (vector::iterator it = (); it != (); ++it) {

cout << "Price: " << it->price << endl;

}

是不是很简单?

如果你不想创建SSQLSes去承接你的表结构,你可以使用MySQL++ v3提供的一种方式,使用 Mysqlpp::Row 去容载。

vector v;

query << "SELECT * FROM stock";

n(v);

for (vector::iterator it = (); it != (); ++it) {

cout << "Price: " << it->at("price") << endl;

}

这样可以不足够明晰优雅,但也不失为一种方法.

2.4 异常

通常情况下,无论任何地方遇到一个错误,库都会将异常抛出。当然,如果你愿意,你可以对库设置一个错误标示而不再要求抛出,当前异常会提供更多的信息,而不仅仅是一个告诉你为什么异常的一个字符串。异常包括异常的类型等信息,会方便你去进行查找分析。

3.指导

前面的章节简要概述了MySQL++的基本情况。接下来我们将深入一些讲一些例子。我们从每个MySQL++必须处理的模块进行讲解,之后将一些更加复杂的模块。你可以读完本章节就停止了,因为后面我会只会将更多的复杂的高级特性。

3.1. 运行例子

如果你通过库源代码编译,那么当你编译完毕,例子也都应该被编译完毕了。如果你下载的是RPM格式,那么例子代码和一个makefile文件可能被安装在

/usr/share/doc/mysql++devel-*/examples 里,当然,根据Linuxes不同也可能有些不同。

在你开始前,请根据平台,先阅读以下库内的 README*.txt ,我们在本文档不再重复。

我们的许多例子需要一个 test 数据库,我们可以通过 resetdb 创建它。你可以输入如下命令:

resetdb [-s server_addr] [-u user] [-p password]

通常来说,MySQL++库应当已经编译完成,放置在操作系统动态链接器可以查找到的一个目录下了。(通常MySQL++不编译为静态的)如果你是下载的RPM格式,则需要根据Source编译一个Lib,然后建议你运行部分例子以保证库运行顺利。如果你的操作系统动态链接器无法找到MySQL++库,我们创建了一些脚本协助你运行这些例子。

MySQL++为Unix相关系统编写了一个 exrun 的Shell脚本,为Windows系统编写了一个

批处理文件。你可以通过

exrun 脚本为这些例子创建一个环境。

./exrun resetdb [-s server_addr] [-u user] [-p password]

当然,如果在Windows里执行,可以去掉上述命令中的 ‘./’ 符号。

参数里 server_addr 可以填写

localhost - 本地(默认的)

192.168.1.224:3306 - 数据库IP和TCP端口

ServerName:SvcName - 这会优先从你的系统网络中查找指定名称的TCP服务,例如Unix下 /etc/services ,Windows下的C:Windowssystem32driversetcservices, 然后从服务中查找到端口和你所建议的服务器名称。

如果参数里没有冒号和指定的端口,那么默认将使用3306端口。

如果参数里没有 –u 指定数据库用户名,则默认使用你当前的登录本机用户名。

如果参数没有 –p 指定数据库密码,则假设MySQL没有密码。

注意,我们要运行 resetdb, 用户名要求能够有足够权限创建 test 数据库。一旦这个数据库创建成功,你就可以运行多个例子去尝试DELETE,INSERT,SELECT,UPDATE 数据库。当然,或许你希望创建一个单独的用户去执行这些在 test 数据库上的测试操作,那么可以执行下述命令。

CREATE USER mysqlpp_test@'%' IDENTIFIED BY ’nunyabinness';

GRANT ALL PRIVILEGES ON mysql_cpp_data.* TO mysqlpp_test@'%';

这样你就创建一个用户,用户名 mysqlpp_test ,密码为 nunyabinness .

然后你可以继续执行

./exrun resetdb -u mysqlpp_test -p nunyabinness

实际运行 resetdb 这个例子,这个例子会为你创建一个 mysql_cpp_data 数据库,内有四个表。

之后,你可以看看 来获取更多信息,运行其他例子看看吧.

3.2. 一个简单的例子

这个简单例子教我们如何去创建一个连接,执行一个查询并将结果显示出来。这个文件在

examples/ 中,代码如下:

#include "cmdline.h"

#include "printdata.h"

#include

#include

#include

using namespace std;

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;

}

// 连接数据库

mysqlpp::Connection conn(false);

if (t(db, server, user, pass))

{

// 从stock表中获取Item字段数据表并显示

mysqlpp::Query query = ("select item from stock");

if (mysqlpp::StoreQueryResult res = ())

{

cout << "We have:" << endl;

for (size_t i = 0; i < _rows(); ++i)

{

cout << 't' << res[i][0] << endl;

}

}

else

{

cerr << "Failed to get item list: " << () << endl;

return 1;

}

return 0;

}

else

{

cerr << "DB connection failed: " << () << endl;

return 1;

}

}

这个例子里我们从数据库中查找 stock 表,获取其中的 item 列数据,并将数据逐一输出。

注意MySQL++中的 StoreQueryResult 起源于 std::vector ,而其中的 Row 又定义了类似

vector 一样的接口,意味着你可以进行下标访问,当然,也可以使用迭代器进行访问。

Row 比 vertor 更强大的一点是,它支持你使用字段标示进行访问,例如: res[i][“Item”]

这段代码中唯一不非常清楚的就是 parse_command_line() ,这个函数只是为了这些例子有一个统一的接口风格,你可以理解它是一个黑盒子,将 argc 和 argv 分割为不同的数据库参数。

3.3. 一个相对复杂一些的例子

Simple1这个例子过于简单,没有太多价值,我们讲的深入一些,这里是

examples/ ,以下是代码:

#include "cmdline.h"

#include "printdata.h"

#include

#include

#include

using namespace std;

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;

}

// 连接数据库

mysqlpp::Connection conn(false);

if (t(db, server, user, pass))

{

// 获取 stock 表内所有数据并存储

mysqlpp::Query query = ("select * from stock");

mysqlpp::StoreQueryResult res = ();

// 输出结果

if (res)

{

// 输出标题列

(ios::left);

cout << setw(31) << "Item" <<

setw(10) << "Num" <<

setw(10) << "Weight" <<

setw(10) << "Price" << “Date” << endl << endl;

// 取结果里的每一行输出

for (size_t i = 0; i < _rows(); ++i)

{

cout << setw(30) << res[i]["item"] << ' ' <<

setw(9) << res[i]["num"] << ' ' <<

setw(9) << res[i]["weight"] << ' ' <<

setw(9) << res[i]["price"] << ' ' <<

setw(9) << res[i]["sdate"] <<

endl;

}

}

else

{

cerr << "Failed to get stock table: " << () << endl;

return 1;

}

return 0;

}

else

{

cerr << "DB connection failed: " << () << endl;

return 1;

}

}

这个例子主要说明,我们使用对象字段标示进行访问,而不再使用下标。这样做会略微慢一些,但是可读性很好。

3.4. 异常

默认状态下,MySQL++会将异常视为一种错误,逐步传递下去。在我们的例子里面,所有异常将在 Connection 的构造函数处传递出来,使其构造失败,这样做会更加允许弹性的处理错误。在我们实际的项目中,我推荐我们将错误处理开启,有错误直接处理掉。这样我们也可以使用默认的 Connection 构造函数。

MySQL++中的所有异常均继承于一个Exception类,而它又是继承于 std::exception 类。所以,所有的标准C++错误都可能被MySQL++捕获。

若你强硬设置异常不进行抛出,那么异常将被视做一个错误返回。或者是一个int的错误码,或者是一个空对象指针,或者是一个false,也可能是一个对象中的一个错误标示。但是,你只能将继承于 OptionalException 接口的异常视为错误而不抛出,这些不被抛出的异常将别传递到 Connection 的构造函数处。

如果一个基于 OptionalException 接口继承的对象有一份拷贝对象,那么这个对象也将会拷贝源对象的一个异常标识。

例如:

mysqlpp::Connection con; // 这样的话,这个对象将会在构造时抛出异常。

但继承于NoException的对象是永远不会抛出异常的

例如:

mysqlpp::NoExceptions ne(con); // 这里没有问题

if (!_db("a_db_that_might_not_exist_yet"))

{

// 只会进入这里,表明某处有错误,但这个错误实际并非 select_db 函数的错误,而是 con

原本就为空。

}

但有些异常,MySQL++是不允许视为错误的,将会无条件抛出:

- 索引访问错误。例如对一个只有5组数据的数组,进行 row[21] 的访问,你将会得到一个 BadIndex 异常。如果你进行row[“field”] 访问,而实际上没有 field 这个字段,你将

会得到一个 BadFieldName 异常。在之前的版本中,由于过于模仿STL容器,所以这里可能会返回 std::range 异常,但是在MySQL++ v3.0.7 版本之后,应该是不会出现问题了。

- String转换时异常。例如,你尝试将”1.25”转换为int类型时,会抛出一个 BadConversion

异常。但是,如果你试图将”1.00”转换为int,则不会抛出异常。MySQL++能够判断哪些类型可以安全转换。

- 模板参数异常。如果你使用模板查询,而你又没有传入足够的参数,将会抛出一个

BadParamCount 异常。

- 类型查找异常。如果你使用一个C++数据结构,但是MySQL++又无法将其转换为SQL结构,MySQL++将抛出一个 TypeLookupFailed 异常。非常建议你使用 lib/sqltypes.h 内定义的类型。

当然,如果你拼错一个字母,或者强制转换一个奇怪类型,也将引发一些异常。

3.5. 引用和引号泄露

SQL语法解析经常需要依赖一些外界数据,例如下面这个查询:

SELECT * FROM stock WHERE item = 'Hotdog Buns'

因为 “Hotdog Buns” 字符串需要被一对单引号包含,是一种引用,在MySQL++中,你通常不需要将这些引用特殊处理。

string s = "Hotdog Buns";

query << "SELECT * FROM stock WHERE item = " << quote_only << s;

这个代码创建了同样的一个查询字符串。我们使用了MySQL++的quote_only操作符。这个操作符会在下一个单元加上单引号,再加入流中。这种方式在MySQL++类型适配器中可以进行完美解析。

引用还是比较简单的,但是SQL语法解析时经常还有一种“单引泄露”。假设一下,我们现在要查找一个数据为”Frank’s Hotdog Buns“这么一个数据表,我们的查询字符串将会是

SELECT * FROM stock WHERE item = 'Frank's Brand Hotdog Buns'

其实这样不是一个有效的查询语法,有效的语法应该是

SELECT * FROM stock WHERE item = 'Frank''s Brand Hotdog Buns'

如你所料,MySQL++对这种引号泄露操作有特殊处理。我们可以用一个很简单的方法来处理它:

string s = "Frank’s Brand Hotdog Buns";

query << "SELECT * FROM stock WHERE item = " << quote << s;

类似于quote_only,quote等相关的特定操作符在MySQL++中还有许多,可以参考代码中

manip.h 一些定义。

有一点非常重要,我们必须明白MySQL++这些操作符的实现机制。这些操作符在你没有将查询语句创建为一个流之前是完全无效的。另外这些操作符是一种建议的性质,不是命令。当MySQL++认为符合SQL语法时候,MySQL++可能无视这些操作符。

还有一点要注意,这些操作符在组成查询流的时候,以及在使用模板参数查询的时候意义是不完全一致的。

3.6. C++ vs. SQL数据类型

C++和SQL数据类型有些不同,这就导致你在使用MySQL++时候可能会引发一些问题。当然,你在使用其它库的时候也可能出现这些问题。

大多数数据类型都可以保存在SQL数据库中,但是SQL自身是一个文本格式化语言,所以数字类型或者其他类型都要求被转化为文本类型进行数据存储。因此,MySQL++做了很多文本化操作,以便将C++的一些数字型数据简便的进行文本化处理。

一些用户会担心类型转变会引发部分的数据丢失,但显然这不成问题,MySQL++承诺对二进制数据以及数字类型的转化完全不会有信息丢失。

(但是最大的问题还是浮点类型数据。FLOAT 和 DOUBLE 类型的SQL数据类型,我们依旧有些麻烦……)(译者笃志按:原文这句话用小八号字体写在小角落里,这不吭爹么……)

关于类型转换,我们还有一个已知的问题就是SQL的DECIMAL类型转化为C++浮点型时可能会有溢出,显然这种情况我们很难遇到,这是为什么我们暂时没有解决这个BUG的原因。

避免数据类型转换的最好方法依然是使用MySQL++里Lib/sqltypes里的标准格式。

MySQL++不会强制你使用这些typedef。所以你可以在自己的程序里写满 int 而绝不使用

mysqlpp::sql tinyint unsigned。但是,使用MySQL++给你带来以下一些好处:

- 空间消耗小。MySQL++类型完全基于MySQL数据类型,没有额外附属信息。

- 平台无关。如果你的程序需要在不同的操作系统上运行,甚至在32位和64位平台上切换运行,使用MySQL++类型将使你的代码平台无关化。

- 清晰易懂。C++类型和SQL类型同时使用的话,将使你的代码混乱不堪,统一为MySQL++类型将使你的代码类型简单易懂。

- 安全。使用C++类型进行数据转换时,可能会引发 TypeLookupFailed 异常,甚至更糟糕。

类型兼容不仅仅对你当前代码很重要,对你以后的代码维护也非常重要。我们会定期的修改MySQL++的类型定义,以更好的适应不同系统平台的C++和不同版本的SQL平台。例如,如果你使用 sql_decimal 替代 DECIMAL,那么当数据库对double不同处理时,我们会自动的使你代码重新编译以适应新的数据库平台。

许多类型定义是使用标准C++数据类型,但是有些是MySQL++自定义的类型。例如:在SQL里的DATETIME类型在MySQL++里被定义为 mysqlpp::DateTime ,为了一致性考虑,在

sql_types.h 里再次将 DateTime 类型定义为 mysqlpp::sql_datetime 类型。

但是MySQL++并没有定义一些非C++标准和SQL的类型,例如空间坐标Vector等类型,使用这些类型时请注意小心。

3.7. 处理SQL的NULL

C++和SQL 都有一类数据称为NULL,但是它们某些情况下是不同的。因此,MySQL++在这里做了一些支持封装。

Both C++ and SQL have things in them called NULL, but they differ in several ways.

Consequently, MySQL++ has

to provide special support for this, rather than just wrap native C++ facilities as it can with most

data type issues.

当SQL NULL 时一个类型修饰时

在SQL中,“NULL”可能是一个类型修饰,它是一个特殊的值。

为适应SQL中的NULL,MySQL++提供一个空的模板去实现类似于C++的NULL类型。例如,我们有一个TINYINT UNSIGNED类型里存储了一个C++的空值。那么可以这样写:

mysqlpp::Null myfield;

在MySQL++ v3.1中,我们可以更简单一些,如下去写:

mysqlpp::sql_tinyint_unsigned_null myfield;

这些类型都定义在 lib/sql_types.h 中。你可以自行查看。模板实例化是C++的一个高级特性,这里使用该特性可以更大程度的避免C++和MySQL之间的NULL不同定义问题。

当SQL NULL是一个唯一值时

在SQL中和标准C++中的NULL第二个明显差异就是:SQL可能是一种特殊类型值,它既不是0,也不是false,也不是空字符串,而代表一种“未知/未定义”。此时我们不能用C++的NULL和它画上等号。这种NULL,在MySQL++中我们使用一个全局的 null 对象表示。

myfield = mysqlpp::null;

如果你将SQL的Null填充到C++的IO流中,输出时候你会得到一个类似于普通字符串的“(NULL)”,这样可以适度的保留SQL的NULL值得特性。

如果你尝试保存一个SQL的NULL,你可以使用Null模板。默认该模板第二个参数是

mysqlpp::NullIsNull 。这会自动标记 mysqlpp::null 为SQL的NULL。

我们看下面代码

mysqlpp::Null myfield(mysqlpp::null);

cout << myfield << endl;

cout << int(myfield) << endl;

这样的话,第一行会输出”(NULL)”,第二行甚至不会被编译通过,而抛出一个错误

CannotConvertNullToAnyOtherDataType 。

但是,如果你希望存储一个标准的 unsigned char 类型的NULL,即0.你可以这样做

mysqlpp::Null myfield(mysqlpp::null);

cout << myfield << endl;

cout << int(myfield) << endl;

这样则会输出两次 0 。 请注意模板的第二个参数。

3.8. MySQL++的特殊的String类型

MySQL++有两个类很类似 std::string : String 和 SQLTypeAdapter 。

这两个类提供了类似 std::string 的方法并额外提供了一些其他方法,但他们都不是

std::string 的继承子类,也不是一个封装。因此很多用户很喜欢直接使用 std::string 而不使用这两个类。但是这两个类的确对MySQL++而言非常重要,下面我们将用些时间去熟悉他们。

SQLTypeAdapter

这俩类中,相对简单一些的是 SQLTypeAdapter,也可简写为 STA。

如它名字所言,这个类的目的是将其他类型转化为SQL理解的类型。它有一系列的类型转换构造函数,可以将许多其他类型转换为 SQLTypeAdapter 类型。在构造函数的转换过程中,SQLTypeAdapter会记录原有的数据类型信息,所以这种转换不会丢弃任何重要信息。

STA 在MySQL++里创建SQL查询时被经常使用。即使你使用模板查询,也同样会在底层使用 STA。

String

如果说MySQL++有自定义类型,那么只能是 String。但是这个类不是足够被大众习惯使用。可能以后MySQL++将更多的开放基于 std::string 的功能,在V2.3版本之前,String 类名是 ColData ,但是后来发现它的功能不仅仅是保存一行数据,于是就修改类名为 SQLString

了。

String 比 std::string 功能强大的一点是,它知道能够将一些SQL的字符串转换为C++格式的一些特殊类型。例如:如果你使用String类型初始化”2007-11-19”,String 可以将其转换为 Date 类型,通样,Date 类型也可以简便的转换为 String 类型。

因为 Row::operator[] 返回的是 String 类型,所以你可以这样编码:

int x = row["x"];

在一些情况下,String 其实和 STA 是相反的: String 可以将SQL类型字符串转换为C++数据类型。STA 可以将C++数据类型转换为SQL 类型字符串。

String 主要有俩个用途:

首先它经常被 Row 使用,例如上面的例子,它不仅仅作为 Row::operator[] 返回值,它也是Row内部的核心组成。所以,当MySQL++从数据库取得数据后,都会通过 String 的转换然后再转为你所需要的C++数据类型。

另外,因为String是从数据库取值转换为我们自定义数据的最后一道也是唯一的接口,所以它是需要拷贝出来提供给我们使用的,此时,如果是MySQL++的sql_blob类型,拷贝的代价就过于昂贵了,所以String采用了引用计数。

引用计数

为了减少不必要的内存拷贝,STA和String都采用了 引用计数 和 写时拷贝 技术。写时拷贝 意味着你使用拷贝构造时,并没有真正的拷贝数据,仅仅是拷贝了一个原本对象的数据内存指针,并修改了引用计数。只有当新的数据发生更变的时候,才减少源内存的引用计数并进行真正的拷贝。这样做可以大幅减少内存拷贝的代码,是很有意义的。例如你进行

Row::operator[] 返回String类型的话,将不会进行真正的内存拷贝。

3.9. 处理二进制数据

一个SQL新手很容易犯的错误就是将数据库服务器看成是一个文件系统。一个平台内部的确将数据库引擎用作一个文件系统,但是SQL基本设计意图不是这个工作。如果你有一个更好一些的文件管理系统,你应该用它去管理那些庞大的,完整的文件块以及二进制流数据。

一个常见的例子是用户讨论数据库后台的WEB应用程序。如果你用数据库保存这些图片信息,那意味着你要编码从数据库中读取这些数据并发送给客户端,这将更加低效的使用了的

I/O Cache 系统。如果你将这些图片信息保存在文件系统中,你需要做的只是告诉WEB服务器这个图片位置,并在你生成的HTML中为图片设置一个URL,因为你给了WEB服务器一个直接访问硬盘的通道,操作系统将更加高效的进行存取操作,WEB服务器将可以很方便的从硬盘中取得数据并发送到网络中。另外补充一句,你需要避免将这些数据通过高级语言进行传输,典型的避免使用解释性语言。 当然,还有许多人会坚持这点,认为使用数据库引擎具有更高的安全性,其实操作系统和WEB服务器同样可以象数据库系统一样进行安全正确的数据控制管理。

当然有时候你或许真需要存储一些庞大的二进制数据到数据库中。考虑到这点,近代的一些SQL数据库服务支持BLOB数据格式,用来支持Binary Large Object。有时候BLOB也简称为 Binary data,二进制数据。

在MySQL++中,处理二进制数据还是相对容易的,没有 C string 那么复杂。C string 中将空字符串视为一个特殊字符’/0’结尾的字符,但是再二进制数据中没有这种规定。我们下面将一些例子来说明,如果去正确处理BLOB数据。

从二进制文件中读取数据保存到BLOB字段中

在上文,我认为将图像数据存储到数据库中是不正确的,特别是在WEB应用中。但是下面我们仅仅是用图像存储进行讲解。

我们不再使用简单的例子,这里我们首先对JPEG格式图像数据进行分析,将其载入到内存中,然后保存到数据库的BLOB字段里。

下面是examples/load_:

#include "cmdline.h"

#include "printdata.h"

#include

#include

using namespace std;

using namespace mysqlpp;

extern int ag_optind;

// 检查文件头标识是否是JPEG格式文件

static bool is_jpeg(const unsigned char* img_data)

{

return (img_data[0] == 0xFF) && (img_data[1] == 0xD8) &&

((memcmp(img_data + 6, "JFIF", 4) == 0) ||

(memcmp(img_data + 6, "Exif", 4) == 0));

}

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,

"[jpeg_file]"))

{

return 1;

}

try

{

// 建立和数据库服务器的连接

mysqlpp::Connection con(db, server, user, pass);

// 假设命令行参数最后是一个文件名。

// 将文件数据读取到img_data 中,然后检查是否是JPEG格式文件。

string img_name, img_data;

if (argc - ag_optind >= 1)

{

img_name = argv[ag_optind];

ifstream img_file(img_name.c_str(), ios::ate);

if (img_file)

{

size_t img_size = img_();

if (img_size > 10)

{

img_(0, ios::beg);

unsigned char* img_buffer = new unsigned char[img_size];

img_(reinterpret_cast(img_buffer),

img_size);

if (is_jpeg(img_buffer))

{

img_(

reinterpret_cast(img_buffer),

img_size);

}

else

{

cerr << '"' << img_file <<

"" isn't a JPEG!" << endl;

}

delete[] img_buffer;

}

else

{

cerr << "File is too short to be a JPEG!" << endl;

}

}

}

if (img_())

{

print_usage(argv[0], "[jpeg_file]");

return 1;

}

// 将图形数据填充到BLOB 字段中。

// 我们将img_data视为一个类似std::string类型的数据进行存储。

// 但并没有使用类似C string一样的'/0'特殊标识。

Query query = ();

query << "INSERT INTO images (data) VALUES("" <<

mysqlpp::escape << img_data << "")";

SimpleResult res = e();

// 如果执行到这里,插入成功

cout << "Inserted "" << img_name <<

"" into images table, " << img_() <<

" bytes, ID " << _id() << endl;

}

catch (const BadQuery& er)

{

// 查询错误

cerr << "Query error: " << () << endl;

return -1;

}

catch (const BadConversion& er)

{

// 数据转换错误

cerr << "Conversion error: " << () << endl <<

"tretrieved data size: " << ved <<

", actual size: " << _size << endl;

return -1;

}

catch (const Exception& er)

{

// 所有其他的SQL++错误

cerr << "Error: " << () << endl;

return -1;

}

return 0;

}

注意,我们在上面创建INSERT查询时使用了 mysqlpp::escape 标识符。因为

mysqlpp::sql_blob 仅仅是MySQL++ string的一个别名,它其实完全就是 String 类型。

从BLOB字段中获取图像

这有一个也非常短小的例子,它从一个给定的字符串中CGI解析得到图像ID。然后根据ImageID从数据库中获取到详细的图像信息。

这个例子是 examples/cgi_ :

#include "cmdline.h"

#include "images.h"

#define CRLF "rn"

#define CRLF2 "rnrn"

int main(int argc, char* argv[])

{

mysqlpp::examples::CommandLine cmdline(argc, argv, "root",

"nunyabinness");

if (!cmdline)

{

return 1;

}

// 解析CGI字符串环境变量以获得图像ID

unsigned int img_id = 0;

char* cgi_query = getenv("QUERY_STRING");

if (cgi_query)

{

if ((strlen(cgi_query) < 4) || memcmp(cgi_query, "id=", 3))

{

std::cout << "Content-type: text/plain" << std::endl << std::endl;

std::cout << "ERROR: Bad query string" << std::endl;

return 1;

}

else

{

img_id = atoi(cgi_query + 3);

}

}

else

{

std::cerr << "Put this program into a web server's cgi-bin "

"directory, then" << std::endl;

std::cerr << "invoke it with a URL like this:" << std::endl;

std::cerr << std::endl;

std::cerr << " /cgi-bin/cgi_jpeg?id=2" <<

std::endl;

std::cerr << std::endl;

std::cerr << "This will retrieve the image with ID 2." << std::endl;

std::cerr << std::endl;

std::cerr << "You will probably have to change some of the #defines "

"at the top of" << std::endl;

std::cerr << "examples/cgi_ to allow the lookup to work." <<

std::endl;

return 1;

}

// 根据ID从数据库中获取Image信息

try

{

mysqlpp::Connection con(mysqlpp::examples::db_name,

(), (), ());

mysqlpp::Query query = ();

query << "SELECT * FROM images WHERE id = " << img_id;

mysqlpp::StoreQueryResult res = ();

if (res && _rows())

{

images img = res[0];

if (_null)

{

std::cout << "Content-type: text/plain" << CRLF2;

std::cout << "No image content!" << CRLF;

}

else

{

std::cout << "X-Image-Id: " << img_id << CRLF;

std::cout << "Content-type: image/jpeg" << CRLF;

std::cout << "Content-length: " <<

() << CRLF2;

std::cout << ;

}

}

else

{

std::cout << "Content-type: text/plain" << CRLF2;

std::cout << "ERROR: No image with ID " << img_id << CRLF;

}

}

catch (const mysqlpp::BadQuery& er)

{

std::cout << "Content-type: text/plain" << CRLF2;

std::cout << "QUERY ERROR: " << () << CRLF;

return 1;

}

catch (const mysqlpp::Exception& er)

{

std::cout << "Content-type: text/plain" << CRLF2;

std::cout << "GENERAL ERROR: " << () << CRLF;

return 1;

}

return 0;

}

当你亲自运行中这个例子的时候,你最好将它安装到一个WEB服务器CGI项目目录下,然后调用一个URL例如:/cgi-bin/cgi_jpeg?id=1 .它将会从数据库查询到ID为1的一个JPEG图片并发送给WEB服务器然后进行公布。

我们已经可以使用MySQL++进行读取和存储图像二进制流数据了,你还可以尝试自己存储下其他的图片,例如 examples/

3.10. 使用事务

MySQL++的事务类比直接使用SQL的事务更加安全。通常你在堆栈中创建一个事务,然后将执行事务中的查询,你可以调用 Transaction::commit() 函数提交事务中的查询组。如果在你提交之前,事务对象已经出了自己的有效范围,该事务将会进行回滚。这可以确保在下一个事务处理之前,如果有错误可以及时抛出。

这里我们可以看 examples/ 的代码如下:

#include "cmdline.h"

#include "printdata.h"

#include "stock.h"

#include

#include

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,

(), (), ());

// 获取数据库基本初始状态

mysqlpp::Query query = ();

cout << "Initial state of stock table:" << endl;

print_stock_table(query);

// 增加一行事务

{

// 使用一个比MySQL默认隔离级别高的事务。

// 我们要求这个事务高于所有其他的DB连接事务,

// 所以这个事物也会影响下一个事务,即使这个事务我们没有托付。

mysqlpp::Transaction trans(con,

mysqlpp::Transaction::serializable,

mysqlpp::Transaction::session);

stock row("Sauerkraut", 42, 1.2, 0.75,

mysqlpp::sql_date("2006-03-06"), mysqlpp::null);

(row);

e();

cout << "nRow inserted, but not committed." << endl;

cout << "Verify this with another program (e.g. simple1), "

"then hit Enter." << endl;

getchar();

cout << "nCommitting transaction gives us:" << endl;

();

print_stock_table(query);

}

// 测试回滚

{

// 进行一个新的事务,设置和上一个事务同样的隔离级别,直到上个事务释放session锁。

mysqlpp::Transaction trans(con);

cout << "nNow adding catsup to " << endl;

stock row("Catsup", 3, 3.9, 2.99,

mysqlpp::sql_date("2006-03-06"), mysqlpp::null);

(row);

e();

}

cout << "nNo, yuck! We don't like catsup. Rolling it back:" <<

endl;

print_stock_table(query);

}

catch (const mysqlpp::BadQuery& er)

{

cerr << "Query error: " << () << endl;

return -1;

}

catch (const mysqlpp::BadConversion& er)

{

cerr << "Conversion error: " << () << endl <<

"tretrieved data size: " << ved <<

", actual size: " << _size << endl;

return -1;

}

catch (const mysqlpp::Exception& er)

{

cerr << "Error: " << () << endl;

return -1;

}

return 0;

}

如果两个进程都想使用同一个事务对同样的两行数据进行存储访问,但是两者操作又无法兼容,此时可能会产生死锁。两者各自持有其中一行数据的锁权,同时它们又得不到另外一行数据的锁权。

MySQL服务器可以发现这个问题,但是我们最好的解决方法也只能是中断第二个事务,这样就可以允许第一个事务正常执行,但是这样第二个事务所在的进程就必须进行事务处理失败的处理了。

在MySQL++里,出现死锁状态,默认情况下会获得这个异常,通过 Query::errnum() 可以得到一个 ER_LOCK_DEADLOCK 异常。但是实际上,事务处理的结果返回值可能是 0,意味着“没错误”。这是为什么呢。因为,MySQL++底层在捕获这个异常后,默认会实现一个回滚查询,所以,你得到的返回值,不再是出现死锁的那个事务返回值,而是回滚查询事务的处理返回值,所以,在MySQL++里不是所有的死锁事务一定会引发一个异常。

但是这个异常我们希望得知,并不希望被隐藏,我们需要对另外一个错误进行检查。不再是

Connection::errnum() 而是Query::errnum()。请查看 examples/ 说明这个问题,代码如下:

#include "cmdline.h"

#include

#include

#include

using namespace std;

extern int run_mode;

int main(int argc, char *argv[])

{

// 从控制台获取数据库参数

mysqlpp::examples::CommandLine cmdline(argc, argv);

if (!cmdline)

{

return 1;

}

// 检查命令行参数是否有意义

const int run_mode = _mode();

if ((run_mode != 1) && (run_mode != 2)) {

cerr << argv[0] << " must be run with -m1 or -m2 as one of "

"its command-line arguments." << endl;

return 1;

}

mysqlpp::Connection con;

try {

// 创建一个和数据库服务器的连接

mysqlpp::Connection con(mysqlpp::examples::db_name,

(), (), ());

// 创建一个事务列。

// 这个事务将为修改的行数据创建锁,一旦两个程序在同一时间意图修改同一行,

// 将出现死锁。MySQL捕获这个问题将通知MySQL++ 使其抛出一个BadQuery

的异常。

// 如果你系那个捕获这个异常,你需要检查BadQuery::errnum() 而不是Connection::errnum(),

// 因为MySQL++创建的回滚查询可能已经执行成功。只有之前的死锁查询才知道本次事务曾失败过。

mysqlpp::Query query = ();

mysqlpp::Transaction trans(con);

// 创建并执行查询。

// 只有当第一个程序实例输入回车,否则第二个程序就一直处于死锁状态

char dummy[100];

for (int i = 0; i < 2; ++i) {

int lock = run_mode + (run_mode == 1 ? i : -i);

cout << "Trying lock " << lock << "..." << endl;

query << "select * from deadlock_test" << lock <<

" where x = " << lock << " for update";

();

cout << "Acquired lock " << lock << ". Press Enter to ";

cout << (i == 0 ? "try next lock" : "exit");

cout << ": " << flush;

e(dummy, sizeof(dummy));

}

}

catch (mysqlpp::BadQuery e)

{

if (() == ER_LOCK_DEADLOCK)

{

cerr << "Transaction deadlock detected!" << endl;

cerr << "Connection::errnum = " << () <<

", BadQuery::errnum = " << () << endl;

}

else

{

cerr << "Unexpected query error: " << () << endl;

}

return 1;

}

catch (mysqlpp::Exception e)

{

cerr << "General error: " << () << endl;

return 1;

}

return 0;

}

这个例子和其他的例子有些不同。你需要先执行一次这个程序实例,它会暂停等待你输入Enter,你需要再执行一次这个程序实例,此时才会有其中一个实例发生死锁异常,你可以看到错误信息输出。

3.11. 使用哪种查询方式?

MySQL++有三种主要的执行查询语句的方式:

Query::execute(), Query::store(), Query::use() ,什么情况下需要用哪个?

execute() 主要服务于那些没有数据返回值的查询。例如:CREATE INDEX。这个查询会通过MySQL获得一些信息,而 execute() 函数返回它的调用者一个 SimpleResult 对象,这个对象里包含了一些 例如 语句是否执行成功,行数 等信息也会提供给你。

如果你仅仅是想知道语句是否执行成功,而不需要其他 例如行数 这些信息的话,有一个更简单的接口 Query::exec() ,它会简单的返回一个 bool 告知是否执行成功。

如果你需要从数据库中获取一些数据信息,需要使用 store() 接口,(我们之前的许多例子均使用该接口),这个接口会返回一个 StoreQueryResult 对象,对象内记录了全部的结果表。这个 StoreQueryResult 对象继承于 std::vector< mysqlpp::Row > ,所以它可以使用STL操作方式去获取结果表,也可以很方便的使用下标访问,包括使用前迭代和后迭代等等。

如果你愿意将查询结构保存在一个STL容器中,但是不希望是 std::vector 中,你可以调用

Query::storein() ,这个接口允许你将结果保存到任何一种标准STL容器中,当然,这样的话,你将无法获得 StoreQueryResult 对象,同时也将丢失 StoreQueryResult 对象中的一些

其他附属信息。

使用store()接口的确很方便,但是它将消耗额外的内存,这可能是一个惊人的消耗。事实上,MySQL数据库将数据是保存在硬盘中的,它对客户端返回的是一个数据结构,对于MySQL++和底层的C API 库接口来说,它们有自己的一块内存区进行数据保存,所以,如果你有百万条数据,每条数据又大于1KB,那么很容易消耗上GB的内存。

对于这种巨大量级的查询,我们建议使用 use() 接口。它会返回一个 UseQueryResult 对象,它和 StoreQueryResult 很类似,但是它不具备 StoreQueryResult 的随机存储特性。它会要求数据库每次只返回一个数据。返回顺序是线性的,和Vertor提供的随机迭代正好相反,有些类似C++流中的输入迭代.

因为这种限制,你可能需要自己创建一些大点的容器去保存这些数据表。

我们看下使用 use() 接口的例子代码,在 examples/ 中。代码如下:

#include "cmdline.h"

#include "printdata.h"

#include

#include

#include

using namespace std;

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;

}

// 连接数据库

mysqlpp::Connection conn(false);

if (t(db, server, user, pass))

{

// 获取表stock 内全部数据并进行显示。这个例子类似simple2 那个例子。

// 但使用use() 接口查询。所以我们需要逐行进行输出。

// 无法像simple2 那样存储在一个内存容器中遍历输出。

mysqlpp::Query query = ("select * from stock");

if (mysqlpp::UseQueryResult res = ())

{

// 输出行首字符标识

(ios::left);

cout << setw(31) << "Item" <<

setw(10) << "Num" <<

setw(10) << "Weight" <<

setw(10) << "Price" <<

"Date" << endl << endl;

// 获取每一行结果,输出。

while (mysqlpp::Row row = _row())

{

cout << setw(30) << row["item"] << ' ' <<

setw(9) << row["num"] << ' ' <<

setw(9) << row["weight"] << ' ' <<

setw(9) << row["price"] << ' ' <<

setw(9) << row["sdate"] <<

endl;

}

if (())

{

cerr << "Error received in fetching a row: " <<

() << endl;

return 1;

}

return 0;

}

else

{

cerr << "Failed to get stock item: " << () << endl;

return 1;

}

}

else

{

cerr << "DB connection failed: " << () << endl;

return 1;

}

}

这个例子和 simple2 例子做了相同的事,仅仅是使用 use() 接口替代了 store() 接口。

就如 use() 接口的设计思路一样,你在解决问题的时候,不应该试图第一时间去考虑如果使用内存解决这些问题。如果能够不将这么庞大的数据保存在内存中,将会更好。或许你会说

SELECT * 得到的结果并不都是你所需要的,你还要使用C++代码去清除一些不需要的数据,那么建议你在查询之前就做出筛选,例如对 SELECT 加上 WHERE 限制,这不仅仅节省了内存,同时也减少了数据库服务器和客户端之间的带宽负载,同样也节省了CPU时间周期。

如果你所需要的限制不能用一个 WHERE 简单的进行限制,请读下一章节。

3.12.处理带条件的结果行

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

例子 examples/store_ 代码如下:

#include "cmdline.h"

#include "printdata.h"

#include "stock.h"

#include

#include

#include

// 定义一个我们进行测试限制的结构

struct is_prime

{

// 根据Num 进行取舍,只留下质数行有效

bool operator()(const stock& s)

{

if (( == 2) || ( == 3))

{

return true;

}

else if (( < 2) || (( % 2) == 0))

{

return false;

}

else

{

for (int i = 3; i <= sqrt(double()); i += 2)

{

if (( % 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 results;

mysqlpp::Query query = ();

_if(results, stock(), is_prime());

// 显示结果

print_stock_header(());

std::vector::const_iterator it;

for (it = (); it != (); ++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: " << () << std::endl;

return 1;

}

catch (const mysqlpp::Exception& er)

{

std::cerr << "Error: " << () << std::endl;

return 1;

}

return 0;

}

我猜未必有人真的需要获取一个表内的质数行数据。我举这个例子只是告诉 Query::store_if()

的用法。而这个限制,你使用SQL的WHERE限制可能无法做到。在我们这个库里你将获

得许多有用的高效方式。

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

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

例子可参考 example/for_ ,代码如下

#include "cmdline.h"

#include "printdata.h"

#include "stock.h"

#include

#include

#include

// 定义一个函数进行结果表的统计处理

class gather_stock_stats

{

public:

gather_stock_stats() :

items_(0),

weight_(0),

cost_(0)

{

}

void operator()(const stock& s)

{

items_ += ;

weight_ += ( * );

cost_ += ( * );

}

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 << _ << " items " <<

"weighing " << _ << " stone and " <<

"costing " << _ << " 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 = ();

std::cout << "There are " << _each(stock(),

gather_stock_stats()) << '.' << std::endl;

}

catch (const mysqlpp::BadQuery& e)

{

std::cerr << "Query failed: " << () << std::endl;

return 1;

}

catch (const mysqlpp::Exception& er)

{

std::cerr << "Error: " << () << 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/ 代码如下:

#include "cmdline.h"

#include "printdata.h"

#include

#include

#include

#include

using namespace std;

using namespace mysqlpp;

typedef vector IntVectorType;

static void print_header(IntVectorType& widths, StoreQueryResult& res)

{

cout << " |" << setfill(' ');

for (size_t i = 0; i < _names()->size(); i++) {

cout << " " << setw((i)) << _name(int(i)) << " |";

}

cout << endl;

}

static void print_row(IntVectorType& widths, Row& row)

{

cout << " |" << setfill(' ');

for (size_t i = 0; i < (); ++i) {

cout << " " << setw((i)) << row[int(i)] << " |";

}

cout << endl;

}

static void print_row_separator(IntVectorType& widths)

{

cout << " +" << setfill('-');

for (size_t i = 0; i < (); i++) {

cout << "-" << setw((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 = ();

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 = _fields();

for (size_t i = 0; i < size; i++) {

_back(max(

(i).max_length(),

_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 = ();

print_result(res, 0);

for (int i = 1; _results(); ++i) {

res = _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;

_option(new MultiStatementsOption(true));

// 连接数据库

if (!t(mysqlpp::examples::db_name, (),

(), ())) {

return 1;

}

// 设置多重查询

Query 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 << () << 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: " <<

() << endl;

return 1;

}

catch (const Exception& er)

{

cerr << "Error: " << () << 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 = ("select garbage from plink where foobie='tamagotchi'");

UseQueryResult r2 = ("select blah from bonk where bletch='smurf'");

这段代码中,第二个use()将调用失败,因为第一个use()结果可能还未执行完毕。

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

3.17.获取字段源数据

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

#include "cmdline.h"

#include "printdata.h"

#include

#include

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,

(), (), ());

// 获取表内信息

mysqlpp::Query query = ("select * from stock");

mysqlpp::StoreQueryResult res = ();

// 显示表中每一字段信息

char widths[] = { 12, 22, 46 };

(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 < _names()->size(); i++)

{

const char* cname = _type(int(i)).name();

mysqlpp::FieldTypes::value_type ft = _type(int(i));

ostringstream os;

os << _name() << " (" << () << ')';

cout << setw(widths[0]) << _name(int(i)).c_str() <<

setw(widths[1]) << () <<

setw(widths[2]) << cname <<

endl;

}

cout << endl;

// 简单的类型检查

if (_type(0) == typeid(string))

{

cout << "SQL type of 'item' field most closely resembles "

"the C++ string type." << endl;

}

if (_type(5) == typeid(string))

{

cout << "Should not happen! Type check failure." << endl;

}

else if (_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 " <<

_type(5).name() << endl;

cout << "Did something recently change in resetdb?" << endl;

}

}

catch (const mysqlpp::BadQuery& er)

{

cerr << "Query error: " << () << endl;

return -1;

}

catch (const mysqlpp::Exception& er)

{

cerr << "Error: " << () << endl;

return -1;

}

return 0;

}

在 () 之前我们创建模板,然后我们对模板查询设置参数,最后我们才真正的执行查询。

我们接下来讲的详细一些。

4.1. 创建模板查询

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

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

();

占位符的格式是:

%###(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 = ("Dinner Rolls", "item", "item", "price")

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

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

4.3. 默认参数

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

te_defaults[2] = "item";

te_defaults["wheref"] = "item";

做的是一样的事情。

这样设计有点像C++函数默认参数,如果你设置了一个查询的默认参数,那么你将可以免去设置查询的部分参数,例如,如果查询有四个参数,而你为最后的三个设置了默认参数,你将可以只设置第一个参数,就可以顺利执行这个模板查询。

这个特性将在查询时有许多不常更变参数时很意义,例如:

te_defaults["field1"] = "item";

te_defaults["field2"] = "price";

StoreQueryResult res1 = ("Hamburger Buns", "item");

StoreQueryResult res2 = (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这俩参数不同,默认参数则发挥了其作用。

当然,你也可以这么做:

te_defaults["what"] = "Hamburger Buns";

te_defaults["wheref"] = "item";

te_defaults["field1"] = "item";

te_defaults["field2"] = "price";

StoreQueryResult res1 = ();

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

4.4. 错误处理

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

te_defaults["field1"] = "item";

te_defaults["field2"] = "price";

StoreQueryResult res = (1.25);

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

5. SQL 专属结构

专属SQL结构(SSQLS)特性允许你方便简单的将定义的C++结构存储到SQL表中。从表面上来看,一个SSQLS每一个成员变量对应了SQL表中的每一个字段。但是,其实SSQLS里同样有一些函数方法,操作和MySQL++使用的数据成员,这些我们将在这一章节进行讲解。

你可以通过 ssqls.h 里面的一些宏来定义 SSQLS 结构。这是唯一一个没有被 mysql++.h 头文件包含的MySQL++头文件。如果你使用了SSQLS特性,你需要自己在项目中包含这个头文件。

5.1. sql_create

我们看一下下面的这个SQL表结构

CREATE TABLE stock (

item CHAR(30) NOT NULL,

num BIGINT NOT NULL,

weight DOUBLE NOT NULL,

price DECIMAL(6,2) NOT NULL,

sdate DATE NOT NULL,

description MEDIUMTEXT NULL)

你可以创建一个类似于该表的C++结构,如下:

sql_create_6(stock, 1, 6,

mysqlpp::sql_char, item,

mysqlpp::sql_bigint, num,

mysqlpp::sql_double, weight,

mysqlpp::sql_decimal, price,

mysqlpp::sql_date, sdate,

mysqlpp::Null, description)

声明 stock 结构,然后为SQL每一列声明一个同名的数据成员。这个结构同样还有一个成员函数,操作着隐藏的数据成员,我们暂时不讲。

在参数之前我们调用了一个 sql_create ,然后使用了C++数据类型保存了SSQLS的数据。当然,这里没有使用标准C++类型,我们更希望你使用MySQL++定义的数据类型,而不是标准C++的。

有时候你不得不用MySQL++内的数据类型,例如 SQL 里的NULL,这个“未定义”值是无法使用C++类型声明定义的。但是MySQL++里等译了Null模板,可以区分两种不同的Null.

Sql_create 宏定义如下

sql_create_#(NAME, COMPCOUNT, SETCOUNT, TYPE1, ITEM1, ... TYPE#, ITEM#)

其中NAME是你准备创建的结构,TYPEx 是你创建的字段类型,ITEMx 则是你创建的字段名称。其中的 COMPCOUNT 和 SETCOUNT 我们在下面的章节说明。

5.2. SSQLS的初始化和比较

Sql_create 宏其实定义了成员函数以便你和另外一个SSQLS实例进行比较。这个函数实际比较的是两个SSQLS结构中的 COMPCOUNT

编号的变量。在上面那个例子里,COMPCOUNT 是 1,所以如果两个SSQLS实例比较的话,则仅仅比较的是 item 这个字段的值。如果COMPCOUNT是2,在上面的那个例子里,比对俩实例的时候,则需要比较 item 和 num 这两个字段的值。

所以非常推荐,你将SQL中“Key”字段作为 COMPCOUNT。

COMPCOUNT必须大于等于1。

另外 sql_create 函数不能创建一个没有 COMPCOUNT 的 SSQLS 对象。

我们也可以使用容器和STL数学方法进行结果的一些查询处理,就如同 SQL 容器的处理一样:

std::set result;

n(result);

cout << _bound(stock("Hamburger"))->item << endl;

这将输出以 “Hamburger” 开头的结果数据的 item 字段。

Sql_create 的第三个参数是 SETCOUNT。如果这个值不是 0, 它将为结构增加一个初始化构造函数,同时增加一个 set() 成员函数,可以初始化结构中的前几个参数。我们看下之前的例子,现在我们修改一下 SETCOUNT ,如下:

sql_create_6(stock, 1, 2,

mysqlpp::sql_char, item,

mysqlpp::sql_bigint, num,

mysqlpp::sql_double, weight,

mysqlpp::sql_decimal, price,

mysqlpp::sql_date, sdate,

mysqlpp::Null, description)

stock foo("Hotdog", 52);

除了一个2个参数的构造函数之外,这个SSQLS结构内其实还有一个两个参数的set()成员函数,作用类似于这个构造函数。

注意,COMPCOUNT 和 SETCOUNT 的值不能相同。如果它们相同的话,这个宏将创建两个同样参数列表的初始化构造函数,这在C++中是禁止的。你可能会问,为什么要为比较这个功能做一个构造函数?这是为了方便我们使用类似 x == stock(“HotDog”) 的操作。这就要求我们使用 COMPCOUNT 个参数创建一个构造函数。

这个限制在实际中并不会带来太多问题,如果你需要同样数量的参数进行比较,也需要同样数量的参数进行初始化,你只需要将 SETCOUNT 设置为 0,这样的话,就不会出现上述的冲突了,但是SETCOUNT个参数的初始化构造函数依旧存在了(被COMPCOUNT创建了),例如

sql_create_1(stock_item, 1, 0, mysqlpp::sql_char, item)

-----------------

TODO:大运放假,回去休息弄别的代码了。

-----------------

2011-9-18 FreeKnight Add

开启新章节。请参考MySQL++ V3.1.9 用户手册<六>

5.3 找回数据

我们对SSQLS进行应用,下面是一个例子在 examples/ 中

#include "cmdline.h"

#include "printdata.h"

#include "stock.h"

#include

#include

using namespace std;

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);

// 从Stock表中获取一个表的部分字段信息,并将这个结果保存在一个Stock的SSQLS结构数组中。

mysqlpp::Query query = ("select item,description from stock");

vector res;

n(res);

// 显示这些元素单元

cout << "We have:" << endl;

vector::iterator it;

for (it = (); it != (); ++it)

{

cout << 't' << it->item;

if (it->description != mysqlpp::null)

{

cout << " (" << it->description << ")";

}

cout << endl;

}

}

catch (const mysqlpp::BadQuery& er)

{

cerr << "Query error: " << () << endl;

return -1;

}

catch (const mysqlpp::BadConversion& er)

{

cerr << "Conversion error: " << () << endl <<

"tretrieved data size: " << ved <<

", actual size: " << _size << endl;

return -1;

}

catch (const mysqlpp::Exception& er)

{

cerr << "Error: " << () << endl;

return -1;

}

return 0;

}

// 下面是例子中依赖到的 stock.h 头文件

#include

#include

// 下面的代码将被多次使用,因为我们其他例子可能还需要不停的创建“stock结构”,这个结构一定具备下述成员变量

//

// sql_char item;

// ...

// Null description;

//

// 这个函数可以创建一个MySQL行。

sql_create_6(stock,

1, 6, // 这行的意义请参考上一节介绍。

mysqlpp::sql_char, item,

mysqlpp::sql_bigint, num,

mysqlpp::sql_double, weight,

mysqlpp::sql_double, price,

mysqlpp::sql_date, sdate,

mysqlpp::Null, description)

这个例子有点类似 那个例子(参见3.2章节),但是这里使用了一个高级的数据结构来替代3.2例子中的低级结构。另外,这个例子额外使用了MySQL++的异常抛出替代了原本的错误判断,在这个短小的例子中,你不会发现MySQL++异常抛出的意义,但如果你在编写一个庞大的项目,你将会发现MySQL++的异常抛出比自己定义错误码以及错误处理更加方便和强大。

你会注意到,我们只是用了stock表内的部分列内信息,但是保存时却保存在一个更多列的stock结构中。这可能让你感觉很不习惯,当然,你可以创建一个小一些的SSQLS结构,如下:

sql_create_1(stock_subset,

1, 0,

string, item)

vector res;

n(res);

MySQL++可以很方便自由的组件SSQLS。它就有点类似于一个网页编程,一个设计就可以支持世界上大部分的系统,仅仅裁决于你的浏览器是否可以解析这个规则。你可以通过一个查询结果来填充一个SSQLS,并且允许查询结果中没有SSQLS的部分列,但是MySQL++就类似浏览器一样会默认的为这些空结果列填写一个默认值。(默认会对数字类型字段填充0,bool类型字段填充false,对于一些特殊类型填充一些其他指定值:例如对

mysqlpp::DateTime 类型会填写系统默认值。)

但是请注意,当查询结果列数和SSQLS列数不同时,MySQL++3.0之前版本进行填充时可能会抛出一些错误,更早期版本可能会引发一个数据丢失,但不会抛出错误。

通常情况下,我们查询语句得到的数据会比SSQLS列更多,此时,多出的查询结果列将被忽略。

因为MySQL是一个网络数据库服务器,意味着将有大量连接。我们无法保证大量的客户端不会在同时进行更新操作,而你得到的查询结果还需要进行一次向SSQLS内的拷贝操作,这意味着你得到的数据未必是最新的(可能已被其他客户端修改)。这样的拷贝将带来一些问题,但是考虑到进行预处理操作带来的当机错误可能,我们不得不这么做,这个我们将在

后期考虑去进行改进。

5.4 增加数据

MySQL++提供了许多将SSQLS内数据写入数据库表中的方式。

----------------------------

增加一个单行

最简单的方式就是一次增加一行数据,请参见例子 examples/:

#include "cmdline.h"

#include "printdata.h"

#include "stock.h"

#include

using namespace std;

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);

// 创建一个SSQLS的Stock对象,我们也可是使用set()函数,它可以类似下面的构造函数一样进行变量设置

stock row("Hot Dogs", 100, 1.5, 1.75,

mysqlpp::sql_date("1998-09-25"), mysqlpp::null);

// 执行一个查询将数据插入stock表中

mysqlpp::Query query = ();

(row);

// 显示将被执行的查询语句

cout << "Query: " << query << endl;

// 执行语句,我们使用的是 INSERT ,不需要结果表,所以可以使用 execute() 函数。

e();

// 查新出新的表内数据

print_stock_table(query);

}

catch (const mysqlpp::BadQuery& er)

{

// 处理查询错误

cerr << "Query error: " << () << endl;

return -1;

}

catch (const mysqlpp::BadConversion& er)

{

// 处理转换失败错误

cerr << "Conversion error: " << () << endl <<

"tretrieved data size: " << ved <<

", actual size: " << _size << endl;

return -1;

}

catch (const mysqlpp::Exception& er)

{

// 处理其他的MySQL++异常

cerr << "Error: " << () << endl;

return -1;

}

return 0;

}

这就是我们需要做的全部过程,足够简单吧。MySQL++甚至将在使用SSQLS时容易由于引用和空格引发的问题都替我们解决了。

----------------------------

插入多行

插入一个单行很有用,但是,你或许希望能够一次插入多行或者多个SSQLS。

最保守的方式,你可以写个循环,然后一条一条写入:)当然,MySQL++提供了一个更加高效的实现方式,你可以从一个查询就完成这个插入操作,唯一的不同仅仅是在insert()操作时,你可以这么写

vector LotsOfStuff;

... // 填充这个vector

((), ()).execute();

顺带一说,你可以继续加强 query 的操作,因为 query 的insert等操作会返回 *this 指针。

----------------------------

关于MySQL的包大小限制

上面的使用两个迭代访问有一个风险:MySQL对SQL查询有一个大小限制。默认限制为1MB。你可以调整这个限制,但是这个限制依然不允许调整到非常高的一个值。所以除非你真的是一行内数据大于1MB而不得不调整,否则避免进行重新设置。你如果一次性插入越多的数据,将更大概率出现错误,最糟糕的情况下,可能发生数据丢失。

所以,如果你想一次性写入数MB的数据,建议还是考虑其他方式。当然你可以自己写个循环,逐条插入,但是这样很慢,因为你将为每一条查询付出代价。你可能会想到使用insert()包含两个迭代,保证两个迭代之间的数据大小接近1MB,然后进行分块插入。这样,即减

少了进行查询的次数,也避免了一次性写入带来的危险。

当前MySQL++也想到了这一点。你可以使用 Query::insertfrom()。之所以不称之为inset(),而另外起了一个函数名,是因为它不会创建一个 INSERT 查询,你在execute()执行的时候,它更类似一个 storein(),它将我们的分块插入封装成了一个函数调用,我们现在来看一个例子来更好的了解它,请参考例子 examples/:

#include "cmdline.h"

#include "printdata.h"

#include "stock.h"

#include

using namespace std;

// 将一个tab制表符分割的一行文本 分割为 多个字符串

static size_t tokenize_line(const string& line, vector& strings)

{

string field;

();

istringstream iss(line);

while (getline(iss, field, 't'))

{

_back(mysqlpp::String(field));

}

return ();

}

// 读取一个Tab制表符分割文件,将返回数据填充到一个 SSQLS 对象向量中

static bool read_stock_items(const char* filename, vector& stock_vector)

{

ifstream input(filename);

if (!input) {

cerr << "Error opening input file '" << filename << "'" << endl;

return false;

}

string line;

vector strings;

while (getline(input, line)) {

if (tokenize_line(line, strings) == 6) {

stock__back(stock(string(strings[0]), strings[1],

strings[2], strings[3], strings[4], strings[5]));

}

else {

cerr << "Error parsing input line (doesn't have 6 fields) " <<

"in file '" << filename << "'" << endl;

cerr << "invalid line: '" << line << "'" << endl;

}

}

return true;

}

int main(int argc, char *argv[])

{

mysqlpp::examples::CommandLine cmdline(argc, argv);

if (!cmdline) {

return 1;

}

// 从一个tab制表符分割的文件中读取一组stock数据

vector stock_vector;

if (!read_stock_items("examples/", stock_vector))

{

return 1;

}

try

{

// 尝试连接数据库服务器

mysqlpp::Connection con(mysqlpp::examples::db_name,

(), (), ());

// 清空整个 stock 表内全部数据

mysqlpp::Query query = ();

("DELETE FROM stock");

// 从CSV文件中读取数据,设置每次实际 insert() 最大许可1000字节。

// 我们这里设置的相对小一些,在实际项目中,你可以设置大一些。

mysqlpp::Query::MaxPacketInsertPolicy<> insert_policy(1000);

from(stock_(), stock_(),

insert_policy);

// 输出插入后的表内数据

print_stock_table(query);

}

catch (const mysqlpp::BadQuery& er)

{

cerr << "Query error: " << () << endl;

return -1;

}

catch (const mysqlpp::BadConversion& er)

{

cerr << "Conversion error: " << () << endl <<

"tretrieved data size: " << ved <<

", actual size: " << _size << endl;

return -1;

}


本文标签: 查询 数据 使用