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

打印 上一主题 下一主题

主题 539|帖子 539|积分 1617

使用持久对象

   前面的章节为我们提供了ODB的高级概述,并展示了如何使用它在数据库中存储C++对象。在本章中,我们将更详细地研究ODB对象持久性模型以及核心数据库API。我们将从第1节和第3节中的根本概念和术语开始,并继续讨论第4节中的odb::数据库类、第5节中的事务和第6节中的连接。本章的别的部门将讨论核心数据库操纵,并在末了讨论ODB非常。
在本章中,我们将继续使用和扩展我们在上一章中开辟的person持久类。
  1.概念和术语



  • 数据库一词可以指三个不同的东西:应用步伐存储数据的地方的一般概念,用于管理这些数据的软件实现(比方MySQL),末了,一些数据库软件实现可能管理多个通常按名称区分的数据存储。这个名字通常也被称为数据库。
  • 在本手册中,当我们使用数据库一词时,我们指的是上面的第一个寄义,比方,“update()函数将对象的状态生存到数据库中。”数据库管理系统(DBMS)一词通常用于指代数据库一词的第二个寄义。在本手册中,我们将简称为数据库系统,比方“独立于数据库系统的应用步伐代码”。末了,为了将第三个寄义与其他两个寄义区分开来,我们将使用数据库名称,比方“第二个选项指定应用步伐应用于存储其数据的数据库名称。”
  • 在C++中,只有一个范例概念和一个范例实例。比方,根本范例(如int)在很大水平上被视为用户界说的类范例。然而,在持久性方面,我们必须对可以存储在数据库中的某些C++范例施加某些限定和要求。因此,我们将持久C++范例分为两组:对象范例和值范例。对象范例的实例称为对象,而值范例的实例则称为值。
  • 对象是一个独立的实体。它可以在数据库中独立于其他对象举行存储、更新和删除。通常,对象有一个标识符,称为对象id,在数据库中对象范例的全部实例中都是唯一的。相比之下,值只能作为对象的一部门存储在数据库中,并且没有自己的唯一标识符。
  • 一个对象由数据成员构成,这些数据成员要么是值(“值范例”),要么是指向其他对象的指针(“关系”),或者是值的容器,要么是指针指向其他对象(“容器”)。指向其他对象和容器的指针可以被视为特别范例的值,因为它们也只能作为对象的一部门存储在数据库中。
  • 对象范例是一个C++类。由于这种一对一的关系,我们将瓜代使用对象范例和对象类这两个术语。相比之下,值范例可以是根本的C++范例,如int或类范例,如std::string。如果一个值由其他值构成,则称为复合值及其范例——复合值范例(“复合值范例”)。否则,该值称为简单值及其范例——简单值范例(“简单值范例”)。请注意,简单值和复合值之间的区别是概念性的,而不是代表性的。比方,std::string是一种简单的值范例,因为从概念上讲,string是一个单一的值,即使string类的表示可能包含多个数据成员,每个数据成员都可以被视为一个值。毕竟上,不同的应用步伐可以将相同的值范例视为简单值范例和复合值范例。
  • 固然在纯面向对象的应用步伐中不是严格必要的,但实际思量通常要求我们只加载对象数据成员的子集或来自多个对象的成员组合。我们可能还需要将一些计算分解到关系数据库中,而不是在应用步伐的过程中实行它们。为了支持这些要求,ODB区分了第三种C++范例,称为视图(“视图”)。ODB视图是一个C++类,它表现了一个或多个持久对象或数据库表或本机SQL查询实行效果的轻量级只读投影。
  • 理解全部这些概念如何映射到关系模型,有望使这些区别更加清楚。在关系数据库中,对象范例映射到表,值范例映射到一个或多个列。简单值范例被映射到一列,而复合值范例则被映射到几列。对象作为一行存储在此表中,值作为一个或多个单元格存储在此行中。一个简单值存储在一个单元格中,而一个复合值占用多个单元格。视图不是持久实体,也不存储在数据库中。相反,它是一种用于捕获SQL查询效果单行的数据布局。
  • 回到简单值和复合值之间的区别,思量一个有三个整数成员的日期范例:年、月和日。在一个应用步伐中,它可以被视为一个复合值,每个成员将在关系数据库中获得自己的列。在另一个应用步伐中,它可以被视为一个简单的值,并存储在单个列中,作为距离某个预界说日期的天数。
  • 到目前为止,我们一直使用术语持久类来指代对象类。我们将继续如许做,即使值范例也可以是类。这种不对称的缘故因由是,在数据库操纵中,值范例具有从属性质。请记着,值从不直接存储,而是作为包含它们的对象的一部门存储。因此,当我们说要使C++类持久化或在数据库中持久化类的实例时,我们总是引用对象类而不是值类。
  • 通常,您会使用对象范例来建模实际天下中的实体,即具有自己身份的事物。比方,在上一章中,我们创建了一个person类来建模一个人,这是一个真实天下的实体。我们在person类中用作数据成员的姓名和年龄显然是值。很难想象31岁或“Joe”这个名字有自己的身份。
  • 确定某物是物体照旧代价的一个很好的测试是思量其他物体是否可能引用它。一个人显然是物体,因为它可以被配偶、雇主或银行等其他物体引用。另一方面,一个人的年龄或名字不是其他物体通常会提及的。
  • 别的,当一个对象代表一个真实的实体时,很容易选择一个合适的对象id。比方,对于一个人来说,有一个既定的标识符概念(SSN、学生id、护照号码等)。另一种选择是使用一个人的电子邮件地点作为标识符。
  • 但请注意,这些只是指导方针。可能有充分的来由将通常是值的东西酿成对象。比方,思量一个存储大量人员的数据库。此数据库中的很多person对象具有相同的名称和姓氏,将它们存储在每个对象中的开销可能会对性能产生负面影响。在这种情况下,我们可以将名字和姓氏分别设置为对象,并在person类中仅存储指向这些对象的指针。
  • 持久类的实例可以处于两种状态之一:瞬态和持久。瞬态实例在应用步伐的内存中只有一个表示,当应用步伐停止时,它将不复存在,除非它被明确地持久化。换句话说,持久类的瞬态实例的行为就像任何普通C++类的实例一样。持久实例在应用步伐的内存和数据库中都有表示。持久实例即使在应用步伐停止后也会保留,除非它被明确地从数据库中删除。
2.声明持久对象和值

   为了使C++类成为持久对象类,我们使用db对象pragma声明它,比方:
  1. #pragma db object
  2. class person
  3. {
  4.   ...
  5. };
复制代码


  • 我们经常使用的另一个语法是db id,它将一个数据成员指定为对象id,比方:
  1. #pragma db object
  2. class person
  3. {
  4.   ...
  5.   #pragma db id
  6.   unsigned long id_;
  7. };
