1. 综述
终于来到了SSQLS( Specialized SQL Structure),照我看来这是一个很类似于Hibernate的功能。也就是说,通过SSQLS可以将一张表完全对应到一个C++结构体中,然后只要对于这个结构体进行操作,同时再加入到对应的mysqlpp:: Query对应的方法中,我们就可以执行对应的SQL语句。
值得说一下的是,该功能在实际项目中还是相当有用的,但是笔者所在公司由于其业务的特殊性,通常来说表的结构会非常非常大(有一些表居然有近200个字段)。根据作者的manual,MYSQL++默认只支持不超过25个字段的表结构,但是可以通过在编译前调整一些参数来修改这个限制。于是笔者进行了一下测试,通过这里的说法,修改为200个字段,然后编译整个MYSQL++重建,发现不是一般的卡,笔者刚开始以为,只是在编译MYSQL++的时候会比较卡,但是实际上,每一个运用了MYSQL++的程序都在编译时非常的卡!原因?后面你会看到的。
最后来说一下SSQLS的限制,其实编译慢也就算了,他最大的缺点是源自于C++的劣根性。我们知道,如果我们申明了一个变量int i,如果没有为其初始化,他的值就是个乱码。由于在SSQLS的“表-结构”对应中也有可能会用到int, double,long这种最原始的数据类型,所以也就存在这个问题,即,他没有办法为你赋初值的(但是由于SQL表中的varchar和char,在MYSQL++中用std::string表示的,所以字符类型没有初始化问题)。笔者为此吃过不少亏,因为在执行SQL语句的时候(利用SSQLS做一句insert)返回了一个语法错误!原来笔者的表里面有一些double,然后由于内存垃圾的缘故,导致了这个double被写成了1.23E10之类的,在insert的时候需要一个数字的地方却有了这么个东西,于是返回出错。退一万步来说,即使没有这个返回错误,这样的表你敢用吗?
2. 用法
老规矩,在分析原理之前,我们先来看一下如何用。最好的文档还是manual和示例程序。
- ”表与结构体映射”宏sql_create_#
如果我们有这样的表
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)
为了使用SSQLS,我们需要对此表“类型化”,如何做?使用作者提供的sql_create_#宏。
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<mysqlpp::sql_mediumtext>, description)
当然,这里需要提供一个除了“mysql++.h”之外的另外一个头文件ssqls.h才能够用这个宏。他具体是怎么实现的我们先不管,我们主要关注他怎么用。
sql_create_#(NAME, COMPCOUNT, SETCOUNT, TYPE1, ITEM1, ... TYPE#, ITEM#)
首先这个#表示这个表中有多少字段。NAME你就把它当做是表名(当然后面会提到这么一种情况:我有两张表结构一模一样的表格,主要用来互换,这里需要sql_create_#两次?显然大牛老外没有这么蠢,他提供了一些方法来让一个结构体为多张同构不同名表服务的机制)。COMPCOUNT是什么?这个参数的意思就是从第一个字段开始之后的COMPCOUNT个字段共同参与了比较。这个机制在有主键的表中特别合适,比如上面的例子中,item自己就形成了“主键”。SETCOUNT是从第一个字段开始之后的SETCOUNT个字段会行程构造器的参数,和类型内的set方法的参数(由sql_create_#创建出来的类型一定会有四个构造函数,分别是无参数版本,仅包含主键版本——即COMPCOUNT所指定的数目,仅有const mysqlpp:: Row&的版本,以及包含有SETCOUNT所指定的数目的参数的构造函数。该类型也会有3个set方法,分别是以单参数const mysqlpp::Row,以主键类型为参数,以SETCOUNT所指定的数目和顺序为参数的版本)。例如
sql_create_6(
some_table, 1, 3,
mysqlpp::sql_char, item,
mysqlpp::sql_bigint, num,
mysqlpp::sql_double, weight,
mysqlpp::sql_decimal, price,
mysqlpp::sql_date, sdate,
mysqlpp::Null<mysqlpp::sql_mediumtext>, description)
some_table foo;
some_table foo(row); //这个row是mysqlpp:: Row类型
some_table foo(“Hotdog”);
some_table foo("Hotdog", 52, 20);
foo.set(row);
foo.set("hello");
foo.set("hello", 500, 300);
在manual中还提到过由于C++的语法不可以将COMPCOUNT和SETCOUNT写为同一个数字,work around是为SETCOUNT设置0,即提示MYSQL++不要生成对应的构造器,具体原理下面讲。
- 获取数据
mysqlpp::Query query = con.query("select item,description from stock");
vector<stock> res;
query.storein(res);
vector<stock>::iterator it = res.begin();
cout << it->description;
或者,如果我们知道获取到的数据只有一条记录,那么可以不用存储在vector里面。
mysqlpp::Query query = con.query("select item,description from stock limit 1 ");
mysqlpp::StoreQueryResult res = query.store();
stock row = res[0];
用起来还是很方便的,和Hibernate很像。我们看到,上面的select语句只抽取item和description两个字段,然后通过mysqlpp:: Query的与SSQLS相关的方法吧结果防止到vector<stock>这个容器中,随后就像普通类型一样用。
有一点需要注意的是,这里只用到了两个字段,所以我们大可在sql_create_#中只用声明两个字段,即使stock表有6个字段之多(见sql_create_#的相关说明)。MYSQL++非常智能,他会根据SELECT出来的字段填充同名类型字段,如果没有这个同名类型字段,MYSQL++则会直接忽略它。
值得一提的是,作者在manual中解释了为什么要这么设计,貌似是因为大家把MYSQL都当做是分布式数据库来用,所以对于schema的改动可能会有先后。所以要让MYSQL++尽量高可拓展。
- 增加数据
stock row("Hot Dogs", 100, 1.5, …);
mysqlpp::Query query = con.query( );
query.insert(row).execute( );
或者
stock row("Hot Dogs", 100, 1.5, …);
vector<stock> rows;
rows.push_back(row);
mysqlpp::Query query = con.query( );
query.insert(rows.begin( ), rows.end( )) .execute( );
基本语法还是很直观的,和声明你要插入的表的对应类型的实例,然后调用mysqlpp:: Query中与SSQLS相关的方法接口就可以了。
但是,作者在这里重点突出了一个地方,那就是insert policy。我记得我在介绍mysql:: Query源代码的时候就说过这个东西,他在源代码中是以直接在private域里面做#include的等方式加入的。
那么,这个insert policy在什么地方会用到?
待会我们就会看到,如果某个vector里面有两个元素(分别为row1和row2),那么在Query:: insert(sth.begin(), sth.end() )中,实际上会把一句insert语句拓展成(意会而已)
“insert into XXX(…) values (row1), (row2)”
然后作者提到说,MySQL has a limit on the size of the SQL query it will process.这就暗示我们了一种情况,如果这个vector里面有几百万个对象,展开的SQL语句超过了MYSQL对于query的limit,那么这句语句必然失败。显然,更改MYSQL的这个limit换汤不换药。那么,更好的办法是什么?
我们可以设置某些“门槛”,一旦query的长度(或者涉及到的条数,或者某个对象的大小)超过了这个“门槛”,那么就截断这条query,让他送出去。剩下的继续来生成下一条query语句。
MYSQL++就是采用这个insert policy机制来实现它的。
mysqlpp::Query::MaxPacketInsertPolicy<> insert_policy(1000);
query.insertfrom(stock_vector.begin(), stock_vector.end(), insert_policy);
有哪些insert policy?
Insert Policy |
说明 |
MaxPacketInsertPolicy |
如果当前已经插入的rows的总体的大小查过了某个threshold,那么can_add就返回false |
SizeThresholdInsertPolicy |
如果当前想要插入的row的大小大于预设的threshold,那么can_add就返回false。即放弃那些row的大小超过某个值的数据行。当你的数据内每一行都比较短小的时候(例如没有BLOB数据字段),使用该限制对象,将使你的插入更加高效和合理。 |
RowCountInsertPolicy |
如果当前已经插入的rows的数目超过了某个threshold,那么can_add就返回false |
- 修改数据
// 先获取一条出来
mysqlpp::Query query = con.query("select item,description from stock limit 1 ");
mysqlpp::StoreQueryResult res = query.store();
stock row = res[0];
row.item = “abc”;
query.update(orig_row, row);
query.execute();
- 删除数据
看了一下mysqlpp:: Query的源代码,貌似没有发现有和update类似的delete方法,所以我估计,在MYSQL++中,如果需要删除一些数据,也只能够通过以下方式进行
mysqlpp::Query query = con.query("delete from stock where … ");
query.execute();
- 当表名与结构体名不一致时
记得在讲sql_create_#宏的时候,第一个参数就是该结构体和其所对应的表名(两者一般是一样的)。但是在我所在的公司,由于业务上的需要,通常一张表(以下假设该表名为stock)可能会有两个版本,即第一天用stock1,第二天用stock2。这种情况下,我们该怎么办?显然使用sql_create_#两次来创建两个一模一样的结构体是一种办法。但是效率显然是低下的,即使在数据库中,为了实现这个功能我们确实需要创建两个一模一样的表。但是在程序中,一个同构stock结构体就可以解决这些事情了。
根据默认的情况,SSQLS默认结构体的名字就是你所需要找的表的名字。那么上面的情况怎么处理?
方法一是全局修改表名,使得这个SSQLS结构体能够匹配特定的表。
stock::table(“stock1”);
这里的stock是SSQLS结构体。
方法二是针对不同的实例(instance)都修改对应的表。例如,
stock s1; s1.instance_table("stock1"); stock s2; s1.instance_table("stock2");
这样插入s1这个实例就会插向stock1, 插入s2这个实例就会插向stock2。
- 当把SSQLS结构类型当做基类
我们确实可以把一个SSQLS结构类型当做基类型使用,但是为了确保你所派生出来的类型他还是一个SSQLS类型,你必须要把所有被sql_create_#所隐藏了的构造函数都显式地列出来,这是因为SSQLS需要使用这些构造函数(还记得不,在介绍sql_create_#的时候有说过这种类型一定有4个构造函数),如果我们没有这些构造函数,那么如果将该类型作为SSQLS所使用,就会出现编译错误。同时不要去试图override那些需要被MYSQL++ internal use的方法。
下面是manual上的一个派生例子。
sql_create_2(
Base, 1, 2,
mysqlpp::sql_varchar, a,
mysqlpp::sql_int, b);
class Derived : public Base
{
public:
// default constructor
Derived() : Base() { }
// for-comparison constructor
Derived(const mysqlpp::sql_varchar& _a) : Base(_a) { }
// full creation constructor
Derived(const mysqlpp::sql_varchar& _a, const mysqlpp::sql_int& _b) : Base(_a, _b) { }
// population constructor
Derived(const mysqlpp::Row& row) : Base(row) { }
// functionality added to the SSQLS through inheritance
bool do_something_interesting(int data);
};
原创作品,转载请注明出处www.cnblogs.com/aicro。