C++开源项目:深入mysqlpp Query类

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

本文是我之前在微信公众号上的一篇文章记录。原链接为:MySQL++学习笔记:深入Query类 (qq.com)

Query

上一篇我们简单介绍过Query类了,它有多种方式构建SQL查询语句,本章内容想要通过深入理解Query类,然后将与其相关的其他内容进行一一学习,比如上层使用不同方式构建了SQL语句,Query类如何进行解析,如何执行不同的SQL语句等等。

下面我们来看下它的声明:

class MYSQLPP_EXPORT Query : public std::ostream, public OptionalExceptions

mysqlpp:: Query类继承了OptionalExceptions和 std::ostream, OptionalExceptions上一篇文章我们说过了就是一个控制异常是否抛出的类,而继承std::ostream目的就是为了能像std::cout一样方便快捷的构建SQL查询语句。Query类还存储了Connection的指针,SQL语句的增删改查都是通过调用connect类然后调用dbdriver类完成的。下面是Query类的继承关系图:

640 (2).png

下面我们来看Query类的构造函数:

Query::Query(Connection* c, bool te, const char* qstr) :
#if defined(MYSQLPP_HAVE_STD__NOINIT)
// prevents a double-init memory leak in native VC++ RTL (not STLport!)
std::ostream(std::_Noinit),
#else
std::ostream(0),
#endif
OptionalExceptions(te),
template_defaults(this),
conn_(c),
copacetic_(true)
{
    // Set up our internal IOStreams string buffer
    init(&sbuffer_);
​
    // Insert passed query string into our string buffer, if given
    if (qstr) {
        sbuffer_.str(qstr);
        seekp(0, std::ios::end);    // allow more insertions at end
    } 
​
    // Override any global locale setting; we want to use the classic C
    // locale so we don't get weird things like thousands separators in
    // integers inserted into the query stream.
    imbue(std::locale::classic());
}

这里面有两个变量比较陌生的成员变量:

  • sbuffer,他是一个std::stringbuf,用来存储assembled query的。另外,init和imbue方法都是std::ostream的方法。
  • template_defaults,这是一个SQLQueryParms,这个变量实际上视为template query做准备的,它的官方解释是“default template parameters, used for filling in parameterized queries”,下面章节会详细讲关于Template Queries。

Query有以下4种不同的用法:

  • 直接利用已经拼凑好了的SQL语句调用mysqlpp::Query::exec*(),mysqlpp::Query::store()或者mysqlpp::Query::use()
  • 使用ostream接口,像构造cout一样构造SQL语句,然后再调用mysqlpp::Query::exec*(),mysqlpp::Query::store()或者mysqlpp::Query::use()
  • 类似于printf一样使用template queries
  • 类似于Hibernate那样使用Specialized SQL Structures feature(SSQLS)

SQLQueryParms

SQL语句有些需要填充条件语句的值,比如有一个模板查询语句:

select * from stock where item = %0q, name = %1q;

这种情况我们需要给where部分语句不同的值就可以使用SQLQueryParms。它的定义解释为:This class holds the parameter values for filling template queries. 它继承自std::vector,SQLTypeAdapter就是数据库中各种数据类型的适配器,后面会讲。

扫描二维码关注公众号,回复: 14306231 查看本文章
class MYSQLPP_EXPORT SQLQueryParms : public std::vector<SQLTypeAdapter>;

重要的是要认识到MySQL++的引用和转义机制是能够自动识别类型的。操纵器没有效果除非将操纵器插入到查询或SQLQueryParms流中。此外,值仅引用和/或如果它们属于可能需要它的数据类型,则进行转义。例如,日期必须被引用,但不需要转义,整数类型既不需要引号也不需要转义。操纵器是对库的建议,而不是命令:如果MySQL++知道不会导致语法错误的SQL,它将忽略这些建议。

上面我们创建的模板查询语句则可以像下面这样填充数据:

mysqlpp::Query query = con.query(
    "select * from stock where item = %0q, name = %1q");
query.parse();
​
mysqlpp::SQLQueryParms sqp;
sqp << "Nuerenberger Bratwurst" << "James";
mysqlpp::StoreQueryResult res1 = query.store(sqp);

SQLQueryParms中的两个参数就可以填充到query中的%0q和%1q中了。

