当前位置: 首页 > news >正文

【开源库学习】libodb库学习(一)

Hello World Example

在本章中,我们将使用传统的“Hello World”示例展示如何创建一个依赖于ODB进行对象持久化的简单C++应用程序。特别是,我们将讨论如何声明持久类、生成数据库支持代码以及编译和运行我们的应用程序。我们还将学习如何使对象持久化,加载、更新和删除持久化对象,以及在数据库中查询符合特定条件的持久化对象。该示例还展示了如何定义和使用视图,这是一种允许我们创建持久化对象、数据库表的投影,或处理本机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编译器后面的类是持久的。请注意,使类持久化并不意味着该类的所有对象都会自动存储在数据库中。您仍然可以像以前一样创建此类的普通或临时实例。不同之处在于,现在您可以使这种瞬态实例持久化,我们稍后会看到。
  • 第三个变化是添加了默认构造函数。当从持久状态实例化对象时,ODB生成的数据库支持代码将使用此构造函数。正如我们为person类所做的那样,如果你不想让类的用户使用默认构造函数,你可以将其设置为私有或受保护。另请注意,有一些限制,可以在没有默认构造函数的情况下拥有持久类。
  • 通过第四个更改,我们使odb::access类成为person类的友元类。这是使数据库支持代码可以访问默认构造函数和数据成员所必需的。如果你的类有一个公共的默认构造函数,以及公共数据成员或数据成员的公共访问器和修饰符,那么朋友声明是不必要的。
  • 最后的更改添加了一个名为id_的数据成员,前面有另一个pragma。在ODB中,每个持久对象在其类中通常都有一个唯一的标识符。或者,换句话说,同一类型的两个持久实例没有相同的标识符。虽然可以定义一个没有对象id的持久类,但可以在这样的类上执行的数据库操作的数量是有限的。对于我们的类,我们使用一个整数id。id_member之前的db-id-auto杂注告诉ODB编译器以下成员是对象的标识符。自动说明符表示它是数据库分配的id。当对象被持久化时,数据库将自动生成一个唯一的id并将其分配给对象。
  • 在这个例子中,我们选择添加一个标识符,因为现有的成员都不能达到同样的目的。但是,如果一个类已经有一个具有合适属性的成员,那么使用该成员作为标识符是很自然的。例如,如果我们的人员类别包含某种形式的个人身份信息(美国的SSN或其他国家的ID/护照号码),那么我们可以将其用作ID。或者,如果我们存储了与每个人相关的电子邮件,那么如果假设每个人都有一个唯一的电子邮件地址,我们就可以使用它。
  • 再举一个例子,考虑以下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. 生成数据库支持代码

我们在上一节中创建的持久类定义对于任何可以实际完成这项工作并将人的数据存储到数据库的代码来说都特别轻。在C++的其他ORM库中,没有序列化或反序列化代码,甚至没有数据成员注册,你通常必须手工编写。这是因为在数据库和C++对象表示之间转换的ODB代码是由ODB编译器自动生成的。

  • 为了编译我们在上一节中创建的person.hxx标头并生成MySQL数据库的支持代码,我们从终端(UNIX)或命令提示符(Windows)调用ODB编译器:
odb -d mysql --generate-query person.hxx
  • 在本章的其余部分,我们将使用MySQL作为首选数据库,但也可以使用其他受支持的数据库系统。
    如果您没有安装公共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中的类执行数据库操作,编译person-odb.cxx并将生成的对象文件链接到您的应用程序。
  • 您可能想知道–generate查询选项的用途。它指示ODB编译器生成可选的查询支持代码,我们稍后将在“Hello World”示例中使用这些代码。我们会发现另一个有用的选项是——生成模式。此选项使ODB编译器生成第四个文件person.sql,它是person.hxx中定义的持久类的数据库模式:
odb -d mysql --generate-query --generate-schema person.hxx
  • 数据库模式文件包含创建存储持久类所需的表的SQL语句。
  • 如果您想查看所有可用ODB编译器选项的列表,请参阅ODB Compiler Command Line Manual
  • 现在我们有了持久类和数据库支持代码,剩下的唯一部分就是应用程序代码,它对所有这些都有用。

3. 编译和运行