复制代码


  • 对象id可以是简单或复合(“复合对象id”)值范例。此范例应为默承认构造、可复制可构造和可复制可分配。也可以声明一个没有对象id的持久类,但是,如许的类的功能有限(“no_id”)。
  • 上述两个语法是声明具有对象id的持久类所需的最低要求。其他语法可用于微调类及其成员的数据库相干属性(“ODB语法语言”)。
  • 通常,持久类应该界说默认构造函数。天生的数据库支持代码在从持久状态实例化对象时使用此构造函数。如果我们只为数据库支持代码添加默认构造函数,那么我们可以将其设置为私有,条件是我们还将<odb/core.hxx>标头中界说的odb::access类设置为该对象类的朋友。比方:
  1. #include <odb/core.hxx>
  2. #pragma db object
  3. class person
  4. {
  5.   ...
  6. private:
  7.   friend class odb::access;
  8.   person () {}
  9. };
复制代码


  • 也可以有一个没有默认构造函数的对象类。然而,在这种情况下,数据库操纵只能将持久状态加载到现有实例中(“加载持久对象”,“查询效果”)。
  • ODB编译器还需要访问持久类的非瞬态(“瞬态”)数据成员。如果这些数据成员是公共的,ODB编译器可以直接访问它们。如果它们是私有的或受保护的,并且odb::access类被声明为对象范例的朋友,它也可以如许做。比方:
  1. #include <odb/core.hxx>
  2. #pragma db object
  3. class person
  4. {
  5.   ...
  6. private:
  7.   friend class odb::access;
  8.   person () {}
  9.   #pragma db id
  10.   unsigned long id_;
  11.   std::string name_;
  12. };
复制代码


  • 如果数据成员不能直接访问,则ODB编译器将尝试自动找到合适的访问器和修饰符函数。为了实现这一点,ODB编译器将尝试查找从数据成员名称派生的通用访问器和修饰符名称。详细来说,对于上述示例中的name_data成员,ODB编译器将查找具有以下名称的访问器函数:get_name()、getName()、get name()和just name(),以及具有以下名称(set_name()、setName(),setName())和just-name())的修饰符函数。您还可以使用--accessor正则表达式和--modifier正则表达式ODB编译器选项添加对自界说名称派生的支持。有关这些选项的详细信息,请参阅《ODB编译器命令行手册》。以下示例说明白自动访问器和修饰符发现:
  1. #pragma db object
  2. class person
  3. {
  4. public:
  5.   person () {}
  6.   ...
  7.   unsigned long id () const;
  8.   void id (unsigned long);
  9.   const std::string& get_name () const;
  10.   std::string& set_name ();
  11. private:
  12.   #pragma db id
  13.   unsigned long id_; // Uses id() for access.
  14.   std::string name_; // Uses get_name()/set_name() for access.
  15. };
复制代码


  • 末了,如果数据成员不能直接访问,并且ODB编译器无法发现合适的访问器和修饰符函数,那么我们可以使用db-get和db-set语法提供自界说访问器和修改器表达式。
  • 持久类的数据成员也可以拆分为单独加载和/或单独更新的部门
  • 您可能想知道我们是否也必须将值范例声明为持久值。对于int或std::string等简单值范例,我们不需要做任何特别的事情,因为ODB编译器知道如何将它们映射到合适的数据库范例,以及如何在两者之间举行转换。另一方面,如果ODB编译器不知道一个简单的值,那么我们需要提供数据库范例的映射,并可能提供在两者之间转换的代码。
  • 与对象类雷同,复合值范例必须使用db-value pragma显式声明为持久值,比方:
  1. #pragma db value
  2. class name
  3. {
  4.   ...
  5.   std::string first_;
  6.   std::string last_;
  7. };
  8.   
复制代码


  • 请注意,复合值不能将数据成员指定为对象id,因为正如我们前面讨论的那样,值没有标识的概念。复合值范例也不肯界说默认构造函数,除非它被用作容器的元素。ODB编译器使用与对象范例相同的机制来访问复合值范例中的数据成员。
3.对象和视图指针



  • 正如我们在上一章中看到的,一些数据库操纵创建了动态分配的持久类实例,并返回指向这些实例的指针。固然在大多数情况下,您不需要处置惩罚指向视图的指针,但可以使用result_iterator::load()函数获取动态分配的视图实例(“查询效果”)。
  • 默认情况下,全部这些机制都使用原始指针来返回对象和视图,以及传递和缓存对象。对于具有简单对象生存期要求且不使用会话或对象关系的应用步伐,这通常就足够了。特别是,从数据库操纵中作为原始指针返回的动态分配对象或视图可以分配给我们选择的智能指针,比方C++11的std::auto_ptr、std::unique_ptr,或TR1、C++11或Boost的shared_ptr。
  • 但是,为了制止任何错误的可能性,比方忘记为返回的对象或视图使用智能指针,以及简化更高级的ODB功能的使用,如会话和双向对象关系,建议您使用具有共享语义的智能指针作为对象指针。TR1、C++11或Boost中的shared_ptr智能指针是一个很好的默认选择。但是,如果不需要共享并且不使用会话,那么也可以使用std::unique_ptr或std::auto_ptr。
  • ODB提供了几种更改对象或视图指针范例的机制。为了在每个对象或每个视图的基础上指定指针范例,我们可以使用db指针语法,比方:
  1. #pragma db object pointer(std::tr1::shared_ptr)
  2. class person
  3. {
  4.   ...
  5. };
复制代码


  • 我们还可以在定名空间级别为一组对象或视图指定默认指针:
  1. #pragma db namespace pointer(std::tr1::shared_ptr)
  2. namespace accounting
  3. {
  4.   #pragma db object
  5.   class employee
  6.   {
  7.     ...
  8.   };
  9.   #pragma db object
  10.   class employer
  11.   {
  12.     ...
  13.   };
  14. }
复制代码


  • 末了,我们可以使用--defaultpointer选项为整个文件指定默认指针。有关此选项参数的详细信息,请参阅《ODB编译器命令行手册》。典型用法如下:
  1. --default-pointer std::tr1::shared_ptr
复制代码


  • 另一种具有相同效果的方法是为全局定名空间指定默认指针:
  1. #pragma db namespace() pointer(std::tr1::shared_ptr)
复制代码


  • 请注意,我们始终可以使用db指针对象或视图pragma覆盖在定名空间级别或使用命令行选项指定的默认指针。比方:
  1. #pragma db object pointer(std::shared_ptr)
  2. namespace accounting
  3. {
  4.   #pragma db object
  5.   class employee
  6.   {
  7.     ...
  8.   };
  9.   #pragma db object pointer(std::unique_ptr)
  10.   class employer
  11.   {
  12.     ...
  13.   };
  14. }
复制代码


  • ODB运行库提供的内置支持答应我们使用shared_ptr(TR1或C++11)、std::unique_ptr(C++11)或std::auto_ptr作为指针范例。别的,ODB配置文件库可用于常用的框架和库(如Boost和Qt),为这些框架和库中的智能指针提供支持(第三部门,“配置文件”)。
4.数据库



  • 在应用步伐可以使用ODB提供的持久性服务之前,它必须创建一个数据库类实例。数据库实例是应用步伐存储其持久对象的位置的表示。我们通过实例化一个特定于数据库系统的类来创建数据库实例。比方,odb::mysql::database就是mysql数据库系统的一个类。我们通常还会将数据库名称作为参数传递给类的构造函数。以下代码片段显示了如作甚MySQL数据库系统创建数据库实例:
  1. #include <odb/database.hxx>
  2. #include <odb/mysql/database.hxx>
  3. auto_ptr<odb::database> db (
  4.   new odb::mysql::database (
  5.     "test_user"     // database login name
  6.     "test_password" // database password
  7.     "test_database" // database name
  8.     ));
