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_#
如果我们有这样的表
1 2 3 4 5 6 7 |
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_#宏。
1 2 3 4 5 6 7 8 |
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才能够用这个宏。他具体是怎么实现的我们先不管,我们主要关注他怎么用。
1 |
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所指定的数目和顺序为参数的版本)。例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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++不要生成对应的构造器,具体原理下面讲。
- 获取数据
1 2 3 4 5 |
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里面。
1 2 3 |
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++尽量高可拓展。
- 增加数据
1 2 3 |
stock row("Hot Dogs", 100, 1.5, …); mysqlpp::Query query = con.query( ); query.insert(row).execute( ); |
或者
1 2 3 4 5 |
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的等方式加入的。
sfasdfasdfasd
那么,这个insert policy在什么地方会用到?
待会我们就会看到,如果某个vector里面有两个元素(分别为row1和row2),那么在Query:: insert(sth.begin(), sth.end() )中,实际上会把一句insert语句拓展成(意会而已)
1 |
“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机制来实现它的。
1 2 |
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 |
- 修改数据
1 2 3 4 5 6 7 |
// 先获取一条出来 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++中,如果需要删除一些数据,也只能够通过以下方式进行
1 2 |
mysqlpp::Query query = con.query("delete from stock where … "); query.execute(); |
- 当表名与结构体名不一致时
记得在讲sql_create_#宏的时候,第一个参数就是该结构体和其所对应的表名(两者一般是一样的)。但是在我所在的公司,由于业务上的需要,通常一张表(以下假设该表名为stock)可能会有两个版本,即第一天用stock1,第二天用stock2。这种情况下,我们该怎么办?显然使用sql_create_#两次来创建两个一模一样的结构体是一种办法。但是效率显然是低下的,即使在数据库中,为了实现这个功能我们确实需要创建两个一模一样的表。但是在程序中,一个同构stock结构体就可以解决这些事情了。
根据默认的情况,SSQLS默认结构体的名字就是你所需要找的表的名字。那么上面的情况怎么处理?
方法一是全局修改表名,使得这个SSQLS结构体能够匹配特定的表。
1 |
stock::table(“stock1”); |
这里的stock是SSQLS结构体。
方法二是针对不同的实例(instance)都修改对应的表。例如,
1 2 3 4 |
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上的一个派生例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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); }; |
发表评论
要发表评论,您必须先登录。