假设main()函数和应用程序代码保存在driver.cxx中,并且数据库支持代码和模式如前一节所述生成,为了构建我们的应用程序,我们首先需要编译所有的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文件创建数据库模式。对于MySQL,我们可以使用MySQL客户端程序,例如:
mysql --user=odb_test --database=odb_test < person.sql
  • 上述命令将以用户odb_test的身份登录到本地MySQL服务器,无需密码,并使用名为odb_test.的数据库。请注意,执行此命令后,存储在odb_test数据库中的所有数据都将被删除。
  • 还要注意,使用独立生成的SQL文件并不是在ODB中创建数据库模式的唯一方法。我们还可以将模式直接嵌入到我们的应用程序中,或者使用不是由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>,它们定义了独立于数据库系统的odb::database和odb:;transaction接口。然后我们包括<odb/mysql/database.hxx>,它定义了数据库接口的mysql实现。最后,我们包括person.hxx和person-odb.hxx,它们定义了我们的持久人类。
  • 然后我们有两个使用命名空间指令。第一个引入了标准名称空间中的名称,第二个引入了ODB声明,我们稍后将在文件中使用这些声明。请注意,在第二个指令中,我们使用odb::core命名空间,而不仅仅是odb。前者只将基本的ODB名称(如数据库和事务类)带入当前名称空间,而不带任何辅助对象。这最大限度地减少了与其他库名称冲突的可能性。还要注意,在限定单个名称时,您应该继续使用odb命名空间。例如,您应该编写odb::database,而不是odb::core::database。
  • 一旦我们进入main(),我们做的第一件事就是创建MySQL数据库对象。请注意,这是driver.cxx中明确提到MySQL的最后一行;其余代码通过通用接口工作,并且独立于数据库系统。我们使用argc/argv-mysql::database构造函数,它从命令行自动提取数据库参数,如登录名、密码、数据库名等。在您自己的应用程序中,您可能更喜欢使用其他mysql::数据库构造函数,它们允许您直接传递此信息。
  • 接下来,我们创建三个人对象。现在它们是瞬态对象,这意味着如果我们此时终止应用程序,它们将消失,没有任何存在的证据。下一行启动数据库事务。我们将在本手册稍后详细讨论交易。目前,我们需要知道的是,所有ODB数据库操作都必须在事务中执行,事务是一个原子工作单元;事务中执行的所有数据库操作要么一起成功(提交),要么自动撤消(回滚)。
  • 一旦我们进入事务,我们就对每个person对象调用persist()数据库函数。此时,每个对象的状态都保存在数据库中。但是,请注意,除非提交事务,否则此状态不是永久性的。例如,如果我们的应用程序此时崩溃,仍然没有证据表明我们的对象曾经存在过。
  • 在我们的例子中,当我们调用persist()时,还会发生另一件事。请记住,我们决定为我们的person对象使用数据库分配的标识符。对persist()的调用就是执行此赋值的地方。一旦此函数返回,id_成员将包含此对象的唯一标识符。为了方便起见,persist()函数还返回它持久化的对象标识符的副本。我们将每个对象的返回标识符保存在局部变量中。我们将在稍后使用这些标识符对持久对象执行其他数据库操作。
  • 在我们持久化了对象之后,是时候提交事务并使更改永久化了。只有在commit()函数成功返回后,我们才能保证对象是持久的。继续崩溃示例,如果我们的应用程序在提交后因任何原因终止,数据库中对象的状态将保持不变。事实上,我们很快就会发现,我们的应用程序可以重新启动并从数据库中加载原始对象。还要注意,事务必须通过commit()调用显式提交。如果事务对象在没有显式提交或回滚事务的情况下离开作用域,它将自动回滚。这种行为使您不必担心在事务中抛出异常;如果它们越过事务边界,事务将自动回滚,对数据库所做的所有更改都将撤消。
  • 我们示例中的最后一段代码是处理数据库异常的catch块。我们通过捕获基本ODB异常并打印诊断来实现这一点。
  • 现在让我们编译,然后运行我们的第一个ODB应用程序:
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_test
  • 我们的第一个应用程序除了错误消息外什么都不打印,所以我们无法真正判断它是否真的将对象的状态存储在数据库中。虽然我们很快就会让我们的应用程序更有趣,但现在我们可以使用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在每次数据库操作中执行的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使用准备好的语句并以二进制形式将数据发送到数据库。

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为我们的应用程序中经常使用的两个模板实例化创建了方便的别名。第一个是人员对象的查询类型,第二个是该查询的结果类型。
  • 然后我们开始一个新的事务并调用query()数据库函数。我们传递一个查询表达式(query::age>30),它将返回的对象限制为年龄大于30的对象。我们还将查询结果保存在局部变量中。
  • 接下来的几行在结果序列上执行标准的循环迭代,为每个返回的人打印hello。然后我们提交事务,就是这样。让我们看看这个应用程序将打印什么:
