赞
踩
在本章中,我们将使用传统的“Hello World”示例展示如何创建一个依赖于ODB进行对象持久化的简单C++应用程序。特别是,我们将讨论如何声明持久类、生成数据库支持代码以及编译和运行我们的应用程序。我们还将学习如何使对象持久化,加载、更新和删除持久化对象,以及在数据库中查询符合特定条件的持久化对象。该示例还展示了如何定义和使用视图,这是一种允许我们创建持久化对象、数据库表的投影,或处理本机SQL查询或存储过程调用结果的机制。
本章中介绍的代码基于hello示例,该示例可以在odb-examples中找到。
// 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.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_; };
<ODB/core.hxx>
。此标头提供了许多核心ODB声明,例如ODB::access,用于定义持久类。pragma
。这个杂注告诉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
我们在上一节中创建的持久类定义对于任何可以实际完成这项工作并将人的数据存储到数据库的代码来说都特别轻。在C++的其他ORM库中,没有序列化或反序列化代码,甚至没有数据成员注册,你通常必须手工编写。这是因为在数据库和C++对象表示之间转换的ODB代码是由ODB编译器自动生成的。
odb -d mysql --generate-query person.hxx
person.hxx:10:24: fatal error: odb/core.hxx: No such file or directory
odb -I.../libodb -d mysql --generate-query person.hxx
odb -d mysql --generate-query --generate-schema person.hxx
假设main()函数和应用程序代码保存在driver.cxx中,并且数据库支持代码和模式如前一节所述生成,为了构建我们的应用程序,我们首先需要编译所有的C++源文件,然后将它们与两个ODB运行时库链接。
在UNIX上,编译部分可以用以下命令完成(用c++编译器名称替换c++;有关Microsoft Visual Studio设置,请参阅odb示例包):
c++ -c driver.cxx
c++ -c person-odb.cxx
c++ -o driver driver.o person-odb.o -lodb-mysql -lodb
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_test
// 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; } }
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_test
mysql --user=odb_test --database=odb_test Welcome 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
// 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 (?,?,?,?)
到目前为止,我们的应用程序不像典型的“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; } }
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_test
Hello, John!
Hello, Jane!
./driver --user odb_test --database odb_test
Hello, John!
Hello, Jane!
Hello, John!
Hello, Jane!
cout << "Hello, " << i->first () << " (" << i->id () << ")!" << endl;
./driver --user odb_test --database odb_test
Hello, John (1)!
Hello, Jane (2)!
Hello, John (4)!
Hello, Jane (5)!
Hello, John (7)!
Hello, Jane (8)!
虽然使对象持久化,然后使用查询选择其中一些是两个有用的操作,但大多数应用程序还需要更改对象的状态,然后使这些更改持久化。让我们通过更新刚刚过生日的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; } }
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_test
Hello, John!
Hello, Jane!
Hello, Joe!
// 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 (); }
假设我们需要收集一些关于数据库中存储的人员的基本统计数据。比如总人数,以及最低和最高年龄。一种方法是查询数据库中的所有person对象,然后在迭代查询结果时计算这些信息。虽然这种方法可能适用于只有三个人的数据库,但如果我们有大量的对象,它的效率会非常低。
#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;
};
// 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 (); }
count : 3
min age: 31
max age: 33
我们将讨论的最后一个操作是从数据库中删除持久对象。以下代码片段显示了如何删除给定标识符的对象:
// 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 (); }
当临时C++类的定义发生变化时,例如通过添加或删除数据成员,我们不必担心该类的任何现有实例与新定义不匹配。毕竟,为了使类更改有效,我们必须重新启动应用程序,并且没有一个瞬态实例能够幸存下来。
// person.hxx
//
#pragma db model version(1, 1)
class person
{
...
std::string first_;
std::string last_;
unsigned short age_;
};
odb -d mysql --generate-query --generate-schema person.hxx
-如果我们现在查看ODB编译器生成的文件列表,我们会注意到一个新文件:person.xml。这是一个更改日志文件,ODB编译器在其中跟踪与我们的类更改相对应的数据库更改。请注意,此文件由ODB编译器自动维护,我们所要做的就是在重新编译之间保留它。
// 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-002-pre.sql
和person-002-post.sql
。这两个文件包含从版本1到版本2的模式迁移语句。与模式创建类似,模式迁移语句也可以嵌入到生成的C++代码中。person-002-pre.sql
和person-002-post.sql
是模式迁移前后的文件。要迁移我们的一个旧数据库,我们首先执行预迁移文件:mysql --user=odb_test --database=odb_test < person-002-pre.sql
mysql --user=odb_test --database=odb_test < person-002-post.sql
访问多个数据库(即数据存储)只需创建多个代表每个数据库的odb::<db>:数据库实例即可。例如:
odb::mysql::database db1 ("john", "secret", "test_db1");
odb::mysql::database db2 ("john", "secret", "test_db2");
odb --multi-database dynamic -d common -d mysql -d pgsql \
--generate-query --generate-schema person.hxx
auto_ptr<database> db (new odb::mysql::database (argc, argv));
// 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)); else cerr << "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 --user=odb_test --database=odb_test < person-mysql.sql
./driver mysql --user odb_test --database odb_test
psql --user=odb_test --dbname=odb_test -f person-pgsql.sql
./driver pgsql --user odb_test --database odb_test
本章介绍了一个非常简单的应用程序,尽管如此,它还是执行了所有核心数据库功能:persist()、query()、load()、update()和erase()。我们还看到,编写使用ODB的应用程序涉及以下步骤:
如果在这一点上,很多事情似乎都不清楚,不要担心。本章的目的只是让您大致了解如何使用ODB持久化C++对象。我们将在本手册的其余部分介绍所有细节。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。