复制代码


  • odb::database类是odb提供的全部数据库系统特定类的通用接口。您通常会通过此接口使用数据库实例,除非您的应用步伐依靠于特定的功能,并且该功能仅由特定系统的数据库类公开。您需要包含<odb/database.hxx>头文件,以便在您的应用步伐中使用此类。
  • odb::database接口界说了启动事务和操纵持久对象的函数。本章的别的部门以及下一章将详细讨论这些内容,下一章专门讨论在数据库中查询持久对象的主题。有关系统特定命据库类的详细信息,请参阅第二部门“数据库系统”。
  • 在持久化对象之前,必须在数据库中创建相应的数据库模式。该模式包含表界说和其他关系数据库工件,用于存储数据库中持久对象的状态。
  • 有几种方法可以创建数据库模式。最简单的方法是指示ODB编译器从持久类天生相应的模式(--generateschema选项)。ODB编译器可以将模式天生为独立的SQL文件,嵌入到天生的C++代码中,也可以天生为单独的C++源文件(--schema format选项)。如果我们使用SQL文件创建数据库模式,那么应该在应用步伐启动之前实行此文件,通常只实行一次。
  • 或者,如果模式直接嵌入到天生的代码中或作为单独的C++源文件天生,那么我们可以使用odb::schema_catalog类在应用步伐中的数据库中创建它,比方:
  1. #include <odb/schema-catalog.hxx>
  2. odb::transaction t (db->begin ());
  3. odb::schema_catalog::create_schema (*db);
  4. t.commit ();
复制代码


  • 有关odb::transaction的信息,请参阅下一节。上述代码片段的完备版本可以在odb-examples中的schema/embedded示例中找到。
  • odb::schema_catalog类具有以下接口。您需要包含<odb/schemacatalog.hxx>头文件,以便在您的应用步伐中使用此类。
  1. namespace odb
  2. {
  3.   class schema_catalog
  4.   {
  5.   public:
  6.     static void
  7.     create_schema (database&,
  8.                    const std::string& name = "",
  9.                    bool drop = true);
  10.     static void
  11.     drop_schema (database&, const std::string& name = "");
  12.     static bool
  13.     exists (database_id, const std::string& name = "");
  14.     static bool
  15.     exists (const database&, const std::string& name = "")
  16.   };
  17. }
复制代码


  • create_schema()函数的第一个参数是我们要在其中创建模式的数据库实例。第二个参数是模式名称。默认情况下,ODB编译器使用默认模式名称(空字符串)天生全部嵌入式模式。但是,如果您的应用步伐需要有几个单独的模式,您可以使用--schema name ODB编译器选项来分配自界说模式名称,然后将这些名称用作create_schema()的第二个参数。默认情况下,create_schema()还将删除全部数据库对象(表、索引等),如果它们在创建新对象之前存在的话。您可以通过将false作为第三个参数传递来更改此行为。drop_schema()函数答应您删除全部数据库对象,而无需创建新对象。
  • 如果找不到schema,create_schema()和drop_schema()函数将抛出odb::unknown_schema非常。您可以使用exists()函数检查目录中是否存在指定命据库和指定名称的架构。还要注意,应该在事务中调用create_schema()和drop_schema()函数。
  • ODB还为数据库模式演化提供支持。与模式创建雷同,模式迁徙语句可以作为独立的SQL文件天生,也可以嵌入到天生的C++代码中。
  • 末了,我们还可以在ODB中使用自界说数据库模式。这种方法可以与上述独立SQL文件雷同地工作,除了数据库模式是手写的或由另一个步伐天生的。或者,我们可以实行自界说SQL语句,直接从我们的应用步伐创建模式。为了将持久类映射到自界说数据库模式,ODB提供了广泛的映射自界说语法,如db表、db列和db范例(“ODB语法语言”)。有关如何对各种C++构造实行此类映射的示例代码,请参阅odb-examples中的schema/custom示例。
5.事务



  • 事务是原子、一致、隔离和持久(ACID)的工作单元。数据库操纵只能在事务中实行,应用步伐中的每个实行线程一次只能有一个运动事务。
  • 原子性是指在事务中对数据库状态举行更改时,要么应用全部更改,要么根本不应用任何更改。比方,思量一个在代表银行账户的两个对象之间转移资金的生意业务。如果第一个对象的借记功能乐成,但第二个对象的贷记功能失败,则事务将回滚,第一个对象上的数据库状态保持不变。
  • 一致性是指事务必须将存储在数据库中的全部对象从一个一致的状态转换为另一个状态。比方,如果银行账户对象必须引用个人对象作为其全部者,而我们在使对象持久化之前忘记设置此引用,则事务将被回滚,数据库将保持不变。
  • 隔离是指在事务期间对数据库状态所做的更改仅在事务内部可见,直到提交为止。使用上述银行转账示例,在光荣操纵乐成完成并提交生意业务之前,对第一个对象实行的借记操纵的效果对其他生意业务不可见。
  • 持久性是指一旦事务被提交,它对数据库状态所做的更改是永久性的,并且可以在应用步伐崩溃等故障中幸存下来。从现在开始,更改此状态的唯一方法是实行并提交另一个事务。
  • 事务通过调用database::begin()或connection::begin()函数来启动。返回的事务句柄存储在odb::transaction类的一个实例中。您需要包含<odb/transaction.hxx>头文件,以便在您的应用步伐中使用此类。比方:
  1. #include <odb/transaction.hxx>
  2. transaction t (db.begin ())
  3. // Perform database operations.
  4. t.commit ();
  5.   
  6. The odb::transaction class has the following interface:
  7. namespace odb
  8. {
  9.   class transaction
  10.   {
  11.   public:
  12.     typedef odb::database database_type;
  13.     typedef odb::connection connection_type;
  14.     explicit
  15.     transaction (transaction_impl*, bool make_current = true);
  16.     transaction ();
  17.     void
  18.     reset (transaction_impl*, bool make_current = true);
  19.     void
  20.     commit ();
  21.     void
  22.     rollback ();
  23.     database_type&
  24.     database ();
  25.     connection_type&
  26.     connection ();
  27.     bool
  28.     finilized () const;
  29.   public:
  30.     static bool
  31.     has_current ();
  32.     static transaction&
  33.     current ();
  34.     static void
  35.     current (transaction&);
  36.     static bool
  37.     reset_current ();
  38.     // Callback API.
  39.     //
  40.   public:
  41.     ...
  42.   };
  43. }