mysql --user=odb_test --database=odb_test < person.sql
./driver --user odb_test --database odb_testHello, John!
Hello, Jane!
  • 这看起来似乎是对的,但我们如何知道查询实际上使用了数据库,而不仅仅是使用了早期persist()调用的一些内存。测试这一点的一种方法是注释掉应用程序中的第一个事务,并在不重新创建数据库模式的情况下重新运行它。这样,将返回上次运行期间持久化的对象。或者,我们可以重新运行同一个应用程序,而不重新创建模式,并注意到我们现在显示了重复的对象:
./driver --user odb_test --database odb_testHello, John!
Hello, Jane!
Hello, John!
Hello, Jane!
  • 这里发生的事情是,我们应用程序的前一次运行持久化了一组person对象,当我们重新运行应用程序时,我们持久化了另一组同名但ID不同的对象。稍后运行查询时,将返回两个集合中的匹配项。我们可以更改打印“Hello”字符串的行,如下所示,以说明这一点:
cout << "Hello, " << i->first () << " (" << i->id () << ")!" << endl;
  • 如果我们现在重新运行这个修改后的程序,再次不重新创建数据库模式,我们将得到以下输出:
./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. 更新持久对象

虽然使对象持久化,然后使用查询选择其中一些是两个有用的操作,但大多数应用程序还需要更改对象的状态,然后使这些更改持久化。让我们通过更新刚刚过生日的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的持久状态实例化一个人对象。我们传递Joe的对象标识符,该标识符是我们之前在使此对象持久化时存储的。虽然在这里我们使用std::auto_ptr来管理返回的对象,但我们也可以使用另一个智能指针,例如C++11的std::unique_ptr或TR1、C++11或Boost的shared_ptr。有关对象生命周期管理和可用于此的智能指针的更多信息,请参阅第3.3节“对象和视图指针”。
  • 有了实例化的对象,我们增加了年龄,并调用update()函数来更新数据库中对象的状态。事务提交后,更改将永久生效。
  • 如果我们现在运行这个应用程序,我们将在输出中看到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. 定义和使用视图

假设我们需要收集一些关于数据库中存储的人员的基本统计数据。比如总人数,以及最低和最高年龄。一种方法是查询数据库中的所有person对象,然后在迭代查询结果时计算这些信息。虽然这种方法可能适用于只有三个人的数据库,但如果我们有大量的对象,它的效率会非常低。

  • 虽然从面向对象编程的角度来看,它可能不是概念上纯粹的,但关系数据库可以比我们自己在应用程序的过程中执行相同的操作更快、更经济地执行一些计算。
  • 为了支持这种情况,网上解决机构提供了视图的概念。ODB视图是一个C++类,它体现了一个或多个持久对象或数据库表的轻量级只读投影,或者是本机SQL查询执行或存储过程调用的结果。
  • 视图的一些常见应用包括从对象或列数据库表加载数据成员的子集,执行和处理任意SQL查询的结果,包括聚合查询,以及使用对象关系或自定义连接条件连接多个对象和/或数据库表。
  • 以下是我们如何定义person_stat视图,该视图返回有关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;
};
  • 通常,为了获得视图的结果,我们使用与在数据库中查询对象时相同的query()函数。然而,在这里,我们执行的是一个聚合查询,它总是只返回一个元素。因此,我们可以使用快捷方式query_value()函数,而不是获取结果实例并对其进行迭代。以下是我们如何使用刚刚创建的视图加载和打印统计数据:
