收錢碼合并的網站怎么做東莞seo黑帽培訓
Hello World Example
在本章中,我們將使用傳統(tǒng)的“Hello World”示例展示如何創(chuàng)建一個依賴于ODB進行對象持久化的簡單C++應用程序。特別是,我們將討論如何聲明持久類、生成數據庫支持代碼以及編譯和運行我們的應用程序。我們還將學習如何使對象持久化,加載、更新和刪除持久化對象,以及在數據庫中查詢符合特定條件的持久化對象。該示例還展示了如何定義和使用視圖,這是一種允許我們創(chuàng)建持久化對象、數據庫表的投影,或處理本機SQL查詢或存儲過程調用結果的機制。
本章中介紹的代碼基于hello示例,該示例可以在odb-examples中找到。
1.聲明持久類
- 首先有一個表示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_;
};
- 為了不錯過我們需要問候的任何人,我們想將人物對象保存在數據庫中。為了實現這一點,我們將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_;
};
- 為了能夠將person對象保存在數據庫中,我們必須對原始類定義進行五次更改,標記為(1)到(5)。第一個更改是包含了ODB標頭
<ODB/core.hxx>
。此標頭提供了許多核心ODB聲明,例如ODB::access,用于定義持久類。 - 第二個變化是在類定義之前添加了db對象
pragma
。這個雜注告訴ODB編譯器后面的類是持久的。請注意,使類持久化并不意味著該類的所有對象都會自動存儲在數據庫中。您仍然可以像以前一樣創(chuàng)建此類的普通或臨時實例。不同之處在于,現在您可以使這種瞬態(tài)實例持久化,我們稍后會看到。 - 第三個變化是添加了默認構造函數。當從持久狀態(tài)實例化對象時,ODB生成的數據庫支持代碼將使用此構造函數。正如我們?yōu)閜erson類所做的那樣,如果你不想讓類的用戶使用默認構造函數,你可以將其設置為私有或受保護。另請注意,有一些限制,可以在沒有默認構造函數的情況下?lián)碛谐志妙悺?/li>
- 通過第四個更改,我們使odb::access類成為person類的友元類。這是使數據庫支持代碼可以訪問默認構造函數和數據成員所必需的。如果你的類有一個公共的默認構造函數,以及公共數據成員或數據成員的公共訪問器和修飾符,那么朋友聲明是不必要的。
- 最后的更改添加了一個名為id_的數據成員,前面有另一個pragma。在ODB中,每個持久對象在其類中通常都有一個唯一的標識符?;蛘?#xff0c;換句話說,同一類型的兩個持久實例沒有相同的標識符。雖然可以定義一個沒有對象id的持久類,但可以在這樣的類上執(zhí)行的數據庫操作的數量是有限的。對于我們的類,我們使用一個整數id。id_member之前的db-id-auto雜注告訴ODB編譯器以下成員是對象的標識符。自動說明符表示它是數據庫分配的id。當對象被持久化時,數據庫將自動生成一個唯一的id并將其分配給對象。
- 在這個例子中,我們選擇添加一個標識符,因為現有的成員都不能達到同樣的目的。但是,如果一個類已經有一個具有合適屬性的成員,那么使用該成員作為標識符是很自然的。例如,如果我們的人員類別包含某種形式的個人身份信息(美國的SSN或其他國家的ID/護照號碼),那么我們可以將其用作ID?;蛘?#xff0c;如果我們存儲了與每個人相關的電子郵件,那么如果假設每個人都有一個唯一的電子郵件地址,我們就可以使用它。
- 再舉一個例子,考慮以下person類的替代版本。在這里,我們使用現有的數據成員之一作為id。此外,數據成員保持私有,而是通過公共訪問器和修飾符函數進行訪問。最后,ODB語法被分組在一起,并放置在類定義之后。它們也可以被移動到一個單獨的標頭中,使原始類完全保持不變。
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. 生成數據庫支持代碼
我們在上一節(jié)中創(chuàng)建的持久類定義對于任何可以實際完成這項工作并將人的數據存儲到數據庫的代碼來說都特別輕。在C++的其他ORM庫中,沒有序列化或反序列化代碼,甚至沒有數據成員注冊,你通常必須手工編寫。這是因為在數據庫和C++對象表示之間轉換的ODB代碼是由ODB編譯器自動生成的。
- 為了編譯我們在上一節(jié)中創(chuàng)建的person.hxx標頭并生成MySQL數據庫的支持代碼,我們從終端(UNIX)或命令提示符(Windows)調用ODB編譯器:
odb -d mysql --generate-query person.hxx
- 在本章的其余部分,我們將使用MySQL作為首選數據庫,但也可以使用其他受支持的數據庫系統(tǒng)。
如果您沒有安裝公共ODB運行時庫(libodb),或者將其安裝到C++編譯器默認不搜索頭文件的目錄中,那么您可能會收到以下錯誤:
person.hxx:10:24: fatal error: odb/core.hxx: No such file or directory
- 要解決這個問題,您需要使用-I預處理器選項指定libodb標頭位置,例如:
odb -I.../libodb -d mysql --generate-query person.hxx
- 這里/libodb表示libodb目錄的路徑。
- 上述對ODB編譯器的調用產生了三個C++文件:person-ODB.hxx、person-ODB.ixx和person-ODB.cxx。您通常不會直接使用這些文件中包含的類型或函數。相反,您所要做的就是在C++文件中包含person-odb.hxx,您可以在其中使用person.hxx中的類執(zhí)行數據庫操作,編譯person-odb.cxx并將生成的對象文件鏈接到您的應用程序。
- 您可能想知道–generate查詢選項的用途。它指示ODB編譯器生成可選的查詢支持代碼,我們稍后將在“Hello World”示例中使用這些代碼。我們會發(fā)現另一個有用的選項是——生成模式。此選項使ODB編譯器生成第四個文件person.sql,它是person.hxx中定義的持久類的數據庫模式:
odb -d mysql --generate-query --generate-schema person.hxx
- 數據庫模式文件包含創(chuàng)建存儲持久類所需的表的SQL語句。
- 如果您想查看所有可用ODB編譯器選項的列表,請參閱ODB Compiler Command Line Manual
- 現在我們有了持久類和數據庫支持代碼,剩下的唯一部分就是應用程序代碼,它對所有這些都有用。
3. 編譯和運行
假設main()函數和應用程序代碼保存在driver.cxx中,并且數據庫支持代碼和模式如前一節(jié)所述生成,為了構建我們的應用程序,我們首先需要編譯所有的C++源文件,然后將它們與兩個ODB運行時庫鏈接。
在UNIX上,編譯部分可以用以下命令完成(用c++編譯器名稱替換c++;有關Microsoft Visual Studio設置,請參閱odb示例包):
c++ -c driver.cxx
c++ -c person-odb.cxx
- 與ODB編譯類似,如果您收到一個錯誤,說明在ODB/或ODB/mysql目錄中找不到標頭,則需要使用-I預處理器選項指定公共ODB運行時庫(libodb)和mysql ODB運行時庫(libodb-mysql)的位置。
- 編譯完成后,我們可以使用以下命令鏈接應用程序:
c++ -o driver driver.o person-odb.o -lodb-mysql -lodb
- 請注意,我們將應用程序與兩個ODB庫鏈接:libodb是一個公共運行時庫,libodb-mysql是一個mysql運行時庫(如果您使用另一個數據庫,則此庫的名稱將相應更改)。如果你收到一個錯誤,說找不到其中一個庫,那么你需要使用-L鏈接器選項來指定它們的位置。
- 在運行應用程序之前,我們需要使用生成的person.sql文件創(chuàng)建數據庫模式。對于MySQL,我們可以使用MySQL客戶端程序,例如:
mysql --user=odb_test --database=odb_test < person.sql
- 上述命令將以用戶odb_test的身份登錄到本地MySQL服務器,無需密碼,并使用名為odb_test.的數據庫。請注意,執(zhí)行此命令后,存儲在odb_test數據庫中的所有數據都將被刪除。
- 還要注意,使用獨立生成的SQL文件并不是在ODB中創(chuàng)建數據庫模式的唯一方法。我們還可以將模式直接嵌入到我們的應用程序中,或者使用不是由ODB編譯器生成的自定義模式。
- 數據庫模式就緒后,我們使用相同的登錄名和數據庫名稱運行應用程序:
./driver --user odb_test --database odb_test
4. 使對象持久化
// 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;}
}
- 讓我們逐一檢查這段代碼。一開始,我們包含了一堆標題。在標準C++頭文件之后,我們包括<odb/database.hxx>和<odb/transaction.hxx>,它們定義了獨立于數據庫系統(tǒng)的odb::database和odb:;transaction接口。然后我們包括<odb/mysql/database.hxx>,它定義了數據庫接口的mysql實現。最后,我們包括person.hxx和person-odb.hxx,它們定義了我們的持久人類。
- 然后我們有兩個使用命名空間指令。第一個引入了標準名稱空間中的名稱,第二個引入了ODB聲明,我們稍后將在文件中使用這些聲明。請注意,在第二個指令中,我們使用odb::core命名空間,而不僅僅是odb。前者只將基本的ODB名稱(如數據庫和事務類)帶入當前名稱空間,而不帶任何輔助對象。這最大限度地減少了與其他庫名稱沖突的可能性。還要注意,在限定單個名稱時,您應該繼續(xù)使用odb命名空間。例如,您應該編寫odb::database,而不是odb::core::database。
- 一旦我們進入main(),我們做的第一件事就是創(chuàng)建MySQL數據庫對象。請注意,這是driver.cxx中明確提到MySQL的最后一行;其余代碼通過通用接口工作,并且獨立于數據庫系統(tǒng)。我們使用argc/argv-mysql::database構造函數,它從命令行自動提取數據庫參數,如登錄名、密碼、數據庫名等。在您自己的應用程序中,您可能更喜歡使用其他mysql::數據庫構造函數,它們允許您直接傳遞此信息。
- 接下來,我們創(chuàng)建三個人對象?,F在它們是瞬態(tài)對象,這意味著如果我們此時終止應用程序,它們將消失,沒有任何存在的證據。下一行啟動數據庫事務。我們將在本手冊稍后詳細討論交易。目前,我們需要知道的是,所有ODB數據庫操作都必須在事務中執(zhí)行,事務是一個原子工作單元;事務中執(zhí)行的所有數據庫操作要么一起成功(提交),要么自動撤消(回滾)。
- 一旦我們進入事務,我們就對每個person對象調用persist()數據庫函數。此時,每個對象的狀態(tài)都保存在數據庫中。但是,請注意,除非提交事務,否則此狀態(tài)不是永久性的。例如,如果我們的應用程序此時崩潰,仍然沒有證據表明我們的對象曾經存在過。
- 在我們的例子中,當我們調用persist()時,還會發(fā)生另一件事。請記住,我們決定為我們的person對象使用數據庫分配的標識符。對persist()的調用就是執(zhí)行此賦值的地方。一旦此函數返回,id_成員將包含此對象的唯一標識符。為了方便起見,persist()函數還返回它持久化的對象標識符的副本。我們將每個對象的返回標識符保存在局部變量中。我們將在稍后使用這些標識符對持久對象執(zhí)行其他數據庫操作。
- 在我們持久化了對象之后,是時候提交事務并使更改永久化了。只有在commit()函數成功返回后,我們才能保證對象是持久的。繼續(xù)崩潰示例,如果我們的應用程序在提交后因任何原因終止,數據庫中對象的狀態(tài)將保持不變。事實上,我們很快就會發(fā)現,我們的應用程序可以重新啟動并從數據庫中加載原始對象。還要注意,事務必須通過commit()調用顯式提交。如果事務對象在沒有顯式提交或回滾事務的情況下離開作用域,它將自動回滾。這種行為使您不必擔心在事務中拋出異常;如果它們越過事務邊界,事務將自動回滾,對數據庫所做的所有更改都將撤消。
- 我們示例中的最后一段代碼是處理數據庫異常的catch塊。我們通過捕獲基本ODB異常并打印診斷來實現這一點。
- 現在讓我們編譯,然后運行我們的第一個ODB應用程序:
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_test
- 我們的第一個應用程序除了錯誤消息外什么都不打印,所以我們無法真正判斷它是否真的將對象的狀態(tài)存儲在數據庫中。雖然我們很快就會讓我們的應用程序更有趣,但現在我們可以使用mysql客戶端來檢查數據庫內容。它還將讓我們了解對象的存儲方式:
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在每次數據庫操作中執(zhí)行的SQL語句。以下是我們如何在transaction期間啟用跟蹤:
// 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 ();}
- 經過此修改,我們的應用程序現在產生以下輸出:
INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
INSERT INTO `person` (`id`,`first`,`last`,`age`) VALUES (?,?,?,?)
- 請注意,我們看到的是問號而不是實際值,因為ODB使用準備好的語句并以二進制形式將數據發(fā)送到數據庫。
5. 查詢數據庫中的對象
到目前為止,我們的應用程序不像典型的“Hello World”示例。除了錯誤消息外,它不會打印任何內容。讓我們改變這一點,并教我們的應用程序向數據庫中的人打招呼。為了讓它更有趣,讓我們只向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;}
}
- 我們應用程序的前半部分與之前相同,為簡潔起見,在上面的列表中被替換為“…”。讓我們再次逐一檢查其余部分。
- 這兩個typedef為我們的應用程序中經常使用的兩個模板實例化創(chuàng)建了方便的別名。第一個是人員對象的查詢類型,第二個是該查詢的結果類型。
- 然后我們開始一個新的事務并調用query()數據庫函數。我們傳遞一個查詢表達式(query::age>30),它將返回的對象限制為年齡大于30的對象。我們還將查詢結果保存在局部變量中。
- 接下來的幾行在結果序列上執(zhí)行標準的循環(huán)迭代,為每個返回的人打印hello。然后我們提交事務,就是這樣。讓我們看看這個應用程序將打印什么:
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_testHello, John!
Hello, Jane!
- 這看起來似乎是對的,但我們如何知道查詢實際上使用了數據庫,而不僅僅是使用了早期persist()調用的一些內存。測試這一點的一種方法是注釋掉應用程序中的第一個事務,并在不重新創(chuàng)建數據庫模式的情況下重新運行它。這樣,將返回上次運行期間持久化的對象?;蛘?#xff0c;我們可以重新運行同一個應用程序,而不重新創(chuàng)建模式,并注意到我們現在顯示了重復的對象:
./driver --user odb_test --database odb_testHello, John!
Hello, Jane!
Hello, John!
Hello, Jane!
- 這里發(fā)生的事情是,我們應用程序的前一次運行持久化了一組person對象,當我們重新運行應用程序時,我們持久化了另一組同名但ID不同的對象。稍后運行查詢時,將返回兩個集合中的匹配項。我們可以更改打印“Hello”字符串的行,如下所示,以說明這一點:
cout << "Hello, " << i->first () << " (" << i->id () << ")!" << endl;
- 如果我們現在重新運行這個修改后的程序,再次不重新創(chuàng)建數據庫模式,我們將得到以下輸出:
./driver --user odb_test --database odb_testHello, John (1)!
Hello, Jane (2)!
Hello, John (4)!
Hello, Jane (5)!
Hello, John (7)!
Hello, Jane (8)!
- 上述列表中缺少的標識符3、6和9屬于此查詢未選擇的“Joe Dirt”對象。
6. 更新持久對象
雖然使對象持久化,然后使用查詢選擇其中一些是兩個有用的操作,但大多數應用程序還需要更改對象的狀態(tài),然后使這些更改持久化。讓我們通過更新剛剛過生日的Joe的年齡來說明這一點:
// 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的開始和結束與前兩個相同。一旦進入事務,我們調用load()數據庫函數,用Joe的持久狀態(tài)實例化一個人對象。我們傳遞Joe的對象標識符,該標識符是我們之前在使此對象持久化時存儲的。雖然在這里我們使用std::auto_ptr來管理返回的對象,但我們也可以使用另一個智能指針,例如C++11的std::unique_ptr或TR1、C++11或Boost的shared_ptr。有關對象生命周期管理和可用于此的智能指針的更多信息,請參閱第3.3節(jié)“對象和視圖指針”。
- 有了實例化的對象,我們增加了年齡,并調用update()函數來更新數據庫中對象的狀態(tài)。事務提交后,更改將永久生效。
- 如果我們現在運行這個應用程序,我們將在輸出中看到Joe,因為他現在已經30多歲了:
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_testHello, John!
Hello, Jane!
Hello, Joe!
- 如果我們沒有Joe的標識符怎么辦?也許這個對象在我們的應用程序的另一次運行中被持久化了,或者被另一個應用程序完全持久化了。假設數據庫中只有一個Joe Dirt,我們可以使用查詢工具來提出上述事務的另一種實現:
// 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. 定義和使用視圖
假設我們需要收集一些關于數據庫中存儲的人員的基本統(tǒng)計數據。比如總人數,以及最低和最高年齡。一種方法是查詢數據庫中的所有person對象,然后在迭代查詢結果時計算這些信息。雖然這種方法可能適用于只有三個人的數據庫,但如果我們有大量的對象,它的效率會非常低。
- 雖然從面向對象編程的角度來看,它可能不是概念上純粹的,但關系數據庫可以比我們自己在應用程序的過程中執(zhí)行相同的操作更快、更經濟地執(zhí)行一些計算。
- 為了支持這種情況,網上解決機構提供了視圖的概念。ODB視圖是一個C++類,它體現了一個或多個持久對象或數據庫表的輕量級只讀投影,或者是本機SQL查詢執(zhí)行或存儲過程調用的結果。
- 視圖的一些常見應用包括從對象或列數據庫表加載數據成員的子集,執(zhí)行和處理任意SQL查詢的結果,包括聚合查詢,以及使用對象關系或自定義連接條件連接多個對象和/或數據庫表。
- 以下是我們如何定義person_stat視圖,該視圖返回有關person對象的基本統(tǒng)計信息:
#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;
};
- 通常,為了獲得視圖的結果,我們使用與在數據庫中查詢對象時相同的query()函數。然而,在這里,我們執(zhí)行的是一個聚合查詢,它總是只返回一個元素。因此,我們可以使用快捷方式query_value()函數,而不是獲取結果實例并對其進行迭代。以下是我們如何使用剛剛創(chuàng)建的視圖加載和打印統(tǒng)計數據:
// 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 ();}
- 如果我們現在將person_stat視圖添加到person.hxx標頭中,將上述事務添加到driver.cxx,并重新編譯和運行我們的示例,那么我們將在輸出中看到以下附加行:
count : 3
min age: 31
max age: 33
8. 刪除持久化對象
我們將討論的最后一個操作是從數據庫中刪除持久對象。以下代碼片段顯示了如何刪除給定標識符的對象:
// John Doe is no longer in our database.//{transaction t (db->begin ());db->erase<person> (john_id);t.commit ();}
- 為了從數據庫中刪除John,我們啟動一個事務,使用John的對象id調用erase()數據庫函數,并提交事務。事務提交后,被擦除的對象不再持久。
- 如果我們手頭沒有對象id,我們可以使用查詢來查找和刪除對象:
// 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. 更改持久類
當臨時C++類的定義發(fā)生變化時,例如通過添加或刪除數據成員,我們不必擔心該類的任何現有實例與新定義不匹配。畢竟,為了使類更改有效,我們必須重新啟動應用程序,并且沒有一個瞬態(tài)實例能夠幸存下來。
- 對于持久類來說,事情并不那么簡單。因為它們存儲在數據庫中,因此在應用程序重啟后仍然存在,所以我們遇到了一個新問題:一旦我們更改了持久類,現有對象(對應于舊定義)的狀態(tài)會發(fā)生什么變化?
- 使用舊對象的問題稱為數據庫模式演化,這是一個復雜的問題,ODB為處理它提供了全面的支持。
- 假設在使用我們的person持久類一段時間并創(chuàng)建了許多包含其實例的數據庫后,我們意識到對于某些人來說,我們還需要存儲他們的中間名。如果我們繼續(xù)添加新的數據成員,那么新數據庫的一切都會正常工作。然而,現有數據庫的表與新的類定義不對應。具體來說,生成的數據庫支持代碼現在期望有一個列來存儲中間名。但在舊數據庫中從未創(chuàng)建過這樣的列。
- ODB可以自動生成SQL語句,這些語句將遷移舊數據庫以匹配新的類定義。但首先,我們需要通過為我們的對象模型定義一個版本來啟用模式演化支持:
// person.hxx
//#pragma db model version(1, 1)class person
{...std::string first_;std::string last_;unsigned short age_;
};
- 版本pragma中的第一個數字是基本模型版本。這是我們能夠遷移的最低版本。第二個數字是當前型號版本。由于我們還沒有對持久類進行任何更改,因此這兩個值都是1。
- 接下來,我們需要使用ODB編譯器重新編譯person.hxx頭文件,就像之前一樣:
odb -d mysql --generate-query --generate-schema person.hxx
-如果我們現在查看ODB編譯器生成的文件列表,我們會注意到一個新文件:person.xml。這是一個更改日志文件,ODB編譯器在其中跟蹤與我們的類更改相對應的數據庫更改。請注意,此文件由ODB編譯器自動維護,我們所要做的就是在重新編譯之間保留它。
- 現在,我們已經準備好將中間名添加到person類中。我們還為它提供了一個默認值(空字符串),該值將分配給舊數據庫中的現有對象。請注意,我們還增加了當前版本:
// 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_;
};
- 如果我們現在再次重新編譯person.hxx標頭,我們將看到兩個額外生成的文件:
person-002-pre.sql
和person-002-post.sql
。這兩個文件包含從版本1到版本2的模式遷移語句。與模式創(chuàng)建類似,模式遷移語句也可以嵌入到生成的C++代碼中。 person-002-pre.sql
和person-002-post.sql
是模式遷移前后的文件。要遷移我們的一個舊數據庫,我們首先執(zhí)行預遷移文件:
mysql --user=odb_test --database=odb_test < person-002-pre.sql
- 如果需要,我們可以在模式遷移前后運行數據遷移代碼。在這個階段,我們既可以訪問舊數據,也可以存儲新數據。在我們的例子中,我們不需要任何數據遷移代碼,因為我們?yōu)樗鞋F有對象的中間名分配了默認值。
- 為了完成遷移過程,我們執(zhí)行遷移后語句:
mysql --user=odb_test --database=odb_test < person-002-post.sql
10. 使用多個數據庫
訪問多個數據庫(即數據存儲)只需創(chuàng)建多個代表每個數據庫的odb::<db>:數據庫實例即可。例如:
odb::mysql::database db1 ("john", "secret", "test_db1");
odb::mysql::database db2 ("john", "secret", "test_db2");
- 一些數據庫系統(tǒng)還允許將多個數據庫附加到同一實例。一個更有趣的問題是,我們如何從同一應用程序訪問多個數據庫系統(tǒng)(即數據庫實現)。例如,我們的應用程序可能需要將一些對象存儲在遠程MySQL數據庫中,而將其他對象存儲在本地SQLite文件中?;蛘?#xff0c;我們的應用程序可能需要能夠將其對象存儲在用戶在運行時選擇的數據庫系統(tǒng)中。
- ODB提供全面的多數據庫支持,從與特定數據庫系統(tǒng)的緊密集成到能夠編寫與數據庫無關的代碼以及動態(tài)加載單個數據庫系統(tǒng)支持。
- 添加多數據庫支持的第一步是重新編譯person.hxx標頭,為其他數據庫系統(tǒng)生成數據庫支持代碼:
odb --multi-database dynamic -d common -d mysql -d pgsql \
--generate-query --generate-schema person.hxx
- –multi-database ODB編譯器選項啟用多數據庫支持。目前,我們傳遞給此選項的動態(tài)值的含義并不重要,但如果你好奇,請參閱第16章。此命令的結果是生成三組文件:person-odb。?xx(公共接口;對應公共數據庫),person-odb-mysql。?xx(MySQL支持代碼)和person-odb-pgsql。?xx(PostgreSQL支持代碼)。還有兩個模式文件:person-mysql.sql和person-pgsql.sql。
- 我們需要在driver.cxx中更改的唯一部分是如何創(chuàng)建數據庫實例。具體來說,這條線:
auto_ptr<database> db (new odb::mysql::database (argc, argv));
- 現在,我們的示例能夠將數據存儲在MySQL或PostgreSQL中,因此我們需要以某種方式允許調用者指定我們必須使用哪個數據庫。為了簡單起見,我們將使第一個命令行參數指定我們必須使用的數據庫系統(tǒng),而其余參數將包含特定于數據庫的選項,我們將像以前一樣將這些選項傳遞給odb:::database構造函數。讓我們把所有這些邏輯放在一個單獨的函數中,我們將調用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....
- 就是這樣。剩下的就是構建和運行我們的示例:
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
- 以下是我們如何訪問MySQL數據庫:
mysql --user=odb_test --database=odb_test < person-mysql.sql
./driver mysql --user odb_test --database odb_test
- 或者PostgreSQL數據庫:
psql --user=odb_test --dbname=odb_test -f person-pgsql.sql
./driver pgsql --user odb_test --database odb_test
11. 總結
本章介紹了一個非常簡單的應用程序,盡管如此,它還是執(zhí)行了所有核心數據庫功能:persist()、query()、load()、update()和erase()。我們還看到,編寫使用ODB的應用程序涉及以下步驟:
- 在頭文件中聲明持久類。
- 編譯這些標頭以生成數據庫支持代碼。
- 將應用程序與生成的代碼和兩個ODB運行時庫鏈接起來。
如果在這一點上,很多事情似乎都不清楚,不要擔心。本章的目的只是讓您大致了解如何使用ODB持久化C++對象。我們將在本手冊的其余部分介紹所有細節(jié)。