持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
本文是我之前在微信公众号上的一篇文章记录。原链接为:MySQL++学习笔记:流程及原理介绍
简介
本章内容主要学习mysqlpp的整体框架,mysqlpp也叫Mysql++是一个为MySQL设计的C++语言的API。Mysql++为Mysql的C-Api的再次封装,它用STL(Standard Template Language)开发并编写,并为C++开发者提供像操作STL容器一样方便的操作数据库的一套机制。
在上一篇文章我们运行了一个mysqlpp的例子,在例子中用了两个类:mysqlpp::Connection
和 mysqlpp::Query
。其实mysqlpp与平常的数据库访问流程一样:
- 1.打开一个连接:mysqlpp::Connection
- 2.构建和执行一个查询:mysqlpp::Query
- 3.如果成功就遍历查询结果
- 4.如果失败则处理异常
Connection
mysqlpp::Connection主要是负责数据库的连接事宜,它主要的方法就是“连接”,“断开连接”,“创建某个数据库",”drop某个数据库“(此二者是通过CREATE DATABASE和DROP DATABASE实现的),”查看某张 table 中的数据行数“,”关闭mysql服务等操作“。
查看Connection类定义,发现它继承自OptionalExceptions,通过查看源码,我看到OptionalExceptions就是一个对于一个表示“是否需要抛出异常”的变量的包装。在Connection类型的内容,会在出现错误的时候调用OpetionalExceptions.throw_exceptions( )方法来查看是否需要使用异常的手段来表示错误。
class MYSQLPP_EXPORT Connection : public OptionalExceptions
同时,Connection类还可以返回一个mysqlpp::Query类型,Query类主要负责数据库查询操作。
另外,可以看到返回Query类型时,必须要传入Connection本身(this),再查看Query类的实现,所有的操作都是基于这个Connection来进行操作,最后调用到mysqlpp::DBDriver类来进行真正的数据库操作。
Query
Connection::query(const char* qstr)
{
return Query(this, throw_exceptions(), qstr);
}
接着说回Connection类本身,Connection类其实也是一个代理类,它的操作都是调用mysqlpp::DBDriver类来完成的,真正调用mysql C接口的其实就是mysqlpp::DBDriver类。
另外,MySQL++ 支持客户端和服务器之间许多不同类型的数据连接:TCP/IP、Unix 域套接字和 Windows 命名管道。 通用 Connection 类支持所有这些,根据传递给 Connection::connect() 的参数确定您指的是哪一个。 但是,如果您事先知道您的程序只需要一种特定的连接类型,则可以使用具有更简单接口的子类。 例如,如果您知道您的程序将始终使用联网的数据库服务器,则可以直接使用 mysqlpp::TCPConnection。
Query
大多数情况下,都是使用由 Connection 对象创建的 Query 对象来创建 SQL 查询。 Query 充当标准的 C++ 输出流,因此可以像写入 std::cout 或 std::ostringstream 一样向其写入数据。这是 MySQL++ 提供的用于构建查询字符串的最 C++ 风格的方式。它包含了类型感知的流操作,因此很容易构建语法正确的 SQL。
mysqlpp::Connection con;
// Establish the connection to the database server
mysqlpp::Connection con(mysqlpp::examples::db_name,
cmdline.server(), cmdline.user(), cmdline.pass());
mysqlpp::Query query = con.query();
// Build and run the queries, with the order depending on the -m
// flag, so that a second copy of the program will deadlock if
// run while the first is waiting for Enter.
for (int i = 0; i < 2; ++i) {
int lock = run_mode + (run_mode == 1 ? i : -i);
query << "select * from deadlock_test" << lock <<
" where x = " << lock << " for update";
query.store();
}
Query 还有一个称为模板查询的功能,它的工作方式类似于 C 的 printf() 函数:您设置一个固定的查询字符串,其中包含指示插入可变部分的位置的标签。如果您有多个结构相似的查询,您只需设置一个模板查询,并在程序的不同位置使用它。
// Establish the connection to the database server.
mysqlpp::Connection con(mysqlpp::examples::db_name,
cmdline.server(), cmdline.user(), cmdline.pass());
// Modify an item using two named template query parameters
mysqlpp::Query query = con.query("update stock "
"set num = %0:quantity where num < %0:quantity");
query.parse();
query.template_defaults["quantity"] = 70;
cout << "Query: " << query << endl;
mysqlpp::SimpleResult result = query.execute();
构建查询的第三种方法是将 Query 与 SSQLS 一起使用。此功能允许您创建反映数据库模式的 C++ 结构。这些反过来又为 Query 提供了为您构建许多常见 SQL 查询所需的信息。它可以在给定 SSQLS 形式的数据的表中插入、替换和更新行。它还可以生成 SELECT * FROM SomeTable 查询并将结果存储为 SSQLSes 的 STL 集合。
// Establish the connection to the database server.
mysqlpp::Connection con(mysqlpp::examples::db_name,
cmdline.server(), cmdline.user(), cmdline.pass());
// Create and populate a stock object. We could also have used
// the set() member, which takes the same parameters as this
// constructor.
stock row("Hot Dogs", 100, 1.5,
numeric_limits<double>::infinity(), // "priceless," ha!
mysqlpp::sql_date("1998-09-25"), mysqlpp::null);
// Form the query to insert the row into the stock table.
mysqlpp::Query query = con.query();
query.insert(row);
// Show the query about to be executed.
cout << "Query: " << query << endl;
// Execute the query. We use execute() because INSERT doesn't
// return a result set.
query.execute();
Result Sets
数据库操作结果一般也就是增删改查,有些操作有返回数据,而有些只是返回成功与否,mysqlpp执行操作后的返回结果主要分为三种:SimpleResult,UseQueryResult和StoreQueryResult。
结果集中的字段数据存储在称为 String(自己封装的类) 的特殊 std::string 类中。 此类具有转换运算符,可自动将这些对象转换为任何基本 C 数据类型。 此外,MySQL++ 定义了 DateTime 等类,您可以从 MySQL DATETIME 字符串对其进行初始化。 这些自动转换可以防止错误转换,并且可以设置警告标志或抛出异常。
UseQueryResult
由于use的语义类似于使用游标,也就是支持一行一行地拉出内容,所以UseQueryResult 也就自然而然地支持一些关于fetch row的功能。
mysqlpp::Query query = conn.query("select * from stock");
if (mysqlpp::UseQueryResult res = query.use()) {
// Display header
cout.setf(ios::left);
cout << setw(31) << "Item" <<
setw(10) << "Num" <<
setw(10) << "Weight" <<
setw(10) << "Price" <<
"Date" << endl << endl;
// Get each row in result set, and print its contents
while (mysqlpp::Row row = res.fetch_row()) {
cout << setw(30) << row["item"] << ' ' <<
setw(9) << row["num"] << ' ' <<
setw(9) << row["weight"] << ' ' <<
setw(9) << row["price"] << ' ' <<
setw(9) << row["sdate"] <<
endl;
}
StoreQueryResult
StoreQueryResult它本身就是从vector继承而来,所以它就是vector。所以用户程序可以直接使用下标的形式来获取所有的ROW。这也就是说在这个store之后,所有的ROW的内容都在了这个vecor里面了。
// Retrieve the sample stock table set up by resetdb
mysqlpp::Query query = conn.query("select * from stock");
mysqlpp::StoreQueryResult res = query.store();
// Display results
if (res) {
// Display header
cout.setf(ios::left);
cout << setw(31) << "Item" <<
setw(10) << "Num" <<
setw(10) << "Weight" <<
setw(10) << "Price" <<
"Date" << endl << endl;
// Get each row in result set, and print its contents
for (size_t i = 0; i < res.num_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;
}
SimpleResult
并非所有 SQL 查询都返回数据。 一个例子是创建表。 对于这些类型的查询,有一种特殊的结果类型 (SimpleResult),它只报告查询产生的状态:查询是否成功,它影响了多少行(如果有)等。这个类中只有下面三个简单的成员函数,
/// \brief Get the last value used for an AUTO_INCREMENT field
ulonglong insert_id() const { return insert_id_; }
/// \brief Get the number of rows affected by the query
ulonglong rows() const { return rows_; }
/// \brief Get any additional information about the query returned
/// by the server.
const char* info() const { return info_.c_str(); }
Exceptions
MySQL++ 的所有自定义异常都派生自一个公共基类 Exception。 而这个类又派生自标准 C++ 的 std::exception类。 可以说MySQL++的异常捕获间接来自标准C++库的异常,因此可以通过捕获 std::exception来捕获来自MySQL++的所有异常。但是,最好当然是每种具体异常类型设置单独的catch块,并为Exception 或std::exception添加一个处理程序,以充当意外异常的“全部捕获”。
class MYSQLPP_EXPORT Exception : public std::exception;
我们看下面一个例子:
try {
// Establish the connection to the database server.
mysqlpp::Connection con(mysqlpp::examples::db_name,
cmdline.server(), cmdline.user(), cmdline.pass());
mysqlpp::Query query = con.query("select item,description from stock");
vector<stock> res;
query.storein(res);
// Display the items
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) {
// Handle any query errors
cerr << "Query error: " << er.what() << endl;
return -1;
}
catch (const mysqlpp::BadConversion& er) {
// Handle bad conversions; e.g. type mismatch populating 'stock'
cerr << "Conversion error: " << er.what() << endl <<
"\tretrieved data size: " << er.retrieved <<
", actual size: " << er.actual_size << endl;
return -1;
}
catch (const mysqlpp::Exception& er) {
// Catch-all for any other MySQL++ exceptions
cerr << "Error: " << er.what() << endl;
return -1;
}
从上面我们可以看到,MySQL++可以区分不同类型的异常,我们上面也讲到Connection类继承自OptionalExceptions,OptionalExceptions类就是控制是否抛出异常的。当异常被关闭时,MySQL++只能通过返回错误代码告知异常。由于在MySQL++中,一切都来自Connection对象,因此在程序开始时禁用它的异常会禁用所有可选的异常。
另外MySQL++的异常机制非常细化。大多数时候我们可以让异常保持启用状态,但也可以在具体某个代码块中暂时关闭异常。可以这样,将不想抛出异常的代码部分放入块中,并在该块的顶部创建一个NoExceptions对象。创建时,它会保存您传递给它的OptionalExceptions派生类的异常标志,然后禁用其上的异常。当 NoExceptions对象在块的末尾超出范围时,它会将异常标志恢复到以前的状态:
mysqlpp::Connection con; // default ctor, so exceptions enabled
{
mysqlpp::NoExceptions ne(con);
if (!con.select_db("a_db_that_might_not_exist_yet")) {
// Our DB doesn’t exist yet, so create and select it here; no need
// to push handling of this case way off in an exception handler.
}
}
看了类NoExceptions的实现,其实就是讲Connection传下去的是否启用异常保存下来,然后关闭异常,当调用NoExceptions这个类的析构函数时恢复原来的异常启用状态。
NoExceptions(const OptionalExceptions& a) :
assoc_(a), //1.保存OptionalExceptions对象引用
exceptions_were_enabled_(a.throw_exceptions()) //2.先保存状态
{
assoc_.disable_exceptions(); //3.不管任何情况直接关闭异常
}
/// \brief Destructor
///
/// Restores our associate object's previous exception state.
~NoExceptions()
{
assoc_.set_exceptions(exceptions_were_enabled_); //4.当退出代码块,调用析构函数恢复异常状态
}
当然还有一些比较严重的异常是无法通过开关打开或者关闭的,比如:
- MySQL++ 总是检查数组索引。 例如,如果你的代码在仅包含 5 个字段的行上显示“row[21]”,您将收到 BadIndex 异常。 如果您在没有“fred”字段的行上说“row[“fred”]”,则会得到 BadFieldName 异常。
- 当您要求它进行不正确的类型转换时,String 将始终抛出 BadConversion。 例如,如果您尝试将“1.25”转换为 int,则会出现异常。
- 如果使用模板查询并且在实例化模板时没有传递足够的参数,Query 将抛出 BadParamCount 异常。
- 如果在 MySQL++ 不知道要转换为 SQL 的查询中使用 C++ 数据类型,则 MySQL++ 将抛出 TypeLookupFailed 异常。
好了,这篇文章就将这么多了,大体上MySQL++的功能流程及原理介绍就这些了,后面有时间再学习一些更细节的,比如数据类型如何处理,连接池等等。