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。