// 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++类的定义发生变化时,例如通过添加或删除数据成员,我们不必担心该类的任何现有实例与新定义不匹配。毕竟,为了使类更改有效,我们必须重新启动应用程序,并且没有一个瞬态实例能够幸存下来。

  • 对于持久类来说,事情并不那么简单。因为它们存储在数据库中,因此在应用程序重启后仍然存在,所以我们遇到了一个新问题:一旦我们更改了持久类,现有对象(对应于旧定义)的状态会发生什么变化?
  • 使用旧对象的问题称为数据库模式演化,这是一个复杂的问题,ODB为处理它提供了全面的支持。
  • 假设在使用我们的person持久类一段时间并创建了许多包含其实例的数据库后,我们意识到对于某些人来说,我们还需要存储他们的中间名。如果我们继续添加新的数据成员,那么新数据库的一切都会正常工作。然而,现有数据库的表与新的类定义不对应。具体来说,生成的数据库支持代码现在期望有一个列来存储中间名。但在旧数据库中从未创建过这样的列。
  • 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.sqlperson-002-post.sql。这两个文件包含从版本1到版本2的模式迁移语句。与模式创建类似,模式迁移语句也可以嵌入到生成的C++代码中。
  • person-002-pre.sqlperson-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

10. 使用多个数据库

访问多个数据库(即数据存储)只需创建多个代表每个数据库的odb::<db>:数据库实例即可。例如:

odb::mysql::database db1 ("john", "secret", "test_db1");
odb::mysql::database db2 ("john", "secret", "test_db2");
  • 一些数据库系统还允许将多个数据库附加到同一实例。一个更有趣的问题是,我们如何从同一应用程序访问多个数据库系统(即数据库实现)。例如,我们的应用程序可能需要将一些对象存储在远程MySQL数据库中,而将其他对象存储在本地SQLite文件中。或者,我们的应用程序可能需要能够将其对象存储在用户在运行时选择的数据库系统中。
  • ODB提供全面的多数据库支持,从与特定数据库系统的紧密集成到能够编写与数据库无关的代码以及动态加载单个数据库系统支持。
  • 添加多数据库支持的第一步是重新编译person.hxx标头,为其他数据库系统生成数据库支持代码:
odb --multi-database dynamic -d common -d mysql -d pgsql \
--generate-query --generate-schema person.hxx
  • –multi-database ODB编译器选项启用多数据库支持。目前,我们传递给此选项的动态值的含义并不重要,但如果你好奇,请参阅第16章。此命令的结果是生成三组文件:person-odb。?xx(公共接口;对应公共数据库),person-odb-mysql。?xx(MySQL支持代码)和person-odb-pgsql。?xx(PostgreSQL支持代码)。还有两个模式文件:person-mysql.sql和person-pgsql.sql。
  • 我们需要在driver.cxx中更改的唯一部分是如何创建数据库实例。具体来说,这条线:
auto_ptr<database> db (new odb::mysql::database (argc, argv));
  • 现在,我们的示例能够将数据存储在MySQL或PostgreSQL中,因此我们需要以某种方式允许调用者指定我们必须使用哪个数据库。为了简单起见,我们将使第一个命令行参数指定我们必须使用的数据库系统,而其余参数将包含特定于数据库的选项,我们将像以前一样将这些选项传递给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. 总结

本章介绍了一个非常简单的应用程序,尽管如此,它还是执行了所有核心数据库功能:persist()、query()、load()、update()和erase()。我们还看到,编写使用ODB的应用程序涉及以下步骤:

  1. 在头文件中声明持久类。
  2. 编译这些标头以生成数据库支持代码。
  3. 将应用程序与生成的代码和两个ODB运行时库链接起来。

如果在这一点上,很多事情似乎都不清楚,不要担心。本章的目的只是让您大致了解如何使用ODB持久化C++对象。我们将在本手册的其余部分介绍所有细节。

相关文章:

【开源库学习】libodb库学习(一)

Hello World Example 在本章中&#xff0c;我们将使用传统的“Hello World”示例展示如何创建一个依赖于ODB进行对象持久化的简单C应用程序。特别是&#xff0c;我们将讨论如何声明持久类、生成数据库支持代码以及编译和运行我们的应用程序。我们还将学习如何使对象持久化&…...

Java中SPI机制原理解析

使用SPI机制前后的代码变化 加载MySQL对JDBC的Driver接口实现 在未使用SPI机制之前&#xff0c;使用JDBC操作数据库的时候&#xff0c;一般会写如下的代码&#xff1a;// 通过这行代码手动加载MySql对Driver接口的实现类 Class.forName("com.mysql.jdbc.Driver") Dr…...

数学建模~~~SPSS相关和回归分析

目录 1.双变量相关分析 1.1理论基础 1.2简单散点图的绘制介绍 1.3相关性分析 1.4分析相关性结果 2.简单线性回归分析 2.1简单概括 2.2分析过程 2.3结果分析 3.曲线回归分析 3.1问题介绍 3.2分析过程 3.3结果分析 1.双变量相关分析 1.1理论基础 双变量相关分析并不…...

