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
一个简单的执行查询的方式是使用 Query::use() ,它会返回一个 UseQueryResult 对象,这个类类似于一个STL的std::vector 输入迭代。你可以一次性处理结果表里的一行。当你不知道结果表内有多少结果的时候,可以迭代遍历到容器尾。这个特性允许我们获得更好的内存效能,因为查询出来的结果不用保存在RAM中,这对我们获得大容量的结果表时很有意义。
- 返回MySQL标准结构或自定义结构
使用MySQL++的数据结构来提取数据,的确比MySQL C API方便,但是你可以定一些自己的C++结构以承接数据库中的一些自定义数据。这样的话,就需要你在代码里加入一些原生的SQL代码。
如果你要用自己定义的C++结构,可以下面这样做:
vector
query << "SELECT * FROM stock";
n(v);
for (vector
cout << "Price: " << it->price << endl;
}
是不是很简单?
如果你不想创建SSQLSes去承接你的表结构,你可以使用MySQL++ v3提供的一种方式,使用 Mysqlpp::Row 去容载。
vector
query << "SELECT * FROM stock";
n(v);
for (vector
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
在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
cout << myfield << endl;
cout << int(myfield) << endl;
这样的话,第一行会输出”(NULL)”,第二行甚至不会被编译通过,而抛出一个错误
CannotConvertNullToAnyOtherDataType 。
但是,如果你希望存储一个标准的 unsigned char 类型的NULL,即0.你可以这样做
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_size);
if (is_jpeg(img_buffer))
{
img_(
reinterpret_cast
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
mysqlpp::Query query = ();
_if(results, stock(), is_prime());
// 显示结果
print_stock_header(());
std::vector
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
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
声明 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
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
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
n(res);
// 显示这些元素单元
cout << "We have:" << endl;
vector
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
//
// 这个函数可以创建一个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
这个例子有点类似 那个例子(参见3.2章节),但是这里使用了一个高级的数据结构来替代3.2例子中的低级结构。另外,这个例子额外使用了MySQL++的异常抛出替代了原本的错误判断,在这个短小的例子中,你不会发现MySQL++异常抛出的意义,但如果你在编写一个庞大的项目,你将会发现MySQL++的异常抛出比自己定义错误码以及错误处理更加方便和强大。
你会注意到,我们只是用了stock表内的部分列内信息,但是保存时却保存在一个更多列的stock结构中。这可能让你感觉很不习惯,当然,你可以创建一个小一些的SSQLS结构,如下:
sql_create_1(stock_subset,
1, 0,
string, item)
vector
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
... // 填充这个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
{
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
{
ifstream input(filename);
if (!input) {
cerr << "Error opening input file '" << filename << "'" << endl;
return false;
}
string line;
vector
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
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;
}
版权声明:本文标题:MySQL++V3.1.0 用户手册 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/free/1708339792h520466.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论