收錢碼合并的網(wǎng)站怎么做東莞seo黑帽培訓(xùn)
Hello World Example
在本章中,我們將使用傳統(tǒng)的“Hello World”示例展示如何創(chuàng)建一個(gè)依賴于ODB進(jìn)行對(duì)象持久化的簡單C++應(yīng)用程序。特別是,我們將討論如何聲明持久類、生成數(shù)據(jù)庫支持代碼以及編譯和運(yùn)行我們的應(yīng)用程序。我們還將學(xué)習(xí)如何使對(duì)象持久化,加載、更新和刪除持久化對(duì)象,以及在數(shù)據(jù)庫中查詢符合特定條件的持久化對(duì)象。該示例還展示了如何定義和使用視圖,這是一種允許我們創(chuàng)建持久化對(duì)象、數(shù)據(jù)庫表的投影,或處理本機(jī)SQL查詢或存儲(chǔ)過程調(diào)用結(jié)果的機(jī)制。
本章中介紹的代碼基于hello示例,該示例可以在odb-examples中找到。
1.聲明持久類
- 首先有一個(gè)表示Person的類
// person.hxx
//#include <string>class person
{
public:person (const std::string& first,const std::string& last,unsigned short age);const std::string& first () const;const std::string& last () const;unsigned short age () const;void age (unsigned short);private:std::string first_;std::string last_;unsigned short age_;
};
- 為了不錯(cuò)過我們需要問候的任何人,我們想將人物對(duì)象保存在數(shù)據(jù)庫中。為了實(shí)現(xiàn)這一點(diǎn),我們將person類聲明為持久類:
// person.hxx
//#include <string>#include <odb/core.hxx> // (1)#pragma db object // (2)
class person
{...private:person () {} // (3)friend class odb::access; // (4)#pragma db id auto // (5)unsigned long id_; // (5)std::string first_;std::string last_;unsigned short age_;
};
- 為了能夠?qū)erson對(duì)象保存在數(shù)據(jù)庫中,我們必須對(duì)原始類定義進(jìn)行五次更改,標(biāo)記為(1)到(5)。第一個(gè)更改是包含了ODB標(biāo)頭
<ODB/core.hxx>
。此標(biāo)頭提供了許多核心ODB聲明,例如ODB::access,用于定義持久類。 - 第二個(gè)變化是在類定義之前添加了db對(duì)象
pragma
。這個(gè)雜注告訴ODB編譯器后面的類是持久的。請(qǐng)注意,使類持久化并不意味著該類的所有對(duì)象都會(huì)自動(dòng)存儲(chǔ)在數(shù)據(jù)庫中。您仍然可以像以前一樣創(chuàng)建此類的普通或臨時(shí)實(shí)例。不同之處在于,現(xiàn)在您可以使這種瞬態(tài)實(shí)例持久化,我們稍后會(huì)看到。 - 第三個(gè)變化是添加了默認(rèn)構(gòu)造函數(shù)。當(dāng)從持久狀態(tài)實(shí)例化對(duì)象時(shí),ODB生成的數(shù)據(jù)庫支持代碼將使用此構(gòu)造函數(shù)。正如我們?yōu)閜erson類所做的那樣,如果你不想讓類的用戶使用默認(rèn)構(gòu)造函數(shù),你可以將其設(shè)置為私有或受保護(hù)。另請(qǐng)注意,有一些限制,可以在沒有默認(rèn)構(gòu)造函數(shù)的情況下?lián)碛谐志妙悺?/li>
- 通過第四個(gè)更改,我們使odb::access類成為person類的友元類。這是使數(shù)據(jù)庫支持代碼可以訪問默認(rèn)構(gòu)造函數(shù)和數(shù)據(jù)成員所必需的。如果你的類有一個(gè)公共的默認(rèn)構(gòu)造函數(shù),以及公共數(shù)據(jù)成員或數(shù)據(jù)成員的公共訪問器和修飾符,那么朋友聲明是不必要的。
- 最后的更改添加了一個(gè)名為id_的數(shù)據(jù)成員,前面有另一個(gè)pragma。在ODB中,每個(gè)持久對(duì)象在其類中通常都有一個(gè)唯一的標(biāo)識(shí)符?;蛘?#xff0c;換句話說,同一類型的兩個(gè)持久實(shí)例沒有相同的標(biāo)識(shí)符。雖然可以定義一個(gè)沒有對(duì)象id的持久類,但可以在這樣的類上執(zhí)行的數(shù)據(jù)庫操作的數(shù)量是有限的。對(duì)于我們的類,我們使用一個(gè)整數(shù)id。id_member之前的db-id-auto雜注告訴ODB編譯器以下成員是對(duì)象的標(biāo)識(shí)符。自動(dòng)說明符表示它是數(shù)據(jù)庫分配的id。當(dāng)對(duì)象被持久化時(shí),數(shù)據(jù)庫將自動(dòng)生成一個(gè)唯一的id并將其分配給對(duì)象。
- 在這個(gè)例子中,我們選擇添加一個(gè)標(biāo)識(shí)符,因?yàn)楝F(xiàn)有的成員都不能達(dá)到同樣的目的。但是,如果一個(gè)類已經(jīng)有一個(gè)具有合適屬性的成員,那么使用該成員作為標(biāo)識(shí)符是很自然的。例如,如果我們的人員類別包含某種形式的個(gè)人身份信息(美國的SSN或其他國家的ID/護(hù)照號(hào)碼),那么我們可以將其用作ID?;蛘?#xff0c;如果我們存儲(chǔ)了與每個(gè)人相關(guān)的電子郵件,那么如果假設(shè)每個(gè)人都有一個(gè)唯一的電子郵件地址,我們就可以使用它。
- 再舉一個(gè)例子,考慮以下person類的替代版本。在這里,我們使用現(xiàn)有的數(shù)據(jù)成員之一作為id。此外,數(shù)據(jù)成員保持私有,而是通過公共訪問器和修飾符函數(shù)進(jìn)行訪問。最后,ODB語法被分組在一起,并放置在類定義之后。它們也可以被移動(dòng)到一個(gè)單獨(dú)的標(biāo)頭中,使原始類完全保持不變。
class person
{
public:person ();const std::string& email () const;void email (const std::string&);const std::string& get_name () const;std::string& set_name ();unsigned short getAge () const;void setAge (unsigned short);private:std::string email_;std::string name_;unsigned short age_;
};#pragma db object(person)
#pragma db member(person::email_) id
2. 生成數(shù)據(jù)庫支持代碼
我們?cè)谏弦还?jié)中創(chuàng)建的持久類定義對(duì)于任何可以實(shí)際完成這項(xiàng)工作并將人的數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫的代碼來說都特別輕。在C++的其他ORM庫中,沒有序列化或反序列化代碼,甚至沒有數(shù)據(jù)成員注冊(cè),你通常必須手工編寫。這是因?yàn)樵跀?shù)據(jù)庫和C++對(duì)象表示之間轉(zhuǎn)換的ODB代碼是由ODB編譯器自動(dòng)生成的。
- 為了編譯我們?cè)谏弦还?jié)中創(chuàng)建的person.hxx標(biāo)頭并生成MySQL數(shù)據(jù)庫的支持代碼,我們從終端(UNIX)或命令提示符(Windows)調(diào)用ODB編譯器:
odb -d mysql --generate-query person.hxx
- 在本章的其余部分,我們將使用MySQL作為首選數(shù)據(jù)庫,但也可以使用其他受支持的數(shù)據(jù)庫系統(tǒng)。
如果您沒有安裝公共ODB運(yùn)行時(shí)庫(libodb),或者將其安裝到C++編譯器默認(rèn)不搜索頭文件的目錄中,那么您可能會(huì)收到以下錯(cuò)誤:
person.hxx:10:24: fatal error: odb/core.hxx: No such file or directory
- 要解決這個(gè)問題,您需要使用-I預(yù)處理器選項(xiàng)指定libodb標(biāo)頭位置,例如:
odb -I.../libodb -d mysql --generate-query person.hxx
- 這里/libodb表示libodb目錄的路徑。
- 上述對(duì)ODB編譯器的調(diào)用產(chǎn)生了三個(gè)C++文件:person-ODB.hxx、person-ODB.ixx和person-ODB.cxx。您通常不會(huì)直接使用這些文件中包含的類型或函數(shù)。相反,您所要做的就是在C++文件中包含person-odb.hxx,您可以在其中使用person.hxx中的類執(zhí)行數(shù)據(jù)庫操作,編譯person-odb.cxx并將生成的對(duì)象文件鏈接到您的應(yīng)用程序。
- 您可能想知道–generate查詢選項(xiàng)的用途。它指示ODB編譯器生成可選的查詢支持代碼,我們稍后將在“Hello World”示例中使用這些代碼。我們會(huì)發(fā)現(xiàn)另一個(gè)有用的選項(xiàng)是——生成模式。此選項(xiàng)使ODB編譯器生成第四個(gè)文件person.sql,它是person.hxx中定義的持久類的數(shù)據(jù)庫模式:
odb -d mysql --generate-query --generate-schema person.hxx
- 數(shù)據(jù)庫模式文件包含創(chuàng)建存儲(chǔ)持久類所需的表的SQL語句。
- 如果您想查看所有可用ODB編譯器選項(xiàng)的列表,請(qǐng)參閱ODB Compiler Command Line Manual
- 現(xiàn)在我們有了持久類和數(shù)據(jù)庫支持代碼,剩下的唯一部分就是應(yīng)用程序代碼,它對(duì)所有這些都有用。
3. 編譯和運(yùn)行
假設(shè)main()函數(shù)和應(yīng)用程序代碼保存在driver.cxx中,并且數(shù)據(jù)庫支持代碼和模式如前一節(jié)所述生成,為了構(gòu)建我們的應(yīng)用程序,我們首先需要編譯所有的C++源文件,然后將它們與兩個(gè)ODB運(yùn)行時(shí)庫鏈接。
在UNIX上,編譯部分可以用以下命令完成(用c++編譯器名稱替換c++;有關(guān)Microsoft Visual Studio設(shè)置,請(qǐng)參閱odb示例包):
c++ -c driver.cxx
c++ -c person-odb.cxx
- 與ODB編譯類似,如果您收到一個(gè)錯(cuò)誤,說明在ODB/或ODB/mysql目錄中找不到標(biāo)頭,則需要使用-I預(yù)處理器選項(xiàng)指定公共ODB運(yùn)行時(shí)庫(libodb)和mysql ODB運(yùn)行時(shí)庫(libodb-mysql)的位置。
- 編譯完成后,我們可以使用以下命令鏈接應(yīng)用程序:
c++ -o driver driver.o person-odb.o -lodb-mysql -lodb
- 請(qǐng)注意,我們將應(yīng)用程序與兩個(gè)ODB庫鏈接:libodb是一個(gè)公共運(yùn)行時(shí)庫,libodb-mysql是一個(gè)mysql運(yùn)行時(shí)庫(如果您使用另一個(gè)數(shù)據(jù)庫,則此庫的名稱將相應(yīng)更改)。如果你收到一個(gè)錯(cuò)誤,說找不到其中一個(gè)庫,那么你需要使用-L鏈接器選項(xiàng)來指定它們的位置。
- 在運(yùn)行應(yīng)用程序之前,我們需要使用生成的person.sql文件創(chuàng)建數(shù)據(jù)庫模式。對(duì)于MySQL,我們可以使用MySQL客戶端程序,例如:
mysql --user=odb_test --database=odb_test < person.sql
- 上述命令將以用戶odb_test的身份登錄到本地MySQL服務(wù)器,無需密碼,并使用名為odb_test.的數(shù)據(jù)庫。請(qǐng)注意,執(zhí)行此命令后,存儲(chǔ)在odb_test數(shù)據(jù)庫中的所有數(shù)據(jù)都將被刪除。
- 還要注意,使用獨(dú)立生成的SQL文件并不是在ODB中創(chuàng)建數(shù)據(jù)庫模式的唯一方法。我們還可以將模式直接嵌入到我們的應(yīng)用程序中,或者使用不是由ODB編譯器生成的自定義模式。
- 數(shù)據(jù)庫模式就緒后,我們使用相同的登錄名和數(shù)據(jù)庫名稱運(yùn)行應(yīng)用程序:
./driver --user odb_test --database odb_test
4. 使對(duì)象持久化
// driver.cxx
//#include <memory> // std::auto_ptr
#include <iostream>#include <odb/database.hxx>
#include <odb/transaction.hxx>#include <odb/mysql/database.hxx>#include "person.hxx"
#include "person-odb.hxx"using namespace std;
using namespace odb::core;int
main (int argc, char* argv[])
{try{auto_ptr<database> db (new odb::mysql::database (argc, argv));unsigned long john_id, jane_id, joe_id;// Create a few persistent person objects.//{person john ("John", "Doe", 33);person jane ("Jane", "Doe", 32);person joe ("Joe", "Dirt", 30);transaction t (db->begin ());// Make objects persistent and save their ids for later use.//john_id = db->persist (john);jane_id = db->persist (jane);joe_id = db->persist (joe);t.commit ();}}catch (const odb::exception& e){cerr << e.what () << endl;return 1;}
}
- 讓我們逐一檢查這段代碼。一開始,我們包含了一堆標(biāo)題。在標(biāo)準(zhǔn)C++頭文件之后,我們包括<odb/database.hxx>和<odb/transaction.hxx>,它們定義了獨(dú)立于數(shù)據(jù)庫系統(tǒng)的odb::database和odb:;transaction接口。然后我們包括<odb/mysql/database.hxx>,它定義了數(shù)據(jù)庫接口的mysql實(shí)現(xiàn)。最后,我們包括person.hxx和person-odb.hxx,它們定義了我們的持久人類。
- 然后我們有兩個(gè)使用命名空間指令。第一個(gè)引入了標(biāo)準(zhǔn)名稱空間中的名稱,第二個(gè)引入了ODB聲明,我們稍后將在文件中使用這些聲明。請(qǐng)注意,在第二個(gè)指令中,我們使用odb::core命名空間,而不僅僅是odb。前者只將基本的ODB名稱(如數(shù)據(jù)庫和事務(wù)類)帶入當(dāng)前名稱空間,而不帶任何輔助對(duì)象。這最大限度地減少了與其他庫名稱沖突的可能性。還要注意,在限定單個(gè)名稱時(shí),您應(yīng)該繼續(xù)使用odb命名空間。例如,您應(yīng)該編寫odb::database,而不是odb::core::database。
- 一旦我們進(jìn)入main(),我們做的第一件事就是創(chuàng)建MySQL數(shù)據(jù)庫對(duì)象。請(qǐng)注意,這是driver.cxx中明確提到MySQL的最后一行;其余代碼通過通用接口工作,并且獨(dú)立于數(shù)據(jù)庫系統(tǒng)。我們使用argc/argv-mysql::database構(gòu)造函數(shù),它從命令行自動(dòng)提取數(shù)據(jù)庫參數(shù),如登錄名、密碼、數(shù)據(jù)庫名等。在您自己的應(yīng)用程序中,您可能更喜歡使用其他mysql::數(shù)據(jù)庫構(gòu)造函數(shù),它們?cè)试S您直接傳遞此信息。
- 接下來,我們創(chuàng)建三個(gè)人對(duì)象?,F(xiàn)在它們是瞬態(tài)對(duì)象,這意味著如果我們此時(shí)終止應(yīng)用程序,它們將消失,沒有任何存在的證據(jù)。下一行啟動(dòng)數(shù)據(jù)庫事務(wù)。我們將在本手冊(cè)稍后詳細(xì)討論交易。目前,我們需要知道的是,所有ODB數(shù)據(jù)庫操作都必須在事務(wù)中執(zhí)行,事務(wù)是一個(gè)原子工作單元;事務(wù)中執(zhí)行的所有數(shù)據(jù)庫操作要么一起成功(提交),要么自動(dòng)撤消(回滾)。
- 一旦我們進(jìn)入事務(wù),我們就對(duì)每個(gè)person對(duì)象調(diào)用persist()數(shù)據(jù)庫函數(shù)。此時(shí),每個(gè)對(duì)象的狀態(tài)都保存在數(shù)據(jù)庫中。但是,請(qǐng)注意,除非提交事務(wù),否則此狀態(tài)不是永久性的。例如,如果我們的應(yīng)用程序此時(shí)崩潰,仍然沒有證據(jù)表明我們的對(duì)象曾經(jīng)存在過。
- 在我們的例子中,當(dāng)我們調(diào)用persist()時(shí),還會(huì)發(fā)生另一件事。請(qǐng)記住,我們決定為我們的person對(duì)象使用數(shù)據(jù)庫分配的標(biāo)識(shí)符。對(duì)persist()的調(diào)用就是執(zhí)行此賦值的地方。一旦此函數(shù)返回,id_成員將包含此對(duì)象的唯一標(biāo)識(shí)符。為了方便起見,persist()函數(shù)還返回它持久化的對(duì)象標(biāo)識(shí)符的副本。我們將每個(gè)對(duì)象的返回標(biāo)識(shí)符保存在局部變量中。我們將在稍后使用這些標(biāo)識(shí)符對(duì)持久對(duì)象執(zhí)行其他數(shù)據(jù)庫操作。
- 在我們持久化了對(duì)象之后,是時(shí)候提交事務(wù)并使更改永久化了。只有在commit()函數(shù)成功返回后,我們才能保證對(duì)象是持久的。繼續(xù)崩潰示例,如果我們的應(yīng)用程序在提交后因任何原因終止,數(shù)據(jù)庫中對(duì)象的狀態(tài)將保持不變。事實(shí)上,我們很快就會(huì)發(fā)現(xiàn),我們的應(yīng)用程序可以重新啟動(dòng)并從數(shù)據(jù)庫中加載原始對(duì)象。還要注意,事務(wù)必須通過commit()調(diào)用顯式提交。如果事務(wù)對(duì)象在沒有顯式提交或回滾事務(wù)的情況下離開作用域,它將自動(dòng)回滾。這種行為使您不必?fù)?dān)心在事務(wù)中拋出異常;如果它們?cè)竭^事務(wù)邊界,事務(wù)將自動(dòng)回滾,對(duì)數(shù)據(jù)庫所做的所有更改都將撤消。
- 我們示例中的最后一段代碼是處理數(shù)據(jù)庫異常的catch塊。我們通過捕獲基本ODB異常并打印診斷來實(shí)現(xiàn)這一點(diǎn)。
- 現(xiàn)在讓我們編譯,然后運(yùn)行我們的第一個(gè)ODB應(yīng)用程序:
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_test
- 我們的第一個(gè)應(yīng)用程序除了錯(cuò)誤消息外什么都不打印,所以我們無法真正判斷它是否真的將對(duì)象的狀態(tài)存儲(chǔ)在數(shù)據(jù)庫中。雖然我們很快就會(huì)讓我們的應(yīng)用程序更有趣,但現(xiàn)在我們可以使用mysql客戶端來檢查數(shù)據(jù)庫內(nèi)容。它還將讓我們了解對(duì)象的存儲(chǔ)方式:
mysql --user=odb_test --database=odb_testWelcome to the MySQL monitor.mysql> select * from person;+----+-------+------+-----+
| id | first | last | age |
+----+-------+------+-----+
| 1 | John | Doe | 33 |
| 2 | Jane | Doe | 32 |
| 3 | Joe | Dirt | 30 |
+----+-------+------+-----+
3 rows in set (0.00 sec)mysql> quit
- 另一種深入了解幕后情況的方法是跟蹤ODB在每次數(shù)據(jù)庫操作中執(zhí)行的SQL語句。以下是我們?nèi)绾卧趖ransaction期間啟用跟蹤:
// Create a few persistent person objects.//{...transaction t (db->begin ());t.tracer (stderr_tracer);// Make objects persistent and save their ids for later use.//john_id = db->persist (john);jane_id = db->persist (jane);joe_id = db->persist (joe);t.commit ();}
- 經(jīng)過此修改,我們的應(yīng)用程序現(xiàn)在產(chǎn)生以下輸出:
INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
- 請(qǐng)注意,我們看到的是問號(hào)而不是實(shí)際值,因?yàn)镺DB使用準(zhǔn)備好的語句并以二進(jìn)制形式將數(shù)據(jù)發(fā)送到數(shù)據(jù)庫。
5. 查詢數(shù)據(jù)庫中的對(duì)象
到目前為止,我們的應(yīng)用程序不像典型的“Hello World”示例。除了錯(cuò)誤消息外,它不會(huì)打印任何內(nèi)容。讓我們改變這一點(diǎn),并教我們的應(yīng)用程序向數(shù)據(jù)庫中的人打招呼。為了讓它更有趣,讓我們只向30歲以上的人打招呼:
// driver.cxx
//...int
main (int argc, char* argv[])
{try{...// Create a few persistent person objects.//{...}typedef odb::query<person> query;typedef odb::result<person> result;// Say hello to those over 30.//{transaction t (db->begin ());result r (db->query<person> (query::age > 30));for (result::iterator i (r.begin ()); i != r.end (); ++i){cout << "Hello, " << i->first () << "!" << endl;}t.commit ();}}catch (const odb::exception& e){cerr << e.what () << endl;return 1;}
}
- 我們應(yīng)用程序的前半部分與之前相同,為簡潔起見,在上面的列表中被替換為“…”。讓我們?cè)俅沃鹨粰z查其余部分。
- 這兩個(gè)typedef為我們的應(yīng)用程序中經(jīng)常使用的兩個(gè)模板實(shí)例化創(chuàng)建了方便的別名。第一個(gè)是人員對(duì)象的查詢類型,第二個(gè)是該查詢的結(jié)果類型。
- 然后我們開始一個(gè)新的事務(wù)并調(diào)用query()數(shù)據(jù)庫函數(shù)。我們傳遞一個(gè)查詢表達(dá)式(query::age>30),它將返回的對(duì)象限制為年齡大于30的對(duì)象。我們還將查詢結(jié)果保存在局部變量中。
- 接下來的幾行在結(jié)果序列上執(zhí)行標(biāo)準(zhǔn)的循環(huán)迭代,為每個(gè)返回的人打印hello。然后我們提交事務(wù),就是這樣。讓我們看看這個(gè)應(yīng)用程序?qū)⒋蛴∈裁?#xff1a;
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_testHello, John!
Hello, Jane!
- 這看起來似乎是對(duì)的,但我們?nèi)绾沃啦樵儗?shí)際上使用了數(shù)據(jù)庫,而不僅僅是使用了早期persist()調(diào)用的一些內(nèi)存。測(cè)試這一點(diǎn)的一種方法是注釋掉應(yīng)用程序中的第一個(gè)事務(wù),并在不重新創(chuàng)建數(shù)據(jù)庫模式的情況下重新運(yùn)行它。這樣,將返回上次運(yùn)行期間持久化的對(duì)象?;蛘?#xff0c;我們可以重新運(yùn)行同一個(gè)應(yīng)用程序,而不重新創(chuàng)建模式,并注意到我們現(xiàn)在顯示了重復(fù)的對(duì)象:
./driver --user odb_test --database odb_testHello, John!
Hello, Jane!
Hello, John!
Hello, Jane!
- 這里發(fā)生的事情是,我們應(yīng)用程序的前一次運(yùn)行持久化了一組person對(duì)象,當(dāng)我們重新運(yùn)行應(yīng)用程序時(shí),我們持久化了另一組同名但I(xiàn)D不同的對(duì)象。稍后運(yùn)行查詢時(shí),將返回兩個(gè)集合中的匹配項(xiàng)。我們可以更改打印“Hello”字符串的行,如下所示,以說明這一點(diǎn):
cout << "Hello, " << i->first () << " (" << i->id () << ")!" << endl;
- 如果我們現(xiàn)在重新運(yùn)行這個(gè)修改后的程序,再次不重新創(chuàng)建數(shù)據(jù)庫模式,我們將得到以下輸出:
./driver --user odb_test --database odb_testHello, John (1)!
Hello, Jane (2)!
Hello, John (4)!
Hello, Jane (5)!
Hello, John (7)!
Hello, Jane (8)!
- 上述列表中缺少的標(biāo)識(shí)符3、6和9屬于此查詢未選擇的“Joe Dirt”對(duì)象。
6. 更新持久對(duì)象
雖然使對(duì)象持久化,然后使用查詢選擇其中一些是兩個(gè)有用的操作,但大多數(shù)應(yīng)用程序還需要更改對(duì)象的狀態(tài),然后使這些更改持久化。讓我們通過更新剛剛過生日的Joe的年齡來說明這一點(diǎn):
// driver.cxx
//...int
main (int argc, char* argv[])
{try{...unsigned long john_id, jane_id, joe_id;// Create a few persistent person objects.//{...// Save object ids for later use.//john_id = john.id ();jane_id = jane.id ();joe_id = joe.id ();}// Joe Dirt just had a birthday, so update his age.//{transaction t (db->begin ());auto_ptr<person> joe (db->load<person> (joe_id));joe->age (joe->age () + 1);db->update (*joe);t.commit ();}// Say hello to those over 30.//{...}}catch (const odb::exception& e){cerr << e.what () << endl;return 1;}
}
- 新transaction的開始和結(jié)束與前兩個(gè)相同。一旦進(jìn)入事務(wù),我們調(diào)用load()數(shù)據(jù)庫函數(shù),用Joe的持久狀態(tài)實(shí)例化一個(gè)人對(duì)象。我們傳遞Joe的對(duì)象標(biāo)識(shí)符,該標(biāo)識(shí)符是我們之前在使此對(duì)象持久化時(shí)存儲(chǔ)的。雖然在這里我們使用std::auto_ptr來管理返回的對(duì)象,但我們也可以使用另一個(gè)智能指針,例如C++11的std::unique_ptr或TR1、C++11或Boost的shared_ptr。有關(guān)對(duì)象生命周期管理和可用于此的智能指針的更多信息,請(qǐng)參閱第3.3節(jié)“對(duì)象和視圖指針”。
- 有了實(shí)例化的對(duì)象,我們?cè)黾恿四挲g,并調(diào)用update()函數(shù)來更新數(shù)據(jù)庫中對(duì)象的狀態(tài)。事務(wù)提交后,更改將永久生效。
- 如果我們現(xiàn)在運(yùn)行這個(gè)應(yīng)用程序,我們將在輸出中看到Joe,因?yàn)樗F(xiàn)在已經(jīng)30多歲了:
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_testHello, John!
Hello, Jane!
Hello, Joe!
- 如果我們沒有Joe的標(biāo)識(shí)符怎么辦?也許這個(gè)對(duì)象在我們的應(yīng)用程序的另一次運(yùn)行中被持久化了,或者被另一個(gè)應(yīng)用程序完全持久化了。假設(shè)數(shù)據(jù)庫中只有一個(gè)Joe Dirt,我們可以使用查詢工具來提出上述事務(wù)的另一種實(shí)現(xiàn):
// Joe Dirt just had a birthday, so update his age. An// alternative implementation without using the object id.//{transaction t (db->begin ());// Here we know that there can be only one Joe Dirt in our// database so we use the query_one() shortcut instead of// manually iterating over the result returned by query().//auto_ptr<person> joe (db->query_one<person> (query::first == "Joe" &&query::last == "Dirt"));if (joe.get () != 0){joe->age (joe->age () + 1);db->update (*joe);}t.commit ();}
7. 定義和使用視圖
假設(shè)我們需要收集一些關(guān)于數(shù)據(jù)庫中存儲(chǔ)的人員的基本統(tǒng)計(jì)數(shù)據(jù)。比如總?cè)藬?shù),以及最低和最高年齡。一種方法是查詢數(shù)據(jù)庫中的所有person對(duì)象,然后在迭代查詢結(jié)果時(shí)計(jì)算這些信息。雖然這種方法可能適用于只有三個(gè)人的數(shù)據(jù)庫,但如果我們有大量的對(duì)象,它的效率會(huì)非常低。
- 雖然從面向?qū)ο缶幊痰慕嵌葋砜?#xff0c;它可能不是概念上純粹的,但關(guān)系數(shù)據(jù)庫可以比我們自己在應(yīng)用程序的過程中執(zhí)行相同的操作更快、更經(jīng)濟(jì)地執(zhí)行一些計(jì)算。
- 為了支持這種情況,網(wǎng)上解決機(jī)構(gòu)提供了視圖的概念。ODB視圖是一個(gè)C++類,它體現(xiàn)了一個(gè)或多個(gè)持久對(duì)象或數(shù)據(jù)庫表的輕量級(jí)只讀投影,或者是本機(jī)SQL查詢執(zhí)行或存儲(chǔ)過程調(diào)用的結(jié)果。
- 視圖的一些常見應(yīng)用包括從對(duì)象或列數(shù)據(jù)庫表加載數(shù)據(jù)成員的子集,執(zhí)行和處理任意SQL查詢的結(jié)果,包括聚合查詢,以及使用對(duì)象關(guān)系或自定義連接條件連接多個(gè)對(duì)象和/或數(shù)據(jù)庫表。
- 以下是我們?nèi)绾味xperson_stat視圖,該視圖返回有關(guān)person對(duì)象的基本統(tǒng)計(jì)信息:
#pragma db view object(person)
struct person_stat
{#pragma db column("count(" + person::id_ + ")")std::size_t count;#pragma db column("min(" + person::age_ + ")")unsigned short min_age;#pragma db column("max(" + person::age_ + ")")unsigned short max_age;
};
- 通常,為了獲得視圖的結(jié)果,我們使用與在數(shù)據(jù)庫中查詢對(duì)象時(shí)相同的query()函數(shù)。然而,在這里,我們執(zhí)行的是一個(gè)聚合查詢,它總是只返回一個(gè)元素。因此,我們可以使用快捷方式query_value()函數(shù),而不是獲取結(jié)果實(shí)例并對(duì)其進(jìn)行迭代。以下是我們?nèi)绾问褂脛倓倓?chuàng)建的視圖加載和打印統(tǒng)計(jì)數(shù)據(jù):
// Print some statistics about all the people in our database.//{transaction t (db->begin ());// The result of this query always has exactly one element.//person_stat ps (db->query_value<person_stat> ());cout << "count : " << ps.count << endl<< "min age: " << ps.min_age << endl<< "max age: " << ps.max_age << endl;t.commit ();}
- 如果我們現(xiàn)在將person_stat視圖添加到person.hxx標(biāo)頭中,將上述事務(wù)添加到driver.cxx,并重新編譯和運(yùn)行我們的示例,那么我們將在輸出中看到以下附加行:
count : 3
min age: 31
max age: 33
8. 刪除持久化對(duì)象
我們將討論的最后一個(gè)操作是從數(shù)據(jù)庫中刪除持久對(duì)象。以下代碼片段顯示了如何刪除給定標(biāo)識(shí)符的對(duì)象:
// John Doe is no longer in our database.//{transaction t (db->begin ());db->erase<person> (john_id);t.commit ();}
- 為了從數(shù)據(jù)庫中刪除John,我們啟動(dòng)一個(gè)事務(wù),使用John的對(duì)象id調(diào)用erase()數(shù)據(jù)庫函數(shù),并提交事務(wù)。事務(wù)提交后,被擦除的對(duì)象不再持久。
- 如果我們手頭沒有對(duì)象id,我們可以使用查詢來查找和刪除對(duì)象:
// John Doe is no longer in our database. An alternative// implementation without using the object id.//{transaction t (db->begin ());// Here we know that there can be only one John Doe in our// database so we use the query_one() shortcut again.//auto_ptr<person> john (db->query_one<person> (query::first == "John" &&query::last == "Doe"));if (john.get () != 0)db->erase (*john);t.commit ();}
9. 更改持久類
當(dāng)臨時(shí)C++類的定義發(fā)生變化時(shí),例如通過添加或刪除數(shù)據(jù)成員,我們不必?fù)?dān)心該類的任何現(xiàn)有實(shí)例與新定義不匹配。畢竟,為了使類更改有效,我們必須重新啟動(dòng)應(yīng)用程序,并且沒有一個(gè)瞬態(tài)實(shí)例能夠幸存下來。
- 對(duì)于持久類來說,事情并不那么簡單。因?yàn)樗鼈兇鎯?chǔ)在數(shù)據(jù)庫中,因此在應(yīng)用程序重啟后仍然存在,所以我們遇到了一個(gè)新問題:一旦我們更改了持久類,現(xiàn)有對(duì)象(對(duì)應(yīng)于舊定義)的狀態(tài)會(huì)發(fā)生什么變化?
- 使用舊對(duì)象的問題稱為數(shù)據(jù)庫模式演化,這是一個(gè)復(fù)雜的問題,ODB為處理它提供了全面的支持。
- 假設(shè)在使用我們的person持久類一段時(shí)間并創(chuàng)建了許多包含其實(shí)例的數(shù)據(jù)庫后,我們意識(shí)到對(duì)于某些人來說,我們還需要存儲(chǔ)他們的中間名。如果我們繼續(xù)添加新的數(shù)據(jù)成員,那么新數(shù)據(jù)庫的一切都會(huì)正常工作。然而,現(xiàn)有數(shù)據(jù)庫的表與新的類定義不對(duì)應(yīng)。具體來說,生成的數(shù)據(jù)庫支持代碼現(xiàn)在期望有一個(gè)列來存儲(chǔ)中間名。但在舊數(shù)據(jù)庫中從未創(chuàng)建過這樣的列。
- ODB可以自動(dòng)生成SQL語句,這些語句將遷移舊數(shù)據(jù)庫以匹配新的類定義。但首先,我們需要通過為我們的對(duì)象模型定義一個(gè)版本來啟用模式演化支持:
// person.hxx
//#pragma db model version(1, 1)class person
{...std::string first_;std::string last_;unsigned short age_;
};
- 版本pragma中的第一個(gè)數(shù)字是基本模型版本。這是我們能夠遷移的最低版本。第二個(gè)數(shù)字是當(dāng)前型號(hào)版本。由于我們還沒有對(duì)持久類進(jìn)行任何更改,因此這兩個(gè)值都是1。
- 接下來,我們需要使用ODB編譯器重新編譯person.hxx頭文件,就像之前一樣:
odb -d mysql --generate-query --generate-schema person.hxx
-如果我們現(xiàn)在查看ODB編譯器生成的文件列表,我們會(huì)注意到一個(gè)新文件:person.xml。這是一個(gè)更改日志文件,ODB編譯器在其中跟蹤與我們的類更改相對(duì)應(yīng)的數(shù)據(jù)庫更改。請(qǐng)注意,此文件由ODB編譯器自動(dòng)維護(hù),我們所要做的就是在重新編譯之間保留它。
- 現(xiàn)在,我們已經(jīng)準(zhǔn)備好將中間名添加到person類中。我們還為它提供了一個(gè)默認(rèn)值(空字符串),該值將分配給舊數(shù)據(jù)庫中的現(xiàn)有對(duì)象。請(qǐng)注意,我們還增加了當(dāng)前版本:
// person.hxx
//#pragma db model version(1, 2)class person
{...std::string first_;#pragma db default("")std::string middle_;std::string last_;unsigned short age_;
};
- 如果我們現(xiàn)在再次重新編譯person.hxx標(biāo)頭,我們將看到兩個(gè)額外生成的文件:
person-002-pre.sql
和person-002-post.sql
。這兩個(gè)文件包含從版本1到版本2的模式遷移語句。與模式創(chuàng)建類似,模式遷移語句也可以嵌入到生成的C++代碼中。 person-002-pre.sql
和person-002-post.sql
是模式遷移前后的文件。要遷移我們的一個(gè)舊數(shù)據(jù)庫,我們首先執(zhí)行預(yù)遷移文件:
mysql --user=odb_test --database=odb_test < person-002-pre.sql
- 如果需要,我們可以在模式遷移前后運(yùn)行數(shù)據(jù)遷移代碼。在這個(gè)階段,我們既可以訪問舊數(shù)據(jù),也可以存儲(chǔ)新數(shù)據(jù)。在我們的例子中,我們不需要任何數(shù)據(jù)遷移代碼,因?yàn)槲覀優(yōu)樗鞋F(xiàn)有對(duì)象的中間名分配了默認(rèn)值。
- 為了完成遷移過程,我們執(zhí)行遷移后語句:
mysql --user=odb_test --database=odb_test < person-002-post.sql
10. 使用多個(gè)數(shù)據(jù)庫
訪問多個(gè)數(shù)據(jù)庫(即數(shù)據(jù)存儲(chǔ))只需創(chuàng)建多個(gè)代表每個(gè)數(shù)據(jù)庫的odb::<db>:數(shù)據(jù)庫實(shí)例即可。例如:
odb::mysql::database db1 ("john", "secret", "test_db1");
odb::mysql::database db2 ("john", "secret", "test_db2");
- 一些數(shù)據(jù)庫系統(tǒng)還允許將多個(gè)數(shù)據(jù)庫附加到同一實(shí)例。一個(gè)更有趣的問題是,我們?nèi)绾螐耐粦?yīng)用程序訪問多個(gè)數(shù)據(jù)庫系統(tǒng)(即數(shù)據(jù)庫實(shí)現(xiàn))。例如,我們的應(yīng)用程序可能需要將一些對(duì)象存儲(chǔ)在遠(yuǎn)程MySQL數(shù)據(jù)庫中,而將其他對(duì)象存儲(chǔ)在本地SQLite文件中?;蛘?#xff0c;我們的應(yīng)用程序可能需要能夠?qū)⑵鋵?duì)象存儲(chǔ)在用戶在運(yùn)行時(shí)選擇的數(shù)據(jù)庫系統(tǒng)中。
- ODB提供全面的多數(shù)據(jù)庫支持,從與特定數(shù)據(jù)庫系統(tǒng)的緊密集成到能夠編寫與數(shù)據(jù)庫無關(guān)的代碼以及動(dòng)態(tài)加載單個(gè)數(shù)據(jù)庫系統(tǒng)支持。
- 添加多數(shù)據(jù)庫支持的第一步是重新編譯person.hxx標(biāo)頭,為其他數(shù)據(jù)庫系統(tǒng)生成數(shù)據(jù)庫支持代碼:
odb --multi-database dynamic -d common -d mysql -d pgsql \
--generate-query --generate-schema person.hxx
- –multi-database ODB編譯器選項(xiàng)啟用多數(shù)據(jù)庫支持。目前,我們傳遞給此選項(xiàng)的動(dòng)態(tài)值的含義并不重要,但如果你好奇,請(qǐng)參閱第16章。此命令的結(jié)果是生成三組文件:person-odb。?xx(公共接口;對(duì)應(yīng)公共數(shù)據(jù)庫),person-odb-mysql。?xx(MySQL支持代碼)和person-odb-pgsql。?xx(PostgreSQL支持代碼)。還有兩個(gè)模式文件:person-mysql.sql和person-pgsql.sql。
- 我們需要在driver.cxx中更改的唯一部分是如何創(chuàng)建數(shù)據(jù)庫實(shí)例。具體來說,這條線:
auto_ptr<database> db (new odb::mysql::database (argc, argv));
- 現(xiàn)在,我們的示例能夠?qū)?shù)據(jù)存儲(chǔ)在MySQL或PostgreSQL中,因此我們需要以某種方式允許調(diào)用者指定我們必須使用哪個(gè)數(shù)據(jù)庫。為了簡單起見,我們將使第一個(gè)命令行參數(shù)指定我們必須使用的數(shù)據(jù)庫系統(tǒng),而其余參數(shù)將包含特定于數(shù)據(jù)庫的選項(xiàng),我們將像以前一樣將這些選項(xiàng)傳遞給odb:::database構(gòu)造函數(shù)。讓我們把所有這些邏輯放在一個(gè)單獨(dú)的函數(shù)中,我們將調(diào)用create_database()。以下是我們修改后的driver.cxx的開頭(其余部分不變):
// driver.cxx
//#include <string>
#include <memory> // std::auto_ptr
#include <iostream>#include <odb/database.hxx>
#include <odb/transaction.hxx>#include <odb/mysql/database.hxx>
#include <odb/pgsql/database.hxx>#include "person.hxx"
#include "person-odb.hxx"using namespace std;
using namespace odb::core;auto_ptr<database>
create_database (int argc, char* argv[])
{auto_ptr<database> r;if (argc < 2){cerr << "error: database system name expected" << endl;return r;}string db (argv[1]);if (db == "mysql")r.reset (new odb::mysql::database (argc, argv));else if (db == "pgsql")r.reset (new odb::pgsql::database (argc, argv));elsecerr << "error: unknown database system " << db << endl;return r;
}int
main (int argc, char* argv[])
{try{auto_ptr<database> db (create_database (argc, argv));if (db.get () == 0)return 1; // Diagnostics has already been issued....
- 就是這樣。剩下的就是構(gòu)建和運(yùn)行我們的示例:
c++ -c driver.cxx
c++ -c person-odb.cxx
c++ -c person-odb-mysql.cxx
c++ -c person-odb-pgsql.cxx
c++ -o driver driver.o person-odb.o person-odb-mysql.o \
person-odb-pgsql.o -lodb-mysql -lodb-pgsql -lodb
- 以下是我們?nèi)绾卧L問MySQL數(shù)據(jù)庫:
mysql --user=odb_test --database=odb_test < person-mysql.sql
./driver mysql --user odb_test --database odb_test
- 或者PostgreSQL數(shù)據(jù)庫:
psql --user=odb_test --dbname=odb_test -f person-pgsql.sql
./driver pgsql --user odb_test --database odb_test
11. 總結(jié)
本章介紹了一個(gè)非常簡單的應(yīng)用程序,盡管如此,它還是執(zhí)行了所有核心數(shù)據(jù)庫功能:persist()、query()、load()、update()和erase()。我們還看到,編寫使用ODB的應(yīng)用程序涉及以下步驟:
- 在頭文件中聲明持久類。
- 編譯這些標(biāo)頭以生成數(shù)據(jù)庫支持代碼。
- 將應(yīng)用程序與生成的代碼和兩個(gè)ODB運(yùn)行時(shí)庫鏈接起來。
如果在這一點(diǎn)上,很多事情似乎都不清楚,不要擔(dān)心。本章的目的只是讓您大致了解如何使用ODB持久化C++對(duì)象。我們將在本手冊(cè)的其余部分介紹所有細(xì)節(jié)。