SQLTypeAdapter

SQLTypeAdapter正如名字所言,是一个“适配器”,也就是说,为了在实现上的方便,很多函数就以他作为中介,来隐藏具体的数据类型(例如int,double,string和mysqlpp::String等)。类似于Query中的use和store等,都有使用SQLTypeAdapter作为参数的方法。使用SQLTypeAdapter就可以省去很多overwrite。

Adapter主要有两个成员变量:

private:
    /// \brief Our internal string buffer
    RefCountedBuffer buffer_;
​
    /// \brief If true, one of the MySQL++ manipulators has processed
    /// the string data.
    ///
    /// "Processing" is escaping special SQL characters, and/or adding
    /// quotes.  See the documentation for manip.h for details.
    ///
    /// This flag is used by the template query mechanism, to prevent a
    /// string from being re-escaped or re-quoted each time that query
    /// is reused.  The flag is reset by operator=, to force the new
    /// parameter value to be re-processed.
    bool is_processed_;

buffer_是RefCountedPointer类型,用来保存此SQLTypeAdapter所包含的具体的数据、长度、类型信息等。

该类型就是SQLTypeAdapter传入的各种类型(int, string, double, long, String, …) 的包装,包装的结果就是

  • 各种类型实例的字符串表示 ( const char* data_; )
  • length 长度 ( size_type length_ )
  • 类型(由mysqlpp::mysql_type_info定义) ( mysql_type_info type_ )
  • 是否是数据库的NULL类型 ( bool is_null_ )

表示类型的类别是mysql_type_info,该类型是一个“C++类型”和“SQL类型”相互映射的utility class。

需要强调的是,该类型实际上是支持BINARY存法的,其实在实现上,就是将length不设定为”\0”的位置,而是整个类型实例的真实长度。以下面的代码为例:

SQLBuffer("abc\0efg", 7, mysql_type_info::string_type, false)

如此一来,上面的四个属性就非常清楚地对号入座了。

Template Queries

MySQL++的另一个强大功能是能够设置模板查询。这有点像C的printf()功能:给MySQL++一个字符串,其中包含查询的固定部分和变量部分的占位符,然后可以稍后将in值替换为这些占位符。

下面我们用一个官方的example来简单说一下这个template query:

int main(int argc, char *argv[])
{
    // Get database access parameters from command line
    mysqlpp::examples::CommandLine cmdline(argc, argv);
    if (!cmdline) {
        return 1;
    }
​
    try {
        // Establish the connection to the database server.
        mysqlpp::Connection con(mysqlpp::examples::db_name,
                cmdline.server(), cmdline.user(), cmdline.pass());
​
        // Build a template query to retrieve a stock item given by item name.
        mysqlpp::Query query = con.query("select * from stock where item = %0q");
        query.parse();
​
        // Retrieve an item added by resetdb; it won't be there if
        // tquery* or ssqls3 is run since resetdb.
        mysqlpp::StoreQueryResult res1 = query.store("Nürnberger Brats");
        if (res1.empty()) {
            throw mysqlpp::BadQuery("UTF-8 bratwurst item not found in "
                    "table, run resetdb");
        }
​
        // Replace the proper German name with a 7-bit ASCII
        // approximation using a different template query.
        query.reset();      // forget previous template query data
        query << "update stock set item = %0q where item = %1q";
        query.parse();
        mysqlpp::SimpleResult res2 = query.execute("Nuerenberger Bratwurst",
                res1[0][0].c_str());
​
        // Print the new table contents.
        print_stock_table(query);
    }
    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
        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;
    }
​
    return 0;
}

1.首先创建连接数据库,通过Connection类创建Query类,查询类创建传入的是带参数的SQL语句(item = %0q)。

2.必须调用query.parse()来对参数进行解析,其实就是在调用query.parse()之前设置模板,parse调用使其生效。然后可以通过调用接受查询模板参数的多个查询成员函数中的任意一个来重用此查询。例如execute,store或者use,这三者最终都是调用dbdriver下面的查询,只不过是查询返回的结果不一样。上一章节我们讲过mysqlpp执行操作后的返回结果主要分为三种:SimpleResult,UseQueryResult和StoreQueryResult。其实就是对于上面这三个不同的方法。

猜你喜欢

转载自juejin.im/post/7111683530001743880