MySQL++ V3.1.0 用户手册6
MySQL++是一个针对MySQL C API的C++封装。它的目的是提供一个类似STL容易一样简单易用的接口,帮助你有效的避免在代码中使用复杂的SQL语句。
5.3 找回数据
我们对SSQLS进行应用,下面是一个例子在 examples/ssqls1.cpp 中
#include "cmdline.h"
#include "printdata.h"
#include "stock.h"
#include <iostream>
#include <vector>
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 = con.query("select item,description from stock");
vector<stock> res;
query.storein(res);
// 显示这些元素单元
cout << "We have:" << endl;
vector<stock>::iterator it;
for (it = res.begin(); it != res.end(); ++it)
{
cout << '\t' << it->item;
if (it->description != mysqlpp::null)
{
cout << " (" << it->description << ")";
}
cout << endl;
}
}
catch (const mysqlpp::BadQuery& er)
{
cerr << "Query error: " << er.what() << endl;
return -1;
}
catch (const mysqlpp::BadConversion& er)
{
cerr << "Conversion error: " << er.what() << endl <<
"\tretrieved data size: " << er.retrieved <<
", actual size: " << er.actual_size << endl;
return -1;
}
catch (const mysqlpp::Exception& er)
{
cerr << "Error: " << er.what() << endl;
return -1;
}
return 0;
}
// 下面是例子中依赖到的 stock.h 头文件
#include
// 下面的代码将被多次使用,因为我们其他例子可能还需要不停的创建“stock结构”,这个结构一定具备下述成员变量
//
// sql_char item;
// …
// Null
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::Nullmysqlpp::sql_mediumtext, description)
这个例子有点类似 Simlpe1.cpp 那个例子(参见3.2章节),但是这里使用了一个高级的数据结构来替代3.2例子中的低级结构。另外,这个例子额外使用了MySQL++的异常抛出替代了原本的错误判断,在这个短小的例子中,你不会发现MySQL++异常抛出的意义,但如果你在编写一个庞大的项目,你将会发现MySQL++的异常抛出比自己定义错误码以及错误处理更加方便和强大。 你会注意到,我们只是用了stock表内的部分列内信息,但是保存时却保存在一个更多列的stock结构中。这可能让你感觉很不习惯,当然,你可以创建一个小一些的SSQLS结构,如下:
sql_create_1(stock_subset,
1, 0,
string, item)
vector
MySQL++可以很方便自由的组件SSQLS。它就有点类似于一个网页编程,一个设计就可以支持世界上大部分的系统,仅仅裁决于你的浏览器是否可以解析这个规则。你可以通过一个查询结果来填充一个SSQLS,并且允许查询结果中没有SSQLS的部分列,但是MySQL++就类似浏览器一样会默认的为这些空结果列填写一个默认值。(默认会对数字类型字段填充0,bool类型字段填充false,对于一些特殊类型填充一些其他指定值:例如对 mysqlpp::DateTime 类型会填写系统默认值。) 但是请注意,当查询结果列数和SSQLS列数不同时,MySQL++3.0之前版本进行填充时可能会抛出一些错误,更早期版本可能会引发一个数据丢失,但不会抛出错误。
通常情况下,我们查询语句得到的数据会比SSQLS列更多,此时,多出的查询结果列将被忽略。 因为MySQL是一个网络数据库服务器,意味着将有大量连接。我们无法保证大量的客户端不会在同时进行更新操作,而你得到的查询结果还需要进行一次向SSQLS内的拷贝操作,这意味着你得到的数据未必是最新的(可能已被其他客户端修改)。这样的拷贝将带来一些问题,但是考虑到进行预处理操作带来的当机错误可能,我们不得不这么做,这个我们将在后期考虑去进行改进。
5.4 增加数据 MySQL++提供了许多将SSQLS内数据写入数据库表中的方式。
增加一个单行 最简单的方式就是一次增加一行数据,请参见例子 examples/ssqls2.cpp:
#include "cmdline.h"
#include "printdata.h"
#include "stock.h"
#include <iostream>
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 = con.query();
query.insert(row);
// 显示将被执行的查询语句
cout << "Query: " << query << endl;
// 执行语句,我们使用的是 INSERT ,不需要结果表,所以可以使用 execute() 函数。
query.execute();
// 查新出新的表内数据
print_stock_table(query);
}
catch (const mysqlpp::BadQuery& er)
{
// 处理查询错误
cerr << "Query error: " << er.what() << endl;
return -1;
}
catch (const mysqlpp::BadConversion& er)
{
// 处理转换失败错误
cerr << "Conversion error: " << er.what() << endl <<
"\tretrieved data size: " << er.retrieved <<
", actual size: " << er.actual_size << endl;
return -1;
}
catch (const mysqlpp::Exception& er)
{
// 处理其他的MySQL++异常
cerr << "Error: " << er.what() << endl;
return -1;
}
return 0;
}
这就是我们需要做的全部过程,足够简单吧。MySQL++甚至将在使用SSQLS时容易由于引用和空格引发的问题都替我们解决了。
插入多行 插入一个单行很有用,但是,你或许希望能够一次插入多行或者多个SSQLS。 最保守的方式,你可以写个循环,然后一条一条写入:)当然,MySQL++提供了一个更加高效的实现方式,你可以从一个查询就完成这个插入操作,唯一的不同仅仅是在insert()操作时,你可以这么写
vector
顺带一说,你可以继续加强 query 的操作,因为 query 的insert等操作会返回 *this 指针。
关于MySQL的包大小限制 上面的使用两个迭代访问有一个风险:MySQL对SQL查询有一个大小限制。默认限制为1MB。你可以调整这个限制,但是这个限制依然不允许调整到非常高的一个值。所以除非你真的是一行内数据大于1MB而不得不调整,否则避免进行重新设置。你如果一次性插入越多的数据,将更大概率出现错误,最糟糕的情况下,可能发生数据丢失。 所以,如果你想一次性写入数MB的数据,建议还是考虑其他方式。当然你可以自己写个循环,逐条插入,但是这样很慢,因为你将为每一条查询付出代价。你可能会想到使用insert()包含两个迭代,保证两个迭代之间的数据大小接近1MB,然后进行分块插入。这样,即减少了进行查询的次数,也避免了一次性写入带来的危险。 当前MySQL++也想到了这一点。你可以使用 Query::insertfrom()。之所以不称之为inset(),而另外起了一个函数名,是因为它不会创建一个 INSERT 查询,你在execute()执行的时候,它更类似一个 storein(),它将我们的分块插入封装成了一个函数调用,我们现在来看一个例子来更好的了解它,请参考例子 examples/ssqls6.cpp:
#include "cmdline.h"
#include "printdata.h"
#include "stock.h"
#include <fstream>
using namespace std;
// 将一个tab制表符分割的一行文本 分割为 多个字符串
static size_t tokenize_line(const string& line, vector<mysqlpp::String>& strings)
{
string field;
strings.clear();
istringstream iss(line);
while (getline(iss, field, '\t'))
{
strings.push_back(mysqlpp::String(field));
}
return strings.size();
}
// 读取一个Tab制表符分割文件,将返回数据填充到一个 SSQLS 对象向量中
static bool read_stock_items(const char* filename, vector<stock>& stock_vector)
{
ifstream input(filename);
if (!input) {
cerr << "Error opening input file '" << filename << "'" << endl;
return false;
}
string line;
vector<mysqlpp::String> strings;
while (getline(input, line)) {
if (tokenize_line(line, strings) == 6) {
stock_vector.push_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> stock_vector;
if (!read_stock_items("examples/stock.txt", stock_vector))
{
return 1;
}
try
{
// 尝试连接数据库服务器
mysqlpp::Connection con(mysqlpp::examples::db_name,
cmdline.server(), cmdline.user(), cmdline.pass());
// 清空整个 stock 表内全部数据
mysqlpp::Query query = con.query();
query.exec("DELETE FROM stock");
// 从CSV文件中读取数据,设置每次实际 insert() 最大许可1000字节。
// 我们这里设置的相对小一些,在实际项目中,你可以设置大一些。
mysqlpp::Query::MaxPacketInsertPolicy<> insert_policy(1000);
query.insertfrom(stock_vector.begin(), stock_vector.end(),
insert_policy);
// 输出插入后的表内数据
print_stock_table(query);
}
catch (const mysqlpp::BadQuery& er)
{
cerr << "Query error: " << er.what() << endl;
return -1;
}
catch (const mysqlpp::BadConversion& er)
{
cerr << "Conversion error: " << er.what() << endl <<
"\tretrieved data size: " << er.retrieved <<
", actual size: " << er.actual_size << endl;
return -1;
}
catch (const mysqlpp::BadInsertPolicy& er)
{
cerr << "InsertPolicy error: " << er.what() << endl;
return -1;
}
catch (const mysqlpp::Exception& er)
{
cerr << "Error: " << er.what() << endl;
return -1;
}
return 0;
}
这个例子里比较复杂的只是从一个CSV,Tab制表符分割文件中读取SSQLS数据。这里实际上最有意义的就两行代码:创建一个insert_policy对象,然后通过 Query::insertfrom(). 对每次插入进行一个分块限制。 这个policy(限制)对象是insertfrom()和insert()的最大区别。它控制了我们如何通过insertfrom()分段的创建一个查询语句。 MySQL++默认提供了三种不同的插入限制类,它们通常可以满足我们大部分需求。 - MaxPacketInsertPolicy:如上面的例子所示,功能也显而易见,当你创建这个对象之后,它将限制你每次创建查询语句中的语句大小,当达到了你指定的限制字节大小,或者达到了行尾,它会封装一个INSERT语句发送。 - SizeThresholdInsertPolicy:它和MaxPacketInsertPolicy不同之处在于,它能够保证一次INSERT最小大小为你指定的值,但是上限它将自动根据你的SQL环境进行调整。当你的数据内每一行都比较短小的时候(例如没有BLOB数据字段),使用该限制对象,将使你的插入更加高效和合理。 - RowCountInsertPolicy:这个是最简单的限制,你可以指定每次INSERT到数据库中的行数。如果你很清楚每一行数据的大小,你可以通过计算得到一个合理的值并通过这个限制进行控制。 如果上述限制你都不满意,你可以很轻松的定制一个限制对象,只要你学习一下 lib/insertpolicy.* 文件就可以了。
事务限制 默认情况下,insertfrom()这个封装一旦其中一个INSERT失败,这个数据库服务器将发生回滚,它本身就是创建了一个SQL事务。这可以有效避免在操作过程中某一个语句崩溃带来的“部分修改”问题,因为通常没有人会喜欢修改了一半的数据库。此时insertfrom()一旦任何一句失败,它将自动的从头重新执行一遍,当然你可以通过 NoTransactions 来限制它不创建事务,放弃回滚功能。