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 #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::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 res; query.storein(res);

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 LotsOfStuff; … // 填充这个vector query.insert(LotsOfStuff.begin(), LotsOfStuff.end()).execute();

顺带一说,你可以继续加强 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 来限制它不创建事务,放弃回滚功能。