【Android】常用基础布局

布局是一种可用于放置很多控件的容器&#xff0c;它可以按照一定的规律调整内部控件的位置&#xff0c;从而编写出精美的界面&#xff0c;布局内不单单可以放控件&#xff0c;也可以嵌套布局&#xff0c;这样可以完成一些复杂的界面&#xff0c;下面就来认识一些常用的布局吧。…...

服务攻防-中间件安全(漏洞复现)

一.中间件-IIS-短文件&解析&蓝屏 IIS现在用的也少了&#xff0c;漏洞也基本没啥用 1、短文件&#xff1a;信息收集 2、文件解析&#xff1a;还有点用 3、HTTP.SYS&#xff1a;蓝屏崩溃 没有和权限挂钩 4、CVE-2017-7269 条件过老 windows 2003上面的漏洞 二.中…...

【SD】深入理解Stable Diffusion与ComfyUI的使用

【SD】深入理解Stable Diffusion与ComfyUI的使用 1. Stable Diffusion&#xff08;SD&#xff09;原理概述2. 各部件详解3. SD的工作流程4. ComfyUI与SD的结合5. 总结 1. Stable Diffusion&#xff08;SD&#xff09;原理概述 整体结构&#xff1a;SD不是单一模型&#xff0c;…...

Linux 12:多线程2

1. 生产者消费者模型 生产者消费者模型有三种关系&#xff0c;两个角色&#xff0c;一个交易场所。 三种关系&#xff1a; 生产者之间是什么关系?竞争 - 互斥 消费者和消费者之间?竞争 - 互斥 消费者和消费者之间?互斥和同步 两个角色&#xff1a; 生产者和消费者 一个交…...

Android RSA 加解密

文章目录 一、RSA简介二、RSA 原理介绍三、RSA 秘钥对生成1. 密钥对生成2. 获取公钥3. 获取私钥 四、PublicKey 和PrivateKey 的保存1. 获取公钥十六进制字符串1. 获取私钥十六进制字符串 五、PublicKey 和 PrivateKey 加载1. 加载公钥2. 加载私钥 六、 RSA加解密1. RSA 支持三…...

类与对象-多态-案例3-电脑组装具体实现

#include<iostream> #include<string> using namespace std; //CPU class CPU { public:virtual void calculate() 0; }; //显卡 class GraCard { public:virtual void graphics() 0; }; //存储 class Memory { public:virtual void memory() 0; }; class Compu…...

try-with-resources 语句的用途和优点有哪些,它如何自动管理资源?

在Java编程中&#xff0c;资源管理是一个重要的议题&#xff0c;尤其是当你在代码中使用那些需要显式关闭的资源&#xff0c;比如文件流、数据库连接或者网络套接字等。 如果资源使用完毕后忘记关闭&#xff0c;不仅会导致资源泄露&#xff0c;还可能引起程序性能问题甚至系统…...

GraphRAG参数与使用步骤 | 基于GPT-4o-mini实现更便宜的知识图谱RAG

首先给兄弟朋友们展示一下结论&#xff0c;一个文本18万多字&#xff0c;txt文本大小185K&#xff0c;采用GraphRAG,GPT-4o-mini模型&#xff0c;索引耗时差不多5分钟&#xff0c;消耗API价格0.15美元 GraphRAG介绍 GraphRAG是微软最近开源的一款基于知识图谱技术的框架&#…...

/秋招突击——7/21——复习{堆——数组中的第K大元素}——新作{回溯——全排列、子集、电话号码的字母组合、组合总和、括号生成}

文章目录 引言复习数组中的第K大的最大元素复习实现参考实现 新作回溯模板46 全排列个人实现参考实现 子集个人实现参考实现 电话号码的字母组合复习实现 组合总和个人实现参考实现 括号生成复习实现 总结 引言 昨天的科大讯飞笔试做的稀烂&#xff0c;今天回来好好练习一下&a…...

matlab 异常值检测与处理——Robust Z-score法

目录 一、算法原理1、概述2、主要函数3、参考文献二、代码实现三、结果展示四、相关链接本文由CSDN点云侠翻译,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 1、概述 Robust Z-score法也被称为中位数绝对偏差法。它类似于Z-sc…...

Ubuntu 20安装JDK17和MySQL8.0