复制代码


  • commit()函数提交一个事务,rollback()将其回滚。除非事务已经完成,即显式提交或回滚,否则当事务实例超出范围时,事务类的析构函数将自动回滚它。如果我们尝试提交或回滚已完成的事务,则会抛出odb::transaction_already_finalized非常。
  • database()访问器返回此事务正在处置惩罚的数据库。同样,connection()访问步伐返回此事务所在的数据库连接(第6节,“连接”)。
  • 静态current()访问器返回此线程的当前运动事务。如果没有运动事务,此函数将抛出odb::not_in_transaction非常。我们可以使用has_current()静态函数检查此线程中是否有事务见效。
  • transaction构造函数中的make_current参数以及静态current()修饰符和reset_current()函数为我们提供了对当前运动事务提名的额外控制。如果我们传递false作为make_current参数,那么新创建的事务将不会自动成为此线程的运动事务。稍后,我们可以使用static current()修饰符将此事务设置为运动事务。reset_current()静态函数打扫当前运动的事务。这些机制共同答应更高级的用例,比方在同一线程上复用两个或多个事务。比方:
  1. transaction t1 (db1.begin ());        // Active transaction.
  2. transaction t2 (db2.begin (), false); // Not active.
  3. // Perform database operations on db1.
  4. transaction::current (t2);            // Deactivate t1, activate t2.
  5. // Perform database operations on db2.
  6. transaction::current (t1);            // Switch back to t1.
  7. // Perform some more database operations on db1.
  8. t1.commit ();
  9. transaction::current (t2);            // Switch to t2.
  10. // Perform some more database operations on db2.
  11. t2.commit ();
复制代码


  • reset()修饰符答应我们重用同一事务实例来完成多个数据库事务。与析构函数雷同,如果当前事务尚未完成,reset()将回滚当前事务。默认事务构造函数创建一个已完成的事务,稍后可以使用reset()对其举行初始化。finished()访问器可用于检查事务是否已完成。以下是我们如何使用此功能提交当前事务,并在每次实行肯定命目标数据库操纵时启动新事务:
  1. transaction t (db.begin ());
  2. for (size_t i (0); i < n; ++i)
  3. {
  4.   // Perform a database operation, such as persist an object.
  5.   // Commit the current transaction and start a new one after
  6.   // every 100 operations.
  7.   //
  8.   if (i % 100 == 0)
  9.   {
  10.     t.commit ();
  11.     t.reset (db.begin ());
  12.   }
  13. }
  14. t.commit ();
复制代码


  • 请注意,在上面关于原子性、一致性、隔离性和持久性的讨论中,全部这些保证仅适用于数据库中对象的状态,而不是应用步伐内存中对象的状态。可以回滚一个事务,但应用步伐内存中仍有该事务的更改。制止这种潜在不一致性的一个简单方法是仅在事务范围内实例化持久对象。比方,思量同一事务的这两个实现:
  1. void
  2. update_age (database& db, person& p)
  3. {
  4.   transaction t (db.begin ());
  5.   p.age (p.age () + 1);
  6.   db.update (p);
  7.   t.commit ();
  8. }
复制代码


  • 在上述实现中,如果update()调用失败并且事务回滚,则数据库中person对象的状态和应用步伐内存中同一对象的状态将不同。现在思量一个替换实现,它只在事务期间实例化person对象:
  1. void
  2. update_age (database& db, unsigned long id)
  3. {
  4.   transaction t (db.begin ());
  5.   auto_ptr<person> p (db.load<person> (id));
  6.   p.age (p.age () + 1);
  7.   db.update (p);
  8.   t.commit ();
  9. }
复制代码


  • 固然,可能并不总是可以或许以这种风格编写应用步伐。我们经常需要访问和修改事务中持久对象的应用步伐状态。在这种情况下,如果事务被回滚并且数据库状态保持不变,那么尝试回滚对应用步伐状态所做的更改可能是有意义的。一种方法是从数据库中重新加载对象的状态,比方:
  1. void
  2. update_age (database& db, person& p)
  3. {
  4.   try
  5.   {
  6.     transaction t (db.begin ());
  7.     p.age (p.age () + 1);
  8.     db.update (p);
  9.     t.commit ();
  10.   }
  11.   catch (...)
  12.   {
  13.     transaction t (db.begin ());
  14.     db.load (p.id (), p);
  15.     t.commit ();
  16.     throw;
  17.   }
  18. }
复制代码
6.连接

   odb::connection类表示与数据库的连接。通常,您不会直接使用连接,而是让ODB运行时根据需要获取和释放连接。但是,某些用例可能需要手动获取连接。为了完备起见,本节描述了连接类并讨论了它的一些用例。如果你是第一次阅读本手册,你可能想跳过这一节。
  

  • 与odb::database雷同,odb::connection类是odb提供的全部数据库系统特定类的通用接口。有关系统特定连接类的详细信息,请参阅第二部门“数据库系统”。
  • 要使odb::connection类在您的应用步伐中可用,您需要包含<odb/connection.hxx>头文件。odb::connection类具有以下接口:
  1. namespace odb
  2. {
  3.   class connection
  4.   {
  5.   public:
  6.     typedef odb::database database_type;
  7.     transaction
  8.     begin () = 0;
  9.     unsigned long long
  10.     execute (const char* statement);
  11.     unsigned long long
  12.     execute (const std::string& statement);
  13.     unsigned long long
  14.     execute (const char* statement, std::size_t length);
  15.     database_type&
  16.     database ();
  17.   };
  18.   typedef details::shared_ptr<connection> connection_ptr;
  19. }
复制代码


  • begin()函数用于在连接上启动事务。execute()函数答应我们在连接上实行本机数据库语句。它们的语义等同于database::execute()函数(第12节,“实行本机SQL语句”),除了它们可以在事务外部合法调用。末了,database()访问器返回对此连接对应的odb::database实例的引用。
为了获得连接,我们调用database::connection() 函数。连接以odb::connection_ptr的情势返回,这是一个具有共享指针语义的特定于实现的智能指针。这尤其意味着可以从函数中复制并返回连接指针。一旦指向同一连接的connection_ptr的末了一个实例被烧毁,该连接将返回给数据库实例。以下代码片段展示了如何获取、使用和释放连接:
  1. using namespace odb::core;
  2. database& db = ...
  3. connection_ptr c (db.connection ());
  4. // Temporarily disable foreign key constraints.
  5. //
  6. c->execute ("SET FOREIGN_KEY_CHECKS = 0");
  7. // Start a transaction on this connection.
  8. //
  9. transaction t (c->begin ());
  10. ...
  11. t.commit ();
  12. // Restore foreign key constraints.
  13. //
  14. c->execute ("SET FOREIGN_KEY_CHECKS = 1");
  15. // When 'c' goes out of scope, the connection is returned to 'db'.
复制代码


  • 可能需要直接操纵连接的一些用例包括事务外语句实行,比方连接配置语句的实行、每个线程连接策略的实现,以及确保在同一连接上实行一组事务。
