Hello World实例
-
声明持久化类
比如一个person.hxx类的头文件如下所示// 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类的声明部分进行改造,需要修改的地方有(1)(2)(3)(4)(5),修改后的声明如下:
// person.hxx // #include <odb/core.hxx> // (1) #include <string> #pragma db object // (2) 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: 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_; };
-
产生odb的中间文件
odb -d mysql --generate-query person.hxx
如果需要mysql语句,则执行如下指令:
odb -d mysql --generate-query --generate-schema person.hxx
-
编译运行
如果是首次运行,需要在mysql数据库中将mysql语句文件导入其中,以此来生成数据库相关的表mysql --user=odb_test --database=odb_test < person.sql
-
创建持久化对象
源码如下:
// 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 { // mysql server's host name: root, password:123456, ip:198.1.17.252, port:30306 auto_ptr<database> db(new odb::mysql::database("root", "123456", "odb_test", "198.1.17.252", 30306)); 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. // 15 Revision 2.4, February 2015 C++ Object Persistence with ODB 2.4 Making Objects Persistent 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; } }
-
查询数据库对象
// 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 { // mysql server's host name: root, password:123456, ip:198.1.17.252, port:30306 auto_ptr<database> db(new odb::mysql::database("root", "123456", "odb_test", "198.1.17.252", 30306)); 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. // 15 Revision 2.4, February 2015 C++ Object Persistence with ODB 2.4 Making Objects Persistent john_id = db->persist (john); jane_id = db->persist (jane); joe_id = db->persist (joe); t.commit (); } 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; } }
-
数据库更新
// 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 { // mysql server's host name: root, password:123456, ip:198.1.17.252, port:30306 auto_ptr<database> db(new odb::mysql::database("root", "123456", "odb_test", "198.1.17.252", 30306)); 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. // 15 Revision 2.4, February 2015 C++ Object Persistence with ODB 2.4 Making Objects Persistent john_id = db->persist (john); jane_id = db->persist (jane); joe_id = db->persist (joe); t.commit (); } // 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 (); } 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; } }
-
删除数据库
// 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 { // mysql server's host name: root, password:123456, ip:198.1.17.252, port:30306 auto_ptr<database> db(new odb::mysql::database("root", "123456", "odb_test", "198.1.17.252", 30306)); 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. // 15 Revision 2.4, February 2015 C++ Object Persistence with ODB 2.4 Making Objects Persistent john_id = db->persist (john); jane_id = db->persist (jane); joe_id = db->persist (joe); t.commit (); } // 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 (); } 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 (); } // John Doe is no longer in our database. // { transaction t (db->begin ()); db->erase<person> (john_id); t.commit (); } // 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 (); } } catch (const odb::exception& e) { cerr << e.what () << endl; return 1; } }
持久化对象的相关处理
-
Objects and Values: Objects属于具有全局唯一标识的实体entity的对象,相当于数据库中的某一条记录,具有id标识符。而Values被称为值对象,这些对象不会单独存在于数据库中,而是存在于实体entity对象的某一个成员变量中。
-
事务(Transaction): 事务是原子(Atomic),一致(consistent),隔离(isolated)和持久(durable)(ACID)的工作单元。事务的代码结构如下:
#include <odb/transaction.hxx> transaction t (db.begin ()) // Perform database operations. t.commit ();
odb::transaction 类有如下接口:
namespace odb { class transaction { public: typedef odb::database database_type; typedef odb::connection connection_type; explicit transaction (transaction_impl*, bool make_current = true); transaction (); void reset (transaction_impl*, bool make_current = true); void commit (); void rollback (); database_type& database (); connection_type& connection (); bool finilized () const; public: static bool has_current (); static transaction& current (); static void current (transaction&); static bool reset_current (); // Callback API. // public: ... }; }
解释说明:
- commit():事务提交
- rollback(): 事务回滚,当然如果没有显示支持需要commit或rollback的话,当transaction类析构时会自动回滚。
- database(): 返回当前事务的database
- connection(): 返回当前事务的connection
- current(): 返回线程的active态的transaction,如果没有active transaction的话,则会抛出odb::not_in_transaction的异常,如果我们想check一下是否有一个有效的transaction在这个线程中,我们可以使用has_current()接口来实现。
- transaction的构造函数输入参数make_current如果为false,则不会自动创建active transaction,之后在使用时通过current()接口人为创建。
- reset_current(): 清除当前的active transaction
以下是针对同一个线程多个transaction的实例代码
transaction t1 (db1.begin ()); // Active transaction. transaction t2 (db2.begin (), false); // Not active. // Perform database operations on db1. transaction::current (t2); // Deactivate t1, activate t2. // Perform database operations on db2. transaction::current (t1); // Switch back to t1. // Perform some more database operations on db1. t1.commit (); transaction::current (t2); // Switch to t2. // Perform some more database operations on db2. t2.commit ();
- reset(): 允许我们重新使用相同的transaction实例来实现多个数据库的事务操作。通常在我们commit()之后后需要发起事务的场景,示例代码如下:
transaction t (db.begin ()); for (size_t i (0); i < n; ++i) { // Perform a database operation, such as persist an object. // Commit the current transaction and start a new one after // every 100 operations. // if (i % 100 == 0) { t.commit (); t.reset (db.begin ()); } } t.commit ();
当然我们还需要注意事务会带来另一个潜在的风险,示例代码如下:
void update_age (database& db, person& p) { transaction t (db.begin ()); p.age (p.age () + 1); db.update (p); t.commit (); }
这段代码如果事务提交失败时,就会回滚,相当于数据库什么都没改,但是内存数据p.age却已经被修改了,而编程人员还未发现此问题,这种问题应该尽量在编码过程中避免。我们可以使用下面的代码来解决:
void update_age (database& db, unsigned long id) { transaction t (db.begin ()); auto_ptr<person> p (db.load<person> (id)); p.age (p.age () + 1); db.update (p); t.commit (); }
当然,还有一种解决方案是当出现异常时,在catch的地方重新从数据库中load数据到内存,实例代码如下:
void update_age (database& db, person& p) { try { transaction t (db.begin ()); p.age (p.age () + 1); db.update (p); t.commit (); } catch (...) { transaction t (db.begin ()); db.load (p.id (), p); t.commit (); throw; } }
-
持久化对象存库(persist)
database::persist()函数的模板如下:
template <typename T> typename object_traits<T>::id_type persist (const T& object); template <typename T> typename object_traits<T>::id_type persist (const object_traits<T>::const_pointer_type& object); template <typename T> typename object_traits<T>::id_type persist (T& object); template <typename T> typename object_traits<T>::id_type persist (const object_traits<T>::pointer_type& object);
实例代码如下:
person john ("John", "Doe", 33); shared_ptr<person> jane (new person ("Jane", "Doe", 32)); transaction t (db.begin ()); db.persist (john); unsigned long jane_id (db.persist (jane)); t.commit (); cerr << "Jane’s id: " << jane_id << endl;
-
加载持久化对象(load)
database::load()函数模板如下:
template <typename T> typename object_traits<T>::pointer_type load (const typename object_traits<T>::id_type& id); template <typename T> void load (const typename object_traits<T>::id_type& id, T& object);
如果load失败(比如你传的id数据库不存在)则会抛出odb::object_not_persistent异常。
示例代码如下:transaction t (db.begin ()); auto_ptr<person> jane (db.load<person> (jane_id)); db.load (jane_id, *jane); t.commit ();
当前如果我们已经load过持久化对象到内存中时,我们希望重新load一下,我们可以使用reload函数,reload函数不会与缓存中的数据进行交互,即直接从数据库中取数据,而不会从缓存中取。函数模板如下:
template <typename T> void reload (T& object); template <typename T> void reload (const object_traits<T>::pointer_type& object);
当我们不确定我们的id在数据库中是否存在时,我们可以通过find()函数来检查,函数模板如下:
template <typename T> typename object_traits<T>::pointer_type find (const typename object_traits<T>::id_type& id); template <typename T> bool find (const typename object_traits<T>::id_type& id, T& object);
第一个find()输入id,返回指针类型,如果id没有找到则返回null,第二个find()输入id,返回bool值,如果bool值为true,则object为数据库的对象实例,如果bool值为false,则表明没有找到对应id的数据
-
持久化对象的更新(update)
先看下database::update()函数的模板
template <typename T> void update (const T& object); template <typename T> void update (const object_traits<T>::const_pointer_type& object); template <typename T> void update (const object_traits<T>::pointer_type& object);
下面通过一个银行账户转账的例子来讲述update()函数的使用,代码如下
void transfer (database& db, unsigned long from_acc, unsigned long to_acc, unsigned int amount) { bank_account from, to; transaction t (db.begin ()); db.load (from_acc, from); if (from.balance () < amount) throw insufficient_funds (); db.load (to_acc, to); to.balance (to.balance () + amount); from.balance (from.balance () - amount); db.update (to); db.update (from); t.commit (); }
上面的例子是将from, to从数据库load到指定对象,我们也可以动态的分配一块内存,然后将内存的数据update到数据库中,实例如下:
void transfer (database& db, unsigned long from_acc, unsigned long to_acc, unsigned int amount) { transaction t (db.begin ()); shared_ptr<bank_account> from (db.load<bank_account> (from_acc)); if (from->balance () < amount) throw insufficient_funds (); shared_ptr<bank_account> to (db.load<bank_account> (to_acc)); to->balance (to->balance () + amount); from->balance (from->balance () - amount); db.update (to); db.update (from); t.commit (); }
-
删除持久化对象(delete)
database::erase_query()函数的模板如下:
template <typename T> void erase (const T& object); template <typename T> void erase (const object_traits<T>::const_pointer_type& object); template <typename T> void erase (const object_traits<T>::pointer_type& object); template <typename T> void erase (const typename object_traits<T>::id_type& id);
使用举例:
person& john = ... shared_ptr<jane> jane = ... unsigned long joe_id = ... transaction t (db.begin ()); db.erase (john); db.erase (jane); db.erase<person> (joe_id); t.commit ();
我们也可以通过erase_query()函数来删除多个匹配的数据库对象,当然使用它的前提是在odb的编译选项中得有–generate-query,erase_query()函数的模板如下:
template <typename T> unsigned long long erase_query (); template <typename T> unsigned long long erase_query (const odb::query<T>&);
第一个函数会删除所有类型为T的数据,第二个函数根据odb::query提供的查询表达式来匹配,样例程序如下:
typedef odb::query<person> query; transaction t (db.begin ()); db.erase_query<person> (query::last == "Doe" && query::age < 30); t.commit ();
与query()函数不同,在调用delete_query()时,我们不能在查询表达式中使用指向对象的成员。但是,我们仍然可以将与指针对应的成员用作具有指向对象的id类型的普通对象成员。这使我们可以比较对象ID并测试指针是否为NULL.接下来我们会通过例子来详细说明
typedef odb::query<employee> query; transaction t (db.begin ()); employer& e = ... // Employer object to be deleted. db.erase_query<employee> (query::employer == e.id ()); db.erase (e); t.commit ();
假设employee对象的成员包含有指向employer对象e的指针,现在我们需要将要删除employer对象,为了避免出现employee中指向employer的指针找不到对象的情况,我们在删除employer对象之前,先删除那些把employer作为成员变量的对象,这里我们通过query来查询一下所有employee对象中,指定e的全部删除。之后再删除e本身
-
执行本地sql语句(execute)
某些场景中,我们可能需要直接使用sql语句来操作数据库,odb提供了相关的api接口。database::execute()函数的重载版本如下:
unsigned long long execute (const char* statement); unsigned long long execute (const std::string& statement); unsigned long long execute (const char* statement, std::size_t length)
使用举例:
transaction t (db.begin ()); db.execute ("DROP TABLE test"); db.execute ("CREATE TABLE test (n INT PRIMARY KEY)"); t.commit ();