一.jdk 安装JDK 第一步&#xff1a;更新软件包&#xff1a;sudo apt update 第二步&#xff1a;安装JDK&#xff1a;sudo apt install openjdk-17-jdk 第三步&#xff1a;检测JDK: java -version 卸载JDK&#xff1a; 第一步&#xff1a;移除JDK包&#xff1a;apt-get purg…...

DC-1靶场打靶第一次!!!!冲冲冲!

今天打了一下DC-1这个靶场&#xff0c;感觉收获比大&#xff0c;我就来记录一下。 我的思路是下面的这个 我们先把靶机导入&#xff0c;然后与我们的liunx(攻击机)在同一个网段中&#xff0c;这也大大的减低难度。 然后我们先对自己这个网段内存活的主机进行操作&#xff0c;我…...

【LeetCode】填充每个节点的下一个右侧节点指针 II

目录 一、题目二、解法完整代码 一、题目 给定一个二叉树&#xff1a; struct Node { int val; Node *left; Node *right; Node *next; } 填充它的每个 next 指针&#xff0c;让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点&#xff0c;则将 next 指针设置为 NUL…...

mac无法清空废纸篓怎么办 mac废纸篓清空了如何找回 cleanmymac误删文件怎么恢复

废纸篓相当于“一颗后悔药”&#xff0c;用于临时存储用户删除的文件。我们从从Mac上删除的文件&#xff0c;一般会进入废纸篓中。如果我们后悔了&#xff0c;可以从废纸篓中找回来。然而&#xff0c;有时我们会发现mac无法清空废纸篓&#xff0c;这是怎么回事?本文将探讨一些…...

树上启发加点分治思想

题目链接 思路&#xff1a; 对于一条链可以组成回文串&#xff0c;意味着最多只有一个奇数字母&#xff0c;比起我们记录路径各个字母的个数和&#xff0c;我们可以发现回文串实际上不在意真正的个数&#xff0c;只在意个数的奇偶。又我们发现字母只有20来个&#xff0c;可以使…...

【iOS】类对象的结构分析

目录 对象的分类object_getClass和class方法isa流程和继承链分析isa流程实例验证类的继承链实例验证 类的结构cache_t结构bits分析实例验证属性properties方法methods协议protocolsro类方法 类结构流程图解 对象的分类 OC中的对象主要可以分为3种&#xff1a;实例对象&#xf…...

接口性能优化思路

前言 日常开发中设计接口&#xff0c;响应时间是衡量一个接口质量的重要指标。 接口响应时间这里粗糙地分为三种&#xff1a; 即时响应&#xff1a;毫秒级&#xff0c;小于500毫秒快速响应&#xff1a;秒级&#xff0c;大于500毫秒且小于2秒长时间操作&#xff1a;大于2秒&a…...

PyQt5 多线程编程详细教程

PyQt5 多线程编程详细教程 在 PyQt5 中&#xff0c;多线程编程是提高应用程序性能和响应性的重要手段。本教程将详细介绍如何在 PyQt5 中使用 QThread 进行多线程编程&#xff0c;学习如何避免界面冻结和线程安全问题&#xff0c;并通过丰富的案例来展示如何实现这些功能。 Q…...

uniapp小程序上传pdf文件

<template><view class"mainInnBox"><view class"formBox"><!-- 注意&#xff0c;如果需要兼容微信小程序&#xff0c;最好通过setRules方法设置rules规则 --><u-form :model"form" ref"uForm" :rules&quo…...

Python酷库之旅-第三方库Pandas(036)

目录 一、用法精讲 111、pandas.Series.item方法 111-1、语法 111-2、参数 111-3、功能 111-4、返回值 111-5、说明 111-6、用法 111-6-1、数据准备 111-6-2、代码示例 111-6-3、结果输出 112、pandas.Series.xs方法 112-1、语法 112-2、参数 112-3、功能 112-…...

Python爬虫(2) --爬取网页页面

文章目录 爬虫URL发送请求UA伪装requests 获取想要的数据打开网页 总结完整代码 爬虫 Python 爬虫是一种自动化工具&#xff0c;用于从互联网上抓取网页数据并提取有用的信息。Python 因其简洁的语法和丰富的库支持&#xff08;如 requests、BeautifulSoup、Scrapy 等&#xf…...

【iOS】——探究isKindOfClass和isMemberOfClass底层实现