7.错误处置惩罚和规复

   ODB使用C++非常来报告数据库操纵错误。大多数ODB非常表示硬错误或没有应用步伐干预就无法纠正的错误。比方,如果我们尝试加载一个对象id未知的对象,则会抛出odb:bject_not_persistent非常。我们的应用步伐可能可以或许纠正此错误,比方,通过获取有效的对象id并重试。本章别的部门将先容每个数据库函数可能引发的硬错误和相应的ODB非常,第14节“ODB非常”为全部ODB非常提供了快速参考。
  

  • 第二组ODB非常表示软错误或可规复错误。此类错误是临时的故障,通常可以通过简单地重新实行事务来纠正。ODB界说了三个如许的非常:ODB::connection_lost、ODB::timeout和odb::deadlock。全部可规复的ODB非常都源自通用的ODB::recovery base非常,该非常可用于通过单个catch块处置惩罚全部可规复情况。
  • 如果在事务处置惩罚过程中丢失了与数据库的连接,则抛出odb::connection_list非常。在这种情况下,事务被中止,但可以在不做任何更改的情况下重新尝试。同样,如果其中一个数据库操纵或整个事务超时,则抛出odb::timeout非常。同样,在这种情况下,事务被中止,但可以按原样重新尝试。
  • 如果两个或多个事务访问或修改多个对象,并且由不同的应用步伐或同一应用步伐中的不同线程并发实行,那么这些事务可能会试图以不兼容的次序访问对象并导致死锁。死锁的典型示例是两个事务,其中第一个事务修改了object1,并等待第二个事务将其更改提交给object2,以便它也可以更新object2。同时,第二个事务已经修改了object2,并且正在等待第一个事务将其更改提交给object1,因为它还需要修改object1。因此,这两项生意业务都无法完成。
  • 数据库系统检测到这种情况,并在其中一个死锁事务中自动中止等待操纵。在ODB中,这转换为从其中一个数据库函数抛出odb::deadlock可规复非常。
  • 以下代码片段显示了如何通过重新启动受影响的事务来处置惩罚可规复的非常:
  1. const unsigned short max_retries = 5;
  2. for (unsigned short retry_count (0); ; retry_count++)
  3. {
  4.   try
  5.   {
  6.     transaction t (db.begin ());
  7.     ...
  8.     t.commit ();
  9.     break;
  10.   }
  11.   catch (const odb::recoverable& e)
  12.   {
  13.     if (retry_count > max_retries)
  14.       throw retry_limit_exceeded (e.what ());
  15.     else
  16.       continue;
  17.   }
  18. }
复制代码
8.使对象持久化



  • 新创建的持久类实例是临时的。我们使用database::persist() 函数模板使瞬态实例持久化。此函数有四个重载版本,具有以下署名:
  1.   template <typename T>
  2.   typename object_traits<T>::id_type
  3.   persist (const T& object);
  4.   template <typename T>
  5.   typename object_traits<T>::id_type
  6.   persist (const object_traits<T>::const_pointer_type& object);
  7.   template <typename T>
  8.   typename object_traits<T>::id_type
  9.   persist (T& object);
  10.   template <typename T>
  11.   typename object_traits<T>::id_type
  12.   persist (const object_traits<T>::pointer_type& object);
复制代码


  • 在这里和本手册的别的部门中,object_traits<T>::pointer_type和object_traits<T>::const_pointer_type分别表示不受限定的和恒定的对象指针范例(第3节,“对象和视图指针”)。同样,object_traits<T>::id_type表示对象id范例。odb:bject_traits模板是odb编译器天生的数据库支持代码的一部门。
  • 第一个persist()函数需要一个对被持久化实例的常量引用。第二个函数需要一个常量对象指针。这两个函数只能用于具有应用步伐分配的对象ID的对象。
  • 第三个和第四个persist()函数与前两个雷同,除了它们对不受限定的引用和对象指针举行操纵。如果数据库分配了要持久化的对象的标识符,则这些函数会用分配的值更新传递实例的id成员。全部四个函数都返回新持久化对象的对象id。
  • 如果数据库中已经包含具有此标识符的此范例的对象,则persist()函数将抛出odb:bject_already_persistent非常。只要持久的对象数目不超过id范例的值空间,数据库分配的对象id就永远不会发生这种情况。
  • 在调用persist()函数时,我们不需要显式指定模板范例,因为它将从传递的参数中自动推断出来。以下示例显示了如何调用这些函数:
  1. person john ("John", "Doe", 33);
  2. shared_ptr<person> jane (new person ("Jane", "Doe", 32));
  3. transaction t (db.begin ());
  4. db.persist (john);
  5. unsigned long jane_id (db.persist (jane));
  6. t.commit ();
  7. cerr << "Jane's id: " << jane_id << endl;
复制代码


  • 请注意,在上述代码片段中,我们创建了计划在启动事务之前使其持久化的实例。同样,我们在提交生意业务后打印了Jane的id。一般来说,您应该制止在事务范围内实行可以在事务开始之前或停止之后实行的操纵。运动事务既斲丧应用步伐的资源,如数据库连接,也斲丧数据库服务器的资源,比方对象锁。通过遵照上述规则,您可以确保这些资源被释放,并尽快提供给应用步伐中的其他线程和其他应用步伐。
  • 一些数据库系统支持通过实行单个底层语句来持久化多个对象,这可以明显进步性能。对于如许的数据库系统,ODB提供了批量persist() 函数。
9.加载持久对象



  • 一旦一个对象被持久化,并且你知道它的对象id,应用步伐就可以使用database::load()函数模板加载它。此函数有两个重载版本,具有以下署名:
  1.   template <typename T>
  2.   typename object_traits<T>::pointer_type
  3.   load (const typename object_traits<T>::id_type& id);
  4.   template <typename T>
  5.   void
  6.   load (const typename object_traits<T>::id_type& id, T& object);
复制代码


  • 给定一个对象id,第一个函数在动态内存中分配一个对象类的新实例,从数据库加载其状态,并返回指向新实例的指针。第二个函数将对象的状态加载到现有实例中。如果数据库中没有具有此id的此范例的对象,则这两个函数都会抛出odb:bject_not_persistent。
当我们调用第一个load()函数时,我们需要显式指定对象范例。我们不需要对第二个函数如许做,因为对象范例将从第二个参数自动推断出来,比方:
  1. transaction t (db.begin ());
  2. auto_ptr<person> jane (db.load<person> (jane_id));
  3. db.load (jane_id, *jane);
  4. t.commit ();
复制代码


  • 在某些情况下,可能需要从数据库中重新加载对象的状态。固然使用第二个load()函数很容易实现,但ODB提供了具有很多特别属性的database::reload() 函数模板。此函数有两个重载版本,具有以下署名:
  1.   template <typename T>
  2.   void
  3.   reload (T& object);
  4.   template <typename T>
  5.   void
  6.   reload (const object_traits<T>::pointer_type& object);
复制代码


  • 第一个reload()函数需要对象引用,而第二个函数需要对象指针。这两个函数都希望传递的对象中的id成员包含有效的对象标识符,并且与load()雷同,如果数据库中没有具有此id的此范例的对象,这两个功能都将抛出odb:bject_not_persistent。
与load()函数相比,reload()的第一个特别属性是它不与会话的对象缓存交互(“对象缓存”)。也就是说,如果重新加载的对象已经在缓存中,那么在reload()返回后,它将留在那里。同样,如果对象不在缓存中,则reload()也不会将其放在那里。


  • reload()函数的第二个特别属性仅在对具有乐观并发模型的对象举行操纵时才显示出来。在这种情况下,如果应用步伐内存和数据库中对象的状态相同,则不会发生重新加载。
  • 如果我们不确定具有给定id的对象是否持久,我们可以使用find()函数而不是load(),比方:
  1.   template <typename T>
  2.   typename object_traits<T>::pointer_type
  3.   find (const typename object_traits<T>::id_type& id);
  4.   template <typename T>
  5.   bool
  6.   find (const typename object_traits<T>::id_type& id, T& object);