isKindOfClass 判断该对象是否为传入的类或其子类的实例 // 类方法实现&#xff0c;用于检查一个类是否属于另一个类或其父类链上的任何类。(BOOL)isKindOfClass:(Class)cls {// 从当前类开始&#xff0c;tcls将沿着元类的继承链向上遍历。for (Class tcls self->ISA(); …...

Python 热门面试题(七)

Python中如何拷贝对象&#xff1f;浅拷贝和深拷贝的区别是什么&#xff1f; 在Python中&#xff0c;拷贝对象是一个常见的需求&#xff0c;尤其是当你需要修改一个对象但又不想影响原始对象时。Python提供了几种拷贝对象的方法&#xff0c;其中最重要的是浅拷贝&#xff08;sh…...

STM32项目分享:智能宠物喂食系统

目录 一、前言 二、项目简介 1.功能详解 2.主要器件 三、原理图设计 四、PCB硬件设计 1.PCB图 五、程序设计 六、实验效果 七、资料内容 项目分享 一、前言 项目成品图片&#xff1a; 哔哩哔哩视频链接&#xff1a; https://www.bilibili.com/video/BV1zy411z7…...

数据结构——栈的实现(java实现)与相应的oj题

文章目录 一 栈栈的概念:栈的实现&#xff1a;栈的数组实现默认构造方法压栈获取栈元素的个数出栈获取栈顶元素判断当前栈是否为空 java提供的Stack类Stack实现的接口&#xff1a; LinkedList也可以当Stack使用虚拟机栈&#xff0c;栈帧&#xff0c;栈的三个概念 二 栈的一些算…...

linux修改时区为CST

目录 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第一步&#xff1a; 备份原来的时区信息 [rootlocalhost ~]# mv /etc/localtime localtime.bak 第二步&#xff1a; 通过软链接将亚洲/上海 的时区信息 指导时区信息 [rootlocalhost ~]# ln -s /usr/share…...

【Spring Security】初识Spring Security

今天晚上因为一个项目问题&#xff0c;而正式开始学习Spring Security。 这个问题是“APP端的操作员应仅可查看管理后台的项目负责人分配给自己的计划”。 一、Spring Security的核心组件&#xff1a; Spring Security的核心组件包括&#xff1a;SecurityContextHolder、Auth…...

做网站有什么不好/crm管理系统

方法 转载于:https://www.cnblogs.com/sen068/p/5362695.html...

番禺区建设局网站/seo招聘职责

通过Flash的正弦函数可以模拟钟摆&#xff1a; 1、新建一fla文件&#xff0c;命名钟摆.fla 2、F9&#xff0c;在帧上添加脚本&#xff0c;如下&#xff1a; var mc:Sprite new Sprite ;addChild(mc);mc.graphics.lineStyle(3,0xff0066);mc.graphics.moveTo(0,0);mc.graphics.…...

彩票做网站犯法吗/长春seo整站优化

本文研究全球与中国市场宠物项圈的发展现状及未来发展趋势&#xff0c;分别从生产和消费的角度分析宠物项圈的主要生产地区、主要消费地区以及主要的生产商。重点分析全球与中国市场的主要厂商产品特点、产品规格、不同规格产品的价格、产量、产值及全球和中国市场主要生产商的…...

制作动画片的软件/哈尔滨百度关键词优化

仅供参考 1&#xff0c;js&#xff1a; onShareAppMessage(res) {return {title: 我在使用俺搜找客户&#xff0c;10万材料人都在用&#xff0c;就差你了,path: /pages/index,success: function(res) {// 转发成功console.log(res)},fail: function(res) {// 转发失败console.l…...

wordpress顶部加载条/衡阳网站建设

1、对于N枚硬币每次翻转N-1枚&#xff0c;等价于有一枚硬币没有翻转&#xff0c;也等价于一枚硬币翻转了&#xff0c;那么对于第一个问题&#xff1a;最少次数也就是N次&#xff08;直接输出&#xff09; 2、对于N枚硬币翻N-1枚的理解&#xff1a;把整个过程分拆成两个步骤 1&a…...

学网站开发/营销型网站建设总结

大带宽服务器的优势肯定是普通服务器所比不上的。从以下几点说明一下。 一、体验更好 大带宽服务器租用&#xff0c;意味着你的网站可以承受更高的流量&#xff0c;在带宽越大的情况下&#xff0c;能够同时支持的在线用户越多&#xff0c;也不会带来卡顿。此时用户的缓存页面&a…...