复制代码


  • 如果在数据库中找不到具有此id的对象,则第一个find()函数将返回NULL指针,而第二个函数将传递的实例保持不变并返回false。
  • 如果我们不知道对象id,那么我们可以使用查询来查找符合某些条件的对象(或多个对象)(“查询数据库”)。但是请注意,使用对象的标识符加载对象的状态可能比实行查询快得多。
10.更新持久对象



  • 如果持久对象已被修改,我们可以使用database::update()函数模板将更新后的状态存储在数据库中。此函数有三个重载版本,具有以下署名:
  1.   template <typename T>
  2.   void
  3.   update (const T& object);
  4.   template <typename T>
  5.   void
  6.   update (const object_traits<T>::const_pointer_type& object);
  7.   template <typename T>
  8.   void
  9.   update (const object_traits<T>::pointer_type& object);
复制代码


  • 第一个update()函数需要一个对象引用,而别的两个函数需要对象指针。如果传递给这些函数之一的对象在数据库中不存在,update()会抛出odb:bject_not_persistent非常(但请参阅下面关于乐观并发性的说明)。
下面是我们在前面的生意业务部门中讨论的资金转账示例。它使用假设的bank_account持久类:
  1. void
  2. transfer (database& db,
  3.           unsigned long from_acc,
  4.           unsigned long to_acc,
  5.           unsigned int amount)
  6. {
  7.   bank_account from, to;
  8.   transaction t (db.begin ());
  9.   db.load (from_acc, from);
  10.   if (from.balance () < amount)
  11.     throw insufficient_funds ();
  12.   db.load (to_acc, to);
  13.   to.balance (to.balance () + amount);
  14.   from.balance (from.balance () - amount);
  15.   db.update (to);
  16.   db.update (from);
  17.   t.commit ();
  18. }
复制代码


  • 使用动态分配的对象和带有对象指针参数的update()函数也可以实现同样的功能,比方:
  1. transaction t (db.begin ());
  2. shared_ptr<bank_account> from (db.load<bank_account> (from_acc));
  3. if (from->balance () < amount)
  4.   throw insufficient_funds ();
  5. shared_ptr<bank_account> to (db.load<bank_account> (to_acc));
  6. to->balance (to->balance () + amount);
  7. from->balance (from->balance () - amount);
  8. db.update (to);
  9. db.update (from);
  10. t.commit ();
复制代码


  • 如果任何update()函数都在乐观并发模型的持久类上运行,那么如果数据库中对象的状态自上次加载到应用步伐内存以来发生了变革,它们将抛出odb:bject_changed非常。别的,对于此类类,如果数据库中没有此类对象,update()将不再抛出object_not_persistent非常。相反,此条件被视为对象状态的更改,并抛出object_chang。
  • 在ODB中,持久类、复合值范例以及单个数据成员都可以声明为只读(“只读(对象)”、“只读的(复合值)”和“只读”(数据成员))。
  • 如果一个单独的数据成员被声明为只读,那么在使用上述任何update()函数更新对象的数据库状态时,对该成员的任何更改都将被忽略。const数据成员会自动被视为只读。如果复合值被声明为只读,则其全部数据成员都被视为只读。
  • 如果整个对象被声明为只读,则无法更改此对象的数据库状态。对如许的对象调用上述任何update()函数都会导致编译时错误。
  • 与persist()雷同,对于支持此功能的数据库系统,ODB提供批量update() 函数。
11.删除持久对象



  • 要从数据库中删除持久对象的状态,我们使用database::erase() 或database::erase_query() 函数模板。如果应用步伐仍然有已擦除对象的实例,则此实例将变为瞬态。erase()函数有以下重载版本:
  1.   template <typename T>
  2.   void
  3.   erase (const T& object);
  4.   template <typename T>
  5.   void
  6.   erase (const object_traits<T>::const_pointer_type& object);
  7.   template <typename T>
  8.   void
  9.   erase (const object_traits<T>::pointer_type& object);
  10.   template <typename T>
  11.   void
  12.   erase (const typename object_traits<T>::id_type& id);
复制代码


  • 第一个erase()函数使用对象引用情势的对象自己从数据库中删除其状态。接下来的两个函数实现了相同的效果,但使用了对象指针。请注意,这三个函数都不会改变传递的对象。它只是变得短暂。末了一个函数使用对象id来标识要删除的对象。如果数据库中不存在该对象,则全部四个函数都会抛出odb:bject_not_persistent非常(但请参阅下面关于乐观并发的解释)。
  • 在调用末了一个erase()函数时,我们必须指定对象范例。对于前三个函数来说,这是不必要的,因为对象范例将从它们的参数中自动推断出来。以下示例显示了如何调用这些函数:
  1. person& john = ...
  2. shared_ptr<jane> jane = ...
  3. unsigned long joe_id = ...
  4. transaction t (db.begin ());
  5. db.erase (john);
  6. db.erase (jane);
  7. db.erase<person> (joe_id);
  8. t.commit ();
复制代码


  • 如果除末了一个函数外的任何erase()函数都在乐观并发模型的持久类上运行,那么如果数据库中的对象状态自上次加载到应用步伐内存以来发生了变革,它们将抛出odb:bject_changed非常。别的,对于如许的类,如果数据库中没有如许的对象,erase()将不再抛出object_not_persistent非常。相反,此条件被视为对象状态的更改,并抛出object_chang。
  • 与persist()和update()雷同,对于支持此功能的数据库系统,ODB提供批量erase()函数。
erase_query()函数答应我们删除符合特定条件的多个对象的状态。它使用查询表达式database::query() (“查询数据库”),并且由于ODB查询功能是可选的,因此只有指定了--generate-query ODB编译器选项,它才可用。erase_query()函数有以下重载版本:
  1.   template <typename T>
  2.   unsigned long long
  3.   erase_query ();
  4.   template <typename T>
  5.   unsigned long long
  6.   erase_query (const odb::query<T>&);
复制代码


  • 第一个erase_query()函数用于删除数据库中存储的给定范例的全部持久对象的状态。第二个函数使用传递的查询实例仅删除符合查询条件的对象的状态。这两个函数都返回已删除的对象数目。调用erase_query()函数时,我们必须显式指定要擦除的对象范例。比方:
  1. typedef odb::query<person> query;
  2. transaction t (db.begin ());
  3. db.erase_query<person> (query::last == "Doe" && query::age < 30);
  4. t.commit ();
复制代码


  • 与query()函数不同,在调用erase_query()时,我们不能使用查询表达式中指向对象的成员。然而,我们仍然可以将指针对应的成员用作具有所指向对象的id范例的普通对象成员(“关系”)。这答应我们比较对象ID以及测试指针是否为NULL。比方,以下事务确保引用即将删除的雇主对象的全部员工对象也被删除。这里我们假设employee类包含一个指向employer类的指针。
  1. typedef odb::query<employee> query;
  2. transaction t (db.begin ());
  3. employer& e = ... // Employer object to be deleted.
  4. db.erase_query<employee> (query::employer == e.id ());
  5. db.erase (e);
  6. t.commit ();
复制代码
12.实行本机SQL语句



  • 在某些情况下,我们可能需要实行本机SQL语句,而不是使用上面描述的面向对象数据库API。比方,我们可能希望调解ODB编译器天生的数据库模式,或者利用我们使用的数据库系统特有的功能。database::execute() 函数有三个重载版本,提供以下功能:
  1.   unsigned long long
  2.   execute (const char* statement);
  3.   unsigned long long
  4.   execute (const std::string& statement);
  5.   unsigned long long
  6.   execute (const char* statement, std::size_t length)
复制代码


  • 第一个execute()函数预期SQL语句为以零末端的C字符串。末了一个版本预期显式语句长度为第二个参数,语句自己可能包含“\0”字符,比方,如果数据库系统支持的话,表示二进制数据。全部三个函数都返回受语句影响的行数。比方:
  1. transaction t (db.begin ());
  2. db.execute ("DROP TABLE test");
  3. db.execute ("CREATE TABLE test (n INT PRIMARY KEY)");
  4. t.commit ();
复制代码


  • 固然这些函数必须始终在事务内调用,但可能需要在事务外实行本机语句。这可以使用第6节“连接”中描述的connection::execute()函数来完成。
13.跟踪SQL语句实行



  • 通常,了解高级数据库操纵实行了哪些SQL语句是有效的。比方,我们可以使用这些信息来找出为什么某些生意业务没有产生预期的效果,或者为什么它们需要比预期更长的时间。
  • 固然这些信息通常可以从数据库日志中获得,但ODB提供了一种更方便、更细粒度的应用步伐端SQL语句跟踪支持。比方,在需要跟踪的典型情况下,我们希望看到SQL语句作为特定事务的效果实行。固然从数据库日志中提取如许的语句子集可能很困难,但使用ODB跟踪支持很容易实现:
  1. transaction t (db.begin ());
  2. t.tracer (stderr_tracer);
  3. ...
  4. t.commit ();
复制代码


  • ODB答应我们在数据库、连接和事务级别指定跟踪器。如果为数据库指定,则将跟踪在此数据库上实行的全部语句。另一方面,如果为连接指定了跟踪器,则只会跟踪在此连接上实行的SQL语句。同样,为事务指定的跟踪器将仅显示作为此事务的一部门实行的语句。全部三个类(odb::database, odb::connection, and odb::transaction)都提供相同的跟踪API:
  1.   void
  2.   tracer (odb::tracer&);
  3.   void
  4.   tracer (odb::tracer*);
  5.   odb::tracer*
  6.   tracer () const;
复制代码


  • 前两个tracer()函数答应我们设置跟踪器对象,第二个函数答应我们通过传递NULL指针来打扫当前跟踪器。末了一个tracer()函数答应我们获取当前的tracer对象。如果没有有效的跟踪步伐,它将返回一个NULL指针。请注意,跟踪API不管理跟踪步伐对象的生存期。示踪剂应在使用期间有效。别的,跟踪API不是线程安全的。试图同时从多个线程设置跟踪器将导致未界说的行为。
  • odb::tracer类界说了一个回调接口,可用于创建自界说跟踪器实现。odb::stderr_tracer和odb::stderror_full_tracer是odb运行时提供的内置跟踪器实现。它们都将正在实行的SQL语句打印到尺度错误流中。完备的跟踪器除了跟踪语句实行外,还跟踪它们的准备和释放。完备跟踪器特别有效的一种情况是,语句(比方自界说查询)包含语法错误。在这种情况下,错误将在准备过程中被检测到,因此,语句将永远不会被实行。查看此类语句的唯一方法是使用完备跟踪。
  • odb::tracer类在<odb/tracer.hxx>头文件中界说,为了使该类在您的应用步伐中可用,您需要将其包含在内。odb::tracer接口提供了以下回调函数:
  1. namespace odb
  2. {
  3.   class tracer
  4.   {
  5.   public:
  6.     virtual void
  7.     prepare (connection&, const statement&);
  8.     virtual void
  9.     execute (connection&, const statement&);
  10.     virtual void
  11.     execute (connection&, const char* statement) = 0;
  12.     virtual void
  13.     deallocate (connection&, const statement&);
  14.   };
  15. }
复制代码


  • prepare()和releaseback()函数分别在创建和烧毁prepare语句时被调用。第一个execute()函数在实行预处置惩罚语句时调用,而第二个函数在实行普通语句时调用。prepare()和releaseplace()函数的默认实现什么也不做,而第一个execute()函数调用第二个函数,将语句文本作为第二个参数传递。因此,如果你只关心正在实行的SQL语句,那么你只需要重写第二个execute()函数。
  • 除了常见的odb::tracer接口外,每个数据库运行时还提供一个特定于数据库的版本,如odb::<database>::tracer。它的接口与通用版本完全相同,除了连接和语句范例是特定于数据库的,这使我们可以访问额外的特定于数据库信息。
    比方,思量一个更复杂的、特定于PostgreSQL的跟踪器实现。在这里,我们依靠于如许一个毕竟,即PostgreSQL ODB运行时使用名称来标识准备好的语句,并且可以从odb::pgsql::statement对象中获得此信息:
  1. #include <odb/pgsql/tracer.hxx>
  2. #include <odb/pgsql/database.hxx>
  3. #include <odb/pgsql/connection.hxx>
  4. #include <odb/pgsql/statement.hxx>
  5. class pgsql_tracer: public odb::pgsql::tracer
  6. {
  7.   virtual void
  8.   prepare (odb::pgsql::connection& c, const odb::pgsql::statement& s)
  9.   {
  10.     cerr << c.database ().db () << ": PREPARE " << s.name ()
  11.          << " AS " << s.text () << endl;
  12.   }
  13.   virtual void
  14.   execute (odb::pgsql::connection& c, const odb::pgsql::statement& s)
  15.   {
  16.     cerr << c.database ().db () << ": EXECUTE " << s.name () << endl;
  17.   }
  18.   virtual void
  19.   execute (odb::pgsql::connection& c, const char* statement)
  20.   {
  21.     cerr << c.database ().db () << ": " << statement << endl;
  22.   }
  23.   virtual void
  24.   deallocate (odb::pgsql::connection& c, const odb::pgsql::statement& s)
  25.   {
  26.     cerr << c.database ().db () << ": DEALLOCATE " << s.name () << endl;
  27.   }
  28. };
复制代码


  • 还请注意,您只能使用数据库特定的数据库实例设置数据库特定的跟踪对象,比方:
  1. pgsql_tracer tracer;
  2. odb::database& db = ...;
  3. db.tracer (tracer); // Compile error.
  4. odb::pgsql::database& db = ...;
  5. db.tracer (tracer); // Ok.
复制代码
14.ODB非常情况



  • 在前面的部门中,我们已经提到了数据库函数可能引发的一些非常。在本节中,我们将讨论ODB非常层次布局,并记载公共ODB运行时可能引发的全部非常。
  • ODB非常层次布局的根是抽象的ODB::exception类。此类派生自std::exception,并具有以下接口:
  1. namespace odb
  2. {
  3.   struct exception: std::exception
  4.   {
  5.     virtual const char*
  6.     what () const throw () = 0;
  7.   };
  8. }
复制代码


  • 捕获此非常保证我们将捕获ODB抛出的全部非常。what()函数返回触发非常的条件的可读描述。
  • ODB可能抛出的详细非常如下表所示:
  1. namespace odb
  2. {
  3.   struct null_pointer: exception
  4.   {
  5.     virtual const char*
  6.     what () const throw ();
  7.   };
  8.   // Transaction exceptions.
  9.   //
  10.   struct already_in_transaction: exception
  11.   {
  12.     virtual const char*
  13.     what () const throw ();
  14.   };
  15.   struct not_in_transaction: exception
  16.   {
  17.     virtual const char*
  18.     what () const throw ();
  19.   };
  20.   struct transaction_already_finalized: exception
  21.   {
  22.     virtual const char*
  23.     what () const throw ();
  24.   };
  25.   // Session exceptions.
  26.   //
  27.   struct already_in_session: exception
  28.   {
  29.     virtual const char*
  30.     what () const throw ();
  31.   };
  32.   struct not_in_session: exception
  33.   {
  34.     virtual const char*
  35.     what () const throw ();
  36.   };
  37.   struct session_required: exception
  38.   {
  39.     virtual const char*
  40.     what () const throw ();
  41.   };
  42.   // Database operations exceptions.
  43.   //
  44.   struct recoverable: exception
  45.   {
  46.   };
  47.   struct connection_lost: recoverable
  48.   {
  49.     virtual const char*
  50.     what () const throw ();
  51.   };
  52.   struct timeout: recoverable
  53.   {
  54.     virtual const char*
  55.     what () const throw ();
  56.   };
  57.   struct deadlock: recoverable
  58.   {
  59.     virtual const char*
  60.     what () const throw ();
  61.   };
  62.   struct object_not_persistent: exception
  63.   {
  64.     virtual const char*
  65.     what () const throw ();
  66.   };
  67.   struct object_already_persistent: exception
  68.   {
  69.     virtual const char*
  70.     what () const throw ();
  71.   };
  72.   struct object_changed: exception
  73.   {
  74.     virtual const char*
  75.     what () const throw ();
  76.   };
  77.   struct result_not_cached: exception
  78.   {
  79.     virtual const char*
  80.     what () const throw ();
  81.   };
  82.   struct database_exception: exception
  83.   {
  84.   };
  85.   // Polymorphism support exceptions.
  86.   //
  87.   struct abstract_class: exception
  88.   {
  89.     virtual const char*
  90.     what () const throw ();
  91.   };
  92.   struct no_type_info: exception
  93.   {
  94.     virtual const char*
  95.     what () const throw ();
  96.   };
  97.   // Prepared query support exceptions.
  98.   //
  99.   struct prepared_already_cached: exception
  100.   {
  101.     const char*
  102.     name () const;
  103.     virtual const char*
  104.     what () const throw ();
  105.   };
  106.   struct prepared_type_mismatch: exception
  107.   {
  108.     const char*
  109.     name () const;
  110.     virtual const char*
  111.     what () const throw ();
  112.   };
  113.   // Schema catalog exceptions.
  114.   //
  115.   struct unknown_schema: exception
  116.   {
  117.     const std::string&
  118.     name () const;
  119.     virtual const char*
  120.     what () const throw ();
  121.   };
  122.   struct unknown_schema_version: exception
  123.   {
  124.     schema_version
  125.     version () const;
  126.     virtual const char*
  127.     what () const throw ();
  128.   };
  129.   // Section exceptions.
  130.   //
  131.   struct section_not_loaded: exception
  132.   {
  133.     virtual const char*
  134.     what () const throw ();
  135.   };
  136.   struct section_not_in_object: exception
  137.   {
  138.     virtual const char*
  139.     what () const throw ();
  140.   };
  141.   // Bulk operation exceptions.
  142.   //
  143.   struct multiple_exceptions: exception
  144.   {
  145.     ...
  146.     virtual const char*
  147.     what () const throw ();
  148.   };
  149. }
复制代码


  • 当指向用db not_null或db value_not_null pragma声明为非null的持久对象的指针具有null值时,会抛出null_pointer非常。
  • 接下来的三个非常(already_in_transaction、not_in_transaction和transaction_already_finalized)由odb::transaction抛出,并在第5节“事务”中举行了讨论。
  • 接下来的两个非常(already_in_session和not_in_session)由odb::session类抛出。
  • 当ODB检测到正确加载双向对象关系需要会话但会话未被使用时,会抛出session_required非常。
  • 可规复非常是全部可规复非常的共同基础,这些非常是:connection_lost、timeout和deadlock。当与数据库的连接丢失时,会抛出connection_lost非常。同样,如果其中一个数据库操纵或整个事务超时,则会抛出超时非常。当数据库系统检测到事务死锁时,会抛出死锁非常。任何数据库函数都可以抛出这些非常。详见第7节“错误处置惩罚和规复”。
  • object_already_persistent非常是由persist()数据库函数抛出的。有关详细信息,请参阅第8节“使对象持久化”。
  • object_not_persistent非常是由load()、update()和erase()数据库函数抛出的。有关更多信息,请参阅第9节“加载持久对象”、第10节“更新持久对象”和第11节“删除持久对象”。
  • 当对具有乐观并发模型的对象举行操纵时,update()数据库函数和某些erase()数据库功能会抛出object_changed非常。
  • 查询效果类引发result_not_cached非常。
  • database_exception非常是数据库系统特定运行时库抛出的全部数据库系统特定非常的基类。
  • 当我们试图持久化、更新、加载或删除多态抽象类的实例时,数据库函数会抛出abstract_class非常。有关抽象类的更多信息,
  • 当我们试图持久化、更新、加载或擦除应用步伐中不存在范例信息的多态类的实例时,数据库函数会抛出no_type_info非常。这通常意味着为此类天生的数据库支持代码尚未链接(或动态加载)到应用步伐中,或者辨别器值尚未映射到持久类。
  • 如果已经缓存了具有指定名称的准备好的查询,则cache_query()函数会抛出prepared_already_cached非常。如果指定的准备好的查询对象范例或参数范例与缓存中的范例不匹配,则lookup_query()函数会抛出prepared_type_mismatch非常。
  • 如果找不到具有指定名称的模式,odb::schema_catalog类将抛出未知模式非常。详见第4节“数据库”。如果传递的版本未知,则处置惩罚数据库模式演变的schema_catalog函数会抛出unknow_schema_version非常。
  • 如果我们试图更新尚未加载的对象节,则会抛出section_not_looaded非常。如果加载或更新的节实例不属于相应的对象,则抛出section_not_in_object非常。
  • multiple_exceptions非常由批量API函数引发。
  • odb::exception类在<odb/exception.hxx>头文件中界说。全部详细的ODB非常都在<ODB/exceptions.hxx>中界说,其中还包括<ODB/exception.hxx>。通常,您不需要包含这两个标头中的任何一个,因为它们是由<odb/database.hxx>自动包含的。但是,如果处置惩罚ODB非常的源文件不包含<ODB/database.hxx>,则需要显式包含其中一个标头。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

民工心事

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表