3 Working with Persistent Objects ODB使用持久对象
The previous chapters gave us a high-level overview of ODB and showed how to use it to store C++ objects in a database. In this chapter we will examine the ODB object persistence model as well as the core database APIs in greater detail. We will start with basic concepts and terminology in Section 3.1 and Section 3.3 and continue with the discussion of the odb::database
class in Section 3.4, transactions in Section 3.5, and connections in Section 3.6. The remainder of this chapter deals with the core database operations and concludes with the discussion of ODB exceptions.
前几章介绍了ODB的高级概述,并展示了如何使用它来存储数据库中的C++对象。在本章中,我们将更详细地研究ODB对象持久性模型以及核心数据库API。我们将从第3.1节和第3.3节中的基本概念和术语开始,继续讨论第3.4节中的odb::database类、第3.5节中的事务以及第3.6节中的连接。本章的其余部分将讨论核心数据库操作,并以ODB异常的讨论作为结束。
In this chapter we will continue to use and expand the person
persistent class that we have developed in the previous chapter.
在本章中,我们将继续使用并扩展我们在前一章中开发的person持久类。
3.1 Concepts and Terminology 概念和术语
The term database can refer to three distinct things: a general notion of a place where an application stores its data, a software implementation for managing this data (for example MySQL), and, finally, some database software implementations may manage several data stores which are usually distinguished by name. This name is also commonly referred to as a database.
术语数据库可以指三种不同的东西:应用程序存储其数据的地方的一般概念,管理此数据的软件实现(例如MySQL),最后,一些数据库软件实现可以管理多个通常按名称区分的数据存储。此名称通常也称为数据库。
In this manual, when we use the word database, we refer to the first meaning above, for example, "The update()
function saves the object's state to the database." The term Database Management System (DBMS) is often used to refer to the second meaning of the word database. In this manual we will use the term database system for short, for example, "Database system-independent application code." Finally, to distinguish the third meaning from the other two, we will use the term database name, for example, "The second option specifies the database name that the application should use to store its data."
在本手册中,当我们使用单词database时,我们引用上面的第一个意思,例如,“update()函数将对象的状态保存到数据库中。”术语数据库管理系统(DBMS)通常用于指代“数据库”一词的第二种含义。在本手册中,我们将简称“数据库系统”,例如,“与数据库系统无关的应用程序代码”最后,为了区分第三种含义与其他两种含义,我们将使用术语database name,例如,“第二个选项指定应用程序应用于存储其数据的数据库名称。”
In C++ there is only one notion of a type and an instance of a type. For example, a fundamental type, such as int
, is, for the most part, treated the same as a user defined class type. However, when it comes to persistence, we have to place certain restrictions and requirements on certain C++ types that can be stored in the database. As a result, we divide persistent C++ types into two groups: object types and value types. An instance of an object type is called an object and an instance of a value type — a value.
在C++中,只有一种类型的概念和类型的实例。例如,基本类型(如int)在大多数情况下被视为与用户定义的类类型相同。但是,当涉及到持久性时,我们必须对某些可以存储在数据库中的C++类型设置一定的限制和要求。因此,我们将持久性C++类型分为两类:对象类型和值类型。对象类型的实例称为对象,值类型的实例称为值。
An object is an independent entity. It can be stored, updated, and deleted in the database independent of other objects. Normally, an object has an identifier, called object id, that is unique among all instances of an object type within a database. In contrast, a value can only be stored in the database as part of an object and doesn't have its own unique identifier.
对象是一个独立的实体。它可以独立于其他对象在数据库中存储、更新和删除。通常,对象具有一个称为对象id的标识符,该标识符在数据库中对象类型的所有实例中都是唯一的。相反,值只能作为对象的一部分存储在数据库中,并且没有自己的唯一标识符。
An object consists of data members which are either values (Chapter 7, "Value Types"), pointers to other objects (Chapter 6, "Relationships"), or containers of values or pointers to other objects (Chapter 5, "Containers"). Pointers to other objects and containers can be viewed as special kinds of values since they also can only be stored in the database as part of an object.
对象由数据成员组成,这些数据成员是值(第7章“值类型”)、指向其他对象的指针(第6章“关系”)或值容器或指向其他对象的指针(第5章“容器”)。指向其他对象和容器的指针可以视为特殊类型的值,因为它们也只能作为对象的一部分存储在数据库中。
An object type is a C++ class. Because of this one-to-one relationship, we will use terms object type and object class interchangeably. In contrast, a value type can be a fundamental C++ type, such as int
or a class type, such as std::string
. If a value consists of other values, then it is called a composite value and its type — a composite value type (Section 7.2, "Composite Value Types"). Otherwise, the value is called simple value and its type — a simple value type (Section 7.1, "Simple Value Types"). Note that the distinction between simple and composite values is conceptual rather than representational. For example, std::string
is a simple value type because conceptually string is a single value even though the representation of the string class may contain several data members each of which could be considered a value. In fact, the same value type can be viewed (and mapped) as both simple and composite by different applications.
对象类型是C++类。由于这种一对一的关系,我们将交替使用术语“对象类型”和“对象类”。相反,值类型可以是基本C++类型,例如int或类类型,例如STD::String。如果一个值由其他值组成,则称为复合值及其类型-复合值类型(第7.2节,“复合值类型”)。否则,该值称为简单值及其类型-简单值类型(第7.1节,“简单值类型”)。请注意,简单值和复合值之间的区别是概念性的,而不是代表性的。例如,std::string是一种简单的值类型,因为从概念上讲,string是一个值,即使string类的表示可能包含多个数据成员,每个数据成员都可以被视为一个值。事实上,相同的值类型可以被不同的应用程序视为简单值和复合值。
While not strictly necessary in a purely object-oriented application, practical considerations often require us to only load a subset of an object's data members or a combination of members from several objects. We may also need to factor out some computations to the relational database instead of performing them in the application's process. To support such requirements ODB distinguishes a third kind of C++ types, called views (Chapter 10, "Views"). An ODB view is a C++ class
that embodies a light-weight, read-only projection of one or more persistent objects or database tables or the result of a native SQL query execution.
虽然在纯面向对象的应用程序中并非绝对必要,但实际考虑通常要求我们仅加载对象数据成员的子集或来自多个对象的成员组合。我们可能还需要对关系数据库进行一些计算,而不是在应用程序的过程中执行这些计算。为了支持这样的需求,ODB区分了第三种C++类型,称为视图(第10章,“视图”)。ODB视图是一个C++类,它包含一个或多个持久对象或数据库表的轻量、只读投影或本机SQL查询执行的结果。
Understanding how all these concepts map to the relational model will hopefully make these distinctions clearer. In a relational database an object type is mapped to a table and a value type is mapped to one or more columns. A simple value type is mapped to a single column while a composite value type is mapped to several columns. An object is stored as a row in this table and a value is stored as one or more cells in this row. A simple value is stored in a single cell while a composite value occupies several cells. A view is not a persistent entity and it is not stored in the database. Rather, it is a data structure that is used to capture a single row of an SQL query result.
了解所有这些概念如何映射到关系模型,有望使这些区别更加清晰。在关系数据库中,对象类型映射到表,值类型映射到一个或多个列。简单值类型映射到单个列,而复合值类型映射到多个列。对象在该表中存储为一行,并输入一个值
Going back to the distinction between simple and composite values, consider a date type which has three integer members: year, month, and day. In one application it can be considered a composite value and each member will get its own column in a relational database. In another application it can be considered a simple value and stored in a single column as a number of days from some predefined date.
回到简单值和复合值之间的区别,考虑一个日期类型,它有三个整数成员:年、月和日。在一个应用程序中,它可以被视为一个复合值,并且每个成员将在关系数据库中获得自己的列。在另一个应用程序中,它可以被视为一个简单的值,并作为从某个预定义日期开始的天数存储在单个列中。
Until now, we have been using the term persistent class to refer to object classes. We will continue to do so even though a value type can also be a class. The reason for this asymmetry is the subordinate nature of value types when it comes to database operations. Remember that values are never stored directly but rather as part of an object that contains them. As a result, when we say that we want to make a C++ class persistent or persist an instance of a class in the database, we invariably refer to an object class rather than a value class.
到目前为止,我们一直使用术语persistent class来指代对象类。我们将继续这样做,即使值类型也可以是类。这种不对称的原因是在数据库操作中,值类型的从属性质。请记住,值从不直接存储,而是作为包含它们的对象的一部分存储。因此,当我们说要让C++类持久化或在数据库中保存类的实例时,我们总是引用对象类而不是值类。
Normally, you would use object types to model real-world entities, things that have their own identity. For example, in the previous chapter we created a person
class to model a person, which is a real-world entity. Name and age, which we used as data members in our person
class are clearly values. It is hard to think of age 31 or name "Joe" as having their own identities.
通常,您会使用对象类型对真实世界的实体进行建模,这些实体具有自己的标识。例如,在上一章中,我们创建了一个person类来对person进行建模,person是一个真实的实体。在person类中用作数据成员的Name和age显然是值。很难想象31岁或叫“乔”有自己的身份。
A good test to determine whether something is an object or a value, is to consider if other objects might reference it. A person is clearly an object because it can be referred to by other objects such as a spouse, an employer, or a bank. On the other hand, a person's age or name is not something that other objects would normally refer to.
确定某事物是一个对象还是一个值是一个很好的测试,是考虑其他对象是否可以引用它。一个人显然是一个对象,因为它可以被其他对象引用,例如配偶、雇主或银行。另一方面,一个人的年龄或名字并不是其他物体通常所指的东西。
Also, when an object represents a real entity, it is easy to choose a suitable object id. For example, for a person there is an established notion of an identifier (SSN, student id, passport number, etc). Another alternative is to use a person's email address as an identifier.
此外,当一个对象代表一个真实的实体时,很容易选择一个合适的对象id。例如,对于一个人来说,有一个确定的标识符概念(SSN、学生id、护照号等)。另一种选择是使用一个人的电子邮件地址作为标识符。
Note, however, that these are only guidelines. There could be good reasons to make something that would normally be a value an object. Consider, for example, a database that stores a vast number of people. Many of the person
objects in this database have the same names and surnames and the overhead of storing them in every object may negatively affect the performance. In this case, we could make the first name and last name each an object and only store pointers to these objects in the person
class.
但是,请注意,这些只是指南。可能有很好的理由将通常是值的东西作为对象。例如,考虑一个存储大量人的数据库。此数据库中的许多person对象具有相同的名称和姓氏,在每个对象中存储它们的开销可能会对性能产生负面影响。在这种情况下,我们可以使名字和姓氏都成为一个对象,并且只在person类中存储指向这些对象的指针。
An instance of a persistent class can be in one of two states: transient and persistent. A transient instance only has a representation in the application's memory and will cease to exist when the application terminates, unless it is explicitly made persistent. In other words, a transient instance of a persistent class behaves just like an instance of any ordinary C++ class. A persistent instance has a representation in both the application's memory and the database. A persistent instance will remain even after the application terminates unless and until it is explicitly deleted from the database.
持久类的实例可以处于两种状态之一:瞬态和持久。临时实例在应用程序的内存中只有一个表示形式,并且在应用程序终止时将不再存在,除非显式地将其永久化。换句话说,持久类的一个临时实例就像任何普通C++类的实例一样。持久实例在应用程序内存和数据库中都有表示。即使在应用程序终止之后,持久实例仍将保留,除非并直到它从数据库中显式删除。
3.2 Declaring Persistent Objects and Values 声明持久对象和值
To make a C++ class a persistent object class we declare it as such using the db object
pragma, for example:
为了使C++类成为持久对象类,我们使用db object
pragma声明它,例如:
#pragma db object class person { ... };
The other pragma that we often use is db id
which designates one of the data members as an object id, for example:
我们经常使用的另一个pragma是db id,它将其中一个数据成员指定为对象id,例如:
#pragma db object class person { ... #pragma db id unsigned long id_; };
The object id can be of a simple or composite (Section 7.2.1, "Composite Object Ids") value type. This type should be default-constructible, copy-constructible, and copy-assignable. It is also possible to declare a persistent class without an object id, however, such a class will have limited functionality (Section 14.1.6, "no_id
").
对象id可以是简单或复合(第7.2.1节“复合对象id”)值类型。此类型应为默认可构造、可复制可构造和可复制可分配。也可以在没有对象id的情况下声明持久类,但是,此类类的功能有限(第14.1.6节,“no id”)。
The above two pragmas are the minimum required to declare a persistent class with an object id. Other pragmas can be used to fine-tune the database-related properties of a class and its members (Chapter 14, "ODB Pragma Language").
上述两个pragmas 是声明具有对象id的持久类所需的最低要求。其他pragmas 可用于微调类及其成员的数据库相关属性(第14章,“ODB pragma语言”)。
Normally, a persistent class should define the default constructor. The generated database support code uses this constructor when instantiating an object from the persistent state. If we add the default constructor only for the database support code, then we can make it private provided we also make the odb::access
class, defined in the <odb/core.hxx>
header, a friend of this object class. For example:
通常,持久类应该定义默认构造函数。生成的数据库支持代码在从持久状态实例化对象时使用此构造函数。如果我们只为数据库支持代码添加默认构造函数,那么我们可以将其设置为私有的,同时我们还设置了odb::access类,定义在<odb/core. hxx>头中,此对象类的友元类。例如:
#include <odb/core.hxx> #pragma db object class person { ... private: friend class odb::access; person () {} };
It is also possible to have an object class without the default constructor. However, in this case, the database operations will only be able to load the persistent state into an existing instance (Section 3.9, "Loading Persistent Objects", Section 4.4, "Query Result").
也可以有一个没有默认构造函数的对象类。但是,在这种情况下,数据库操作只能将持久状态加载到现有实例中(第3.9节“加载持久化对象”,第4.4节“查询结果”)。
The ODB compiler also needs access to the non-transient (Section 14.4.11, "transient
") data members of a persistent class. The ODB compiler can access such data members directly if they are public. It can also do so if they are private or protected and the odb::access
class is declared a friend of the object type. For example:
ODB编译器还需要访问持久类的非瞬态(第14.4.11节,“瞬态”)数据成员。如果这些数据成员是公共的,ODB编译器可以直接访问它们。如果它们是私有的或受保护的,并且odb::access类被声明为对象类型的友元,那么它也可以这样做。例如:
#include <odb/core.hxx>
#pragma db object class person { ... private: friend class odb::access; person () {} #pragma db id unsigned long id_; std::string name_; };
If data members are not accessible directly, then the ODB compiler will try to automatically find suitable accessor and modifier functions. To accomplish this, the ODB compiler will try to lookup common accessor and modifier names derived from the data member name. Specifically, for the name_
data member in the above example, the ODB compiler will look for accessor functions with names: get_name()
, getName()
, getname()
, and just name()
as well as for modifier functions with names: set_name()
, setName()
, setname()
, and just name()
. You can also add support for custom name derivations with the --accessor-regex
and --modifier-regex
ODB compiler options. Refer to the ODB Compiler Command Line Manual for details on these options. The following example illustrates automatic accessor and modifier discovery:
如果无法直接访问数据成员,那么ODB编译器将尝试自动找到合适的访问器和修改器函数。为了实现这一点,ODB编译器将尝试查找从数据成员名称派生的公共访问器和修饰符名称。具体地说,对于上面示例中的name_ 数据成员,ODB编译器将查找具有以下名称的访问器函数:get_name()、getName()、getName()和 name(),以及具有以下名称的修改器函数:set_name()、setName()、setName()和 name()。您还可以使用--accessor regex和--modifier regex ODB编译器选项添加对自定义名称派生的支持。有关这些选项的详细信息,请参阅ODB编译器命令行手册。以下示例演示了自动访问器和修改器发现:
#pragma db object class person { public: person () {} ... unsigned long id () const; void id (unsigned long); const std::string& get_name () const; std::string& set_name (); private: #pragma db id unsigned long id_; // Uses id() for access. std::string name_; // Uses get_name()/set_name() for access. };
Finally, if a data member is not directly accessible and the ODB compiler was unable to discover suitable accessor and modifier functions, then we can provide custom accessor and modifier expressions using the db get
and db set
pragmas. For more information on custom accessor and modifier expressions refer to Section 14.4.5, "get
/set
/access
".
最后,如果数据成员不能直接访问,并且ODB编译器无法发现合适的访问器和修饰符函数,那么我们可以使用db get和db set pragmas提供自定义访问器和修饰符表达式。有关自定义访问器和修饰符表达式的更多信息,请参阅第14.4节。5,“获取/设置/访问”。
Data members of a persistent class can also be split into separately-loaded and/or separately-updated sections. For more information on this functionality, refer to Chapter 9, "Sections".
持久类的数据成员也可以拆分为单独加载和/或单独更新的部分。有关此功能的更多信息,请参阅第9章“Sections”。
You may be wondering whether we also have to declare value types as persistent. We don't need to do anything special for simple value types such as int
or std::string
since the ODB compiler knows how to map them to suitable database types and how to convert between the two. On the other hand, if a simple value is unknown to the ODB compiler then we will need to provide the mapping to the database type and, possibly, the code to convert between the two. For more information on how to achieve this refer to the db type
pragma description in Section 14.3.1, "type
".
您可能想知道我们是否还必须将值类型声明为持久的。对于简单的值类型,例如int或std::string,我们不需要做任何特殊的操作,因为ODB编译器知道如何将它们映射到合适的数据库类型,以及如何在两者之间进行转换。另一方面,如果ODB编译器不知道一个简单的值,那么我们将需要提供到数据库类型的映射,可能还需要提供在两者之间转换的代码。有关如何实现此目的的更多信息,请参阅第14.3节中的db类型db type
pragma。
Similar to object classes, composite value types have to be explicitly declared as persistent using the db value
pragma, for example:
与对象类类似,复合值类型必须使用db value pragma显式声明为持久,例如:
#pragma db value class name { ... std::string first_; std::string last_; };
Note that a composite value cannot have a data member designated as an object id since, as we have discussed earlier, values do not have a notion of identity. A composite value type also doesn't have to define the default constructor, unless it is used as an element of a container. The ODB compiler uses the same mechanisms to access data members in composite value types as in object types. Composite value types are discussed in more detail in Section 7.2, "Composite Value Types".
请注意,复合值不能将数据成员指定为对象id,因为正如我们前面所讨论的,值没有标识的概念。复合值类型也不必定义默认构造函数,除非它用作容器的元素。ODB编译器使用与对象类型相同的机制来访问复合值类型中的数据成员。第7.2节“复合值类型”详细讨论了复合值类型。
3.3 Object and View Pointers 对象和视图指针
As we have seen in the previous chapter, some database operations create dynamically allocated instances of persistent classes and return pointers to these instances. As we will see in later chapters, pointers are also used to establish relationships between objects (Chapter 6, "Relationships") as well as to cache persistent objects in a session (Chapter 11, "Session"). While in most cases you won't need to deal with pointers to views, it is possible to a obtain a dynamically allocated instance of a view using the result_iterator::load()
function (Section 4.4, "Query Results").
正如我们在前一章中所看到的,一些数据库操作创建动态分配的持久类实例,并返回指向这些实例的指针。我们将在后面的章节中看到,指针还用于建立对象之间的关系(第6章“关系”),以及在会话中缓存持久对象(第11章“会话”)。虽然在大多数情况下,您不需要处理指向视图的指针,但可以使用result_iterator::load()
函数(第4.4节“查询结果”)获取动态分配的视图实例。
By default, all these mechanisms use raw pointers to return objects and views as well as to pass and cache objects. This is normally sufficient for applications that have simple object lifetime requirements and do not use sessions or object relationships. In particular, a dynamically allocated object or view that is returned as a raw pointer from a database operation can be assigned to a smart pointer of our choice, for example std::auto_ptr
, std::unique_ptr
from C++11, or shared_ptr
from TR1, C++11, or Boost.
默认情况下,所有这些机制都使用原始指针返回对象和视图,以及传递和缓存对象。对于具有简单对象生存期要求且不使用会话或对象关系的应用程序,这通常就足够了。特别是,作为原始指针从数据库操作返回的动态分配的对象或视图可以分配给我们选择的智能指针,例如C++11中的std::auto_ptr、std::unique_ptr或TR1、C++11或Boost中的shared_ptr。
However, to avoid any possibility of a mistake, such as forgetting to use a smart pointer for a returned object or view, as well as to simplify the use of more advanced ODB functionality, such as sessions and bidirectional object relationships, it is recommended that you use smart pointers with the sharing semantics as object pointers. The shared_ptr
smart pointer from TR1, C++11, or Boost is a good default choice. However, if sharing is not required and sessions are not used, then std::unique_ptr
or std::auto_ptr
can be used just as well.
但是,为了避免任何错误的可能性,例如忘记对返回的对象或视图使用智能指针,以及简化更高级ODB功能(如会话和双向对象关系)的使用,建议使用具有共享语义的智能指针作为对象指针。来自TR1、C++11或Boost的shared_ptr
智能指针是一个很好的默认选择。但是,如果不需要共享且不使用会话,则也可以使用std::unique_ptr
或std::auto_ptr
。
ODB provides several mechanisms for changing the object or view pointer type. To specify the pointer type on the per object or per view basis we can use the db pointer
pragma, for example:
ODB提供了几种更改对象或视图指针类型的机制。要基于每个对象或每个视图指定指针类型,我们可以使用db pointer pragma,例如:
#pragma db object pointer(std::tr1::shared_ptr) class person { ... };
We can also specify the default pointer for a group of objects or views at the namespace level:
我们还可以在命名空间级别为一组对象或视图指定默认指针:
#pragma db namespace pointer(std::tr1::shared_ptr) namespace accounting { #pragma db object class employee { ... }; #pragma db object class employer { ... }; }
Finally, we can use the --default-pointer
option to specify the default pointer for the whole file. Refer to the ODB Compiler Command Line Manual for details on this option's argument. The typical usage is shown below:
最后,我们可以使用--default pointer选项为整个文件指定默认指针。有关此选项参数的详细信息,请参阅ODB编译器命令行手册。典型用法如下所示:
--default-pointer std::tr1::shared_ptr
An alternative to this method with the same effect is to specify the default pointer for the global namespace
此方法的另一种替代方法具有相同的效果,即为全局命名空间指定默认指针:
#pragma db namespace() pointer(std::tr1::shared_ptr)
请注意,我们始终可以使用db pointer对象或view pragma覆盖在命名空间级别指定的默认指针,或使用命令行选项。例如:
#pragma db object pointer(std::shared_ptr) namespace accounting { #pragma db object class employee { ... }; #pragma db object pointer(std::unique_ptr) class employer { ... }; }
Refer to Section 14.1.2, "pointer
(object)", Section 14.2.4, "pointer
(view)", and Section 14.5.1, "pointer
(namespace)" for more information on these mechanisms.
参考第14.1.2,“指针(对象)”,第14.2.4,“指针(视图)”和第14.5.1,“指针(名称空间)”,了解有关这些机制的更多信息。
Built-in support that is provided by the ODB runtime library allows us to use shared_ptr
(TR1 or C++11), std::unique_ptr
(C++11), or std::auto_ptr
as pointer types. Plus, ODB profile libraries, that are available for commonly used frameworks and libraries (such as Boost and Qt), provide support for smart pointers found in these frameworks and libraries (Part III, "Profiles"). It is also easy to add support for our own smart pointers, as described in Section 6.5, "Using Custom Smart Pointers".
ODB运行时库提供的内置支持允许我们使用shared_ptr(TR1或C++11)、std::unique_ptr(C++11)或std::auto_ptr作为指针类型。另外,ODB概要文件库可用于常用的框架和库(如Boost和Qt),为这些框架和库中的智能指针提供支持(第三部分,“概要文件”)。还可以很容易地添加对我们自己的智能指针的支持,如第6.5节“使用自定义智能指针”所述。
3.4 Database 数据库
Before an application can make use of persistence services offered by ODB, it has to create a database class instance. A database instance is the representation of the place where the application stores its persistent objects. We create a database instance by instantiating one of the database system-specific classes. For example, odb::mysql::database
would be such a class for the MySQL database system. We will also normally pass a database name as an argument to the class' constructor. The following code fragment shows how we can create a database instance for the MySQL database system:
在应用程序可以使用ODB提供的持久性服务之前,它必须创建一个数据库类实例。数据库实例表示应用程序存储其持久对象的位置。我们通过实例化一个特定于数据库系统的类来创建一个数据库实例。例如,odb::mysql::database
就是mysql数据库系统的此类。我们通常还会将数据库名称作为参数传递给类的构造函数。下面的代码片段显示了如何为MySQL数据库系统创建数据库实例:
#include <odb/database.hxx> #include <odb/mysql/database.hxx> auto_ptr<odb::database> db ( new odb::mysql::database ( "test_user" // database login name "test_password" // database password "test_database" // database name ));
The odb::database
class is a common interface for all the database system-specific classes provided by ODB. You would normally work with the database instance via this interface unless there is a specific functionality that your application depends on and which is only exposed by a particular system's database
class. You will need to include the <odb/database.hxx>
header file to make this class available in your application.
odb::database
是odb提供的所有数据库系统特定类的公共接口。您通常会通过此接口处理数据库实例,除非您的应用程序依赖于特定的功能,并且该功能仅由特定系统的数据库类公开。您需要包括<odb/database.hxx>
头文件,使该类在应用程序中可用。
The odb::database
interface defines functions for starting transactions and manipulating persistent objects. These are discussed in detail in the remainder of this chapter as well as the next chapter which is dedicated to the topic of querying the database for persistent objects. For details on the system-specific database
classes, refer to Part II, "Database Systems".
数据库接口定义了用于启动事务和操作持久对象的函数。这些将在本章的其余部分以及下一章中详细讨论,下一章将专门讨论查询数据库中持久对象的主题。有关系统特定数据库类的详细信息,请参阅第二部分“数据库系统”。
Before we can persist our objects, the corresponding database schema has to be created in the database. The schema contains table definitions and other relational database artifacts that are used to store the state of persistent objects in the database.
在持久化对象之前,必须在数据库中创建相应的数据库模式。模式包含表定义和其他关系数据库工件,用于存储数据库中持久对象的状态。
There are several ways to create the database schema. The easiest is to instruct the ODB compiler to generate the corresponding schema from the persistent classes (--generate-schema
option). The ODB compiler can generate the schema as a standalone SQL file, embedded into the generated C++ code, or as a separate C++ source file (--schema-format
option). If we are using the SQL file to create the database schema, then this file should be executed, normally only once, before the application is started.
有几种方法可以创建数据库模式。最简单的方法是指示ODB编译器从持久类(--generate-schema
选项)生成相应的模式。ODB编译器可以生成模式作为独立SQL文件,嵌入到生成的C++代码中,或者作为单独的C++源文件(模式模式选项)。如果我们使用SQL文件来创建数据库模式,那么这个文件应该在应用程序启动之前执行,通常只执行一次。
Alternatively, if the schema is embedded directly into the generated code or produced as a separate C++ source file, then we can use the odb::schema_catalog
class to create it in the database from within our application, for example:
或者,如果模式直接嵌入生成代码中或作为单独的C++源文件生成,那么我们可以使用odb::schema_catalog
类来在数据库中从我们的应用程序中创建它,例如:
#include <odb/schema-catalog.hxx> odb::transaction t (db->begin ()); odb::schema_catalog::create_schema (*db); t.commit ();
Refer to the next section for information on the odb::transaction
class. The complete version of the above code fragment is available in the schema/embedded
example in the odb-examples
package.
有关odb::transaction类的信息,请参阅下一节。上述代码片段的完整版本可在odb-example
包的schema/embedded
示例中获得。
The odb::schema_catalog
class has the following interface. You will need to include the <odb/schema-catalog.hxx>
header file to make this class available in your application.
odb::schema_catalog
类具有以下接口。您需要包括<odb/schema-catalog.hxx>
头文件,使该类在应用程序中可用。
namespace odb { class schema_catalog { public: static void create_schema (database&, const std::string& name = "", bool drop = true); static void drop_schema (database&, const std::string& name = ""); static bool exists (database_id, const std::string& name = ""); static bool exists (const database&, const std::string& name = "") }; }
The first argument to the create_schema()
function is the database instance that we would like to create the schema in. The second argument is the schema name. By default, the ODB compiler generates all embedded schemas with the default schema name (empty string). However, if your application needs to have several separate schemas, you can use the --schema-name
ODB compiler option to assign custom schema names and then use these names as a second argument to create_schema()
. By default, create_schema()
will also delete all the database objects (tables, indexes, etc.) if they exist prior to creating the new ones. You can change this behavior by passing false
as the third argument. The drop_schema()
function allows you to delete all the database objects without creating the new ones.
create_schema()函数的第一个参数是要在其中创建模式的数据库实例。第二个参数是模式名称。默认情况下,ODB编译器使用默认模式名称(空字符串)生成所有嵌入式模式。但是,如果您的应用程序需要有多个单独的模式,则可以使用--schema name ODB 编译选项来分配自定义模式名称,然后将这些名称用作create_schema()
的第二个参数。默认情况下,create_schema()还将删除所有数据库对象(表、索引等),如果它们在创建新对象之前存在。您可以通过传递false作为第三个参数来更改此行为。drop_schema()函数允许您删除所有数据库对象,而无需创建新对象。
If the schema is not found, the create_schema()
and drop_schema()
functions throw the odb::unknown_schema
exception. You can use the exists()
function to check whether a schema for the specified database and with the specified name exists in the catalog. Note also that the create_schema()
and drop_schema()
functions should be called within a transaction.
如果找不到架构,create_schema()和drop_schema()函数将抛出odb::unknown_schema异常。可以使用exists()函数检查目录中是否存在指定数据库和具有指定名称的架构。还请注意,应该在事务中调用create_schema()和drop_schema()函数。
ODB also provides support for database schema evolution. Similar to schema creation, schema migration statements can be generated either as standalone SQL files or embedded into the generated C++ code. For more information on schema evolution support, refer to Chapter 13, "Database Schema Evolution".
ODB还提供对数据库模式演化的支持。与架构创建类似,模式迁移语句可以作为独立SQL文件生成,也可以嵌入到生成的C++代码中。有关模式演化支持的更多信息,请参阅第13章“数据库模式演化”。
Finally, we can also use a custom database schema with ODB. This approach can work similarly to the standalone SQL file described above except that the database schema is hand-written or produced by another program. Or we could execute custom SQL statements that create the schema directly from our application. To map persistent classes to custom database schemas, ODB provides a wide range of mapping customization pragmas, such as db table
, db column
, and db type
(Chapter 14, "ODB Pragma Language"). For sample code that shows how to perform such mapping for various C++ constructs, refer to the schema/custom
example in the odb-examples
package.
最后,我们还可以在ODB中使用自定义数据库模式。除了数据库模式是手工编写或由另一个程序生成之外,这种方法的工作原理与上述独立SQL文件类似。或者我们可以执行自定义SQL语句,直接从应用程序创建模式。为了将持久类映射到自定义数据库模式,ODB提供了一系列映射自定义pragmas,例如db table、db column和db type(第14章,“ODB Pragma Language”)。对于显示如何为各种C++构造执行这种映射的示例代码,请参阅odb-examples
包中的schema/custom
示例。
3.5 Transactions 事务
A transaction is an atomic, consistent, isolated and durable (ACID) unit of work. Database operations can only be performed within a transaction and each thread of execution in an application can have only one active transaction at a time.
事务是原子的、一致的、隔离的和持久的(ACID)工作单元。数据库操作只能在事务中执行,应用程序中的每个执行线程一次只能有一个活动事务。
By atomicity we mean that when it comes to making changes to the database state within a transaction, either all the changes are applied or none at all. Consider, for example, a transaction that transfers funds between two objects representing bank accounts. If the debit function on the first object succeeds but the credit function on the second fails, the transaction is rolled back and the database state of the first object remains unchanged.
原子性是指在事务中对数据库状态进行更改时,要么应用所有更改,要么根本不应用任何更改。例如,考虑在代表银行账户的两个对象之间转移资金的交易。如果第一个对象上的借方函数成功,但第二个对象上的贷方函数失败,则事务将回滚,并且第一个对象的数据库状态保持不变。
By consistency we mean that a transaction must take all the objects stored in the database from one consistent state to another. For example, if a bank account object must reference a person object as its owner and we forget to set this reference before making the object persistent, the transaction will be rolled back and the database will remain unchanged.
一致性是指事务必须将数据库中存储的所有对象从一个一致状态转移到另一个一致状态。例如,如果银行帐户对象必须引用person对象作为其所有者,而我们忘记在持久化对象之前设置此引用,则事务将回滚,数据库将保持不变。
By isolation we mean that the changes made to the database state during a transaction are only visible inside this transaction until and unless it is committed. Using the above example with the bank transfer, the results of the debit operation performed on the first object is not visible to other transactions until the credit operation is successfully completed and the transaction is committed.
所谓隔离,我们的意思是,在事务期间对数据库状态所做的更改只有在提交之前和提交之前在该事务内部可见。使用上述银行转账示例,在成功完成贷记操作并提交交易之前,对第一个对象执行的借记操作的结果对其他交易不可见。
By durability we mean that once the transaction is committed, the changes that it made to the database state are permanent and will survive failures such as an application crash. From now on the only way to alter this state is to execute and commit another transaction.
所谓持久性,我们的意思是,一旦事务被提交,它对数据库状态所做的更改将是永久性的,并且将在诸如应用程序崩溃之类的故障中生存下来。从现在起,改变这种状态的唯一方法是执行并提交另一个事务。
A transaction is started by calling either the database::begin()
or connection::begin()
function. The returned transaction handle is stored in an instance of the odb::transaction
class. You will need to include the <odb/transaction.hxx>
header file to make this class available in your application. For example:
通过调用database::begin()
或connection::begin()
函数启动事务。返回的事务句柄存储在odb::transaction类的实例中。您需要包含<odb/transaction.hxx>
头文件,使该类在应用程序中可用。例如:
#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: ... }; }
The commit()
function commits a transaction and rollback()
rolls it back. Unless the transaction has been finalized, that is, explicitly committed or rolled back, the destructor of the transaction
class will automatically roll it back when the transaction instance goes out of scope. If we try to commit or roll back a finalized transaction, the odb::transaction_already_finalized
exception is thrown.
commit()函数提交一个事务,rollback()将其回滚。除非事务已完成,即显式提交或回滚,否则事务类的析构函数将在事务实例超出范围时自动回滚它。如果我们尝试提交或回滚已完成的事务,则会引发odb::transaction_ready_finalized异常。
The database()
accessor returns the database this transaction is working on. Similarly, the connection()
accessor returns the database connection this transaction is on (Section 3.6, "Connections").
database()访问器返回此事务处理的数据库。类似地,connection()访问器返回该事务所在的数据库连接(第3.6节“连接”)。
The static current()
accessor returns the currently active transaction for this thread. If there is no active transaction, this function throws the odb::not_in_transaction
exception. We can check whether there is a transaction in effect in this thread using the has_current()
static function.
静态的current()访问器返回此线程当前活动的事务。如果没有活动事务,此函数将抛出odb::not_in_事务异常。我们可以使用has_current()静态函数检查此线程中是否存在有效的事务。
The make_current
argument in the transaction
constructor as well as the static current()
modifier and reset_current()
function give us additional control over the nomination of the currently active transaction. If we pass false
as the make_current
argument, then the newly created transaction will not automatically be made the active transaction for this thread. Later, we can use the static current()
modifier to set this transaction as the active transaction. The reset_current()
static function clears the currently active transaction. Together, these mechanisms allow for more advanced use cases, such as multiplexing two or more transactions on the same thread. For example:
事务构造函数中的make_current参数以及static current()修饰符和reset_current()函数为我们提供了对当前活动事务的指定的额外控制。如果我们将false作为make_current参数传递,那么新创建的事务将不会自动成为该线程的活动事务。稍后,我们可以使用static current()修饰符将此事务设置为活动事务。reset_current()静态函数用于清除当前活动的事务。这些机制一起支持更高级的用例,例如在同一线程上多路复用两个或多个事务。例如:
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 ();
The reset()
modifier allows us to reuse the same transaction
instance to complete several database transactions. Similar to the destructor, reset()
will roll the current transaction back if it hasn't been finalized. The default transaction
constructor creates a finalized transaction which can later be initialized using reset()
. The finilized()
accessor can be used to check whether the transaction has been finalized. Here is how we can use this functionality to commit the current transaction and start a new one every time a certain number of database operations has been performed:
reset()修饰符允许我们重用同一事务实例来完成多个数据库事务。与析构函数类似,如果当前事务尚未完成,reset()将回滚该事务。默认事务构造函数创建一个最终完成的事务,稍后可以使用reset()初始化该事务。finilized()访问器可用于检查事务是否已完成。下面是我们如何使用此功能提交当前事务,并在每次执行一定数量的数据库操作时启动新事务:
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 ();
For more information on the transaction callback support, refer to Section 15.1, "Transaction Callbacks".
有关事务回调支持的更多信息,请参阅第15.1节“事务回调”。
Note that in the above discussion of atomicity, consistency, isolation, and durability, all of those guarantees only apply to the object's state in the database as opposed to the object's state in the application's memory. It is possible to roll a transaction back but still have changes from this transaction in the application's memory. An easy way to avoid this potential inconsistency is to instantiate persistent objects only within the transaction scope. Consider, for example, these two implementations of the same transaction:
注意,在上面关于原子性、一致性、隔离性和持久性的讨论中,所有这些保证只适用于数据库中对象的状态,而不是应用程序内存中对象的状态。可以回滚事务,但应用程序内存中仍有来自该事务的更改。避免这种潜在不一致性的一种简单方法是仅在事务范围内实例化持久对象。例如,考虑同一事务的这两种实现:
void update_age (database& db, person& p) { transaction t (db.begin ()); p.age (p.age () + 1); db.update (p); t.commit (); }
In the above implementation, if the update()
call fails and the transaction is rolled back, the state of the person
object in the database and the state of the same object in the application's memory will differ. Now consider an alternative implementation which only instantiates the person
object for the duration of the transaction:
在上述实现中,如果update()调用失败并回滚事务,则数据库中person对象的状态和应用程序内存中相同对象的状态将不同。现在考虑一个替代的实现,它只在事务的持续时间实例化对象对象:
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 (); }
Of course, it may not always be possible to write the application in this style. Oftentimes we need to access and modify the application's state of persistent objects out of transactions. In this case it may make sense to try to roll back the changes made to the application state if the transaction was rolled back and the database state remains unchanged. One way to do this is to re-load the object's state from the database, for example:
当然,并非总是可以用这种风格编写应用程序。通常,我们需要从事务中访问和修改应用程序的持久对象状态。在这种情况下,如果事务已回滚且数据库状态保持不变,则尝试回滚对应用程序状态所做的更改可能是有意义的。一种方法是从数据库重新加载对象的状态,例如:
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; } }
See also Section 15.1, "Transaction Callbacks" for an alternative approach.
另请参见第15.1节“事务回调”,了解替代方法。
3.6 Connections 连接
The odb::connection
class represents a connection to the database. Normally, you wouldn't work with connections directly but rather let the ODB runtime obtain and release connections as needed. However, certain use cases may require obtaining a connection manually. For completeness, this section describes the connection
class and discusses some of its use cases. You may want to skip this section if you are reading through the manual for the first time.
odb::connection
类表示到数据库的连接。通常,您不会直接使用连接,而是让ODB运行时根据需要获取和释放连接。但是,某些用例可能需要手动获取连接。为了完整起见,本节描述了connection类并讨论了它的一些用例。如果您是第一次阅读本手册,您可能希望跳过本节。
Similar to odb::database
, the odb::connection
class is a common interface for all the database system-specific classes provided by ODB. For details on the system-specific connection
classes, refer to Part II, "Database Systems".
与odb::database类似,odb::connection类是odb提供的所有数据库系统特定类的公共接口。有关系统特定连接类的详细信息,请参阅第二部分“数据库系统”。
To make the odb::connection
class available in your application you will need to include the <odb/connection.hxx>
header file. The odb::connection
class has the following interface:
要使odb::connection类在应用程序中可用,您需要包含<odb/connection.hxx>头文件。odb::connection类具有以下接口:
namespace odb { class connection { public: typedef odb::database database_type; transaction begin () = 0; 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); database_type& database (); }; typedef details::shared_ptr<connection> connection_ptr; }
The begin()
function is used to start a transaction on the connection. The execute()
functions allow us to execute native database statements on the connection. Their semantics are equivalent to the database::execute()
functions (Section 3.12, "Executing Native SQL Statements") except that they can be legally called outside a transaction. Finally, the database()
accessor returns a reference to the odb::database
instance to which this connection corresponds.
函数用于启动连接上的事务。 execute()函数允许我们在连接上执行本机数据库语句。它们的语义与database::execute()函数(第3.12节,“执行本机SQL语句”)相同,只是它们可以在事务外部合法调用。最后,database()访问器返回对此连接所对应的odb::database
实例的引用。
To obtain a connection we call the database::connection()
function. The connection is returned as odb::connection_ptr
, which is an implementation-specific smart pointer with the shared pointer semantics. This, in particular, means that the connection pointer can be copied and returned from functions. Once the last instance of connection_ptr
pointing to the same connection is destroyed, the connection is returned to the database
instance. The following code fragment shows how we can obtain, use, and release a connection:
为了获得连接,我们调用database::connection()函数。该连接作为odb::connection_ptr返回,它是一个具有共享指针语义的特定于实现的智能指针。这尤其意味着可以从函数中复制和返回连接指针。一旦指向同一连接的connection_ptr的最后一个实例被破坏,该连接将返回到数据库实例。以下代码片段显示了如何获取、使用和释放连接:
using namespace odb::core; database& db = ... connection_ptr c (db.connection ()); // Temporarily disable foreign key constraints. // c->execute ("SET FOREIGN_KEY_CHECKS = 0"); // Start a transaction on this connection. // transaction t (c->begin ()); ... t.commit (); // Restore foreign key constraints. // c->execute ("SET FOREIGN_KEY_CHECKS = 1"); // When 'c' goes out of scope, the connection is returned to 'db'.
Some of the use cases which may require direct manipulation of connections include out-of-transaction statement execution, such as the execution of connection configuration statements, the implementation of a connection-per-thread policy, and making sure that a set of transactions is executed on the same connection.
一些可能需要直接操纵连接的用例包括事务外语句执行,例如连接配置语句的执行、每个线程连接策略的实现,以及确保在同一连接上执行一组事务。
3.7 Error Handling and Recovery 错误处理和恢复
ODB uses C++ exceptions to report database operation errors. Most ODB exceptions signify hard errors or errors that cannot be corrected without some intervention from the application. For example, if we try to load an object with an unknown object id, the odb::object_not_persistent
exception is thrown. Our application may be able to correct this error, for instance, by obtaining a valid object id and trying again. The hard errors and corresponding ODB exceptions that can be thrown by each database function are described in the remainder of this chapter with Section 3.14, "ODB Exceptions" providing a quick reference for all the ODB exceptions.
ODB使用C++异常报告数据库操作错误。大多数ODB异常表示硬错误或在没有应用程序干预的情况下无法更正的错误。例如,如果我们试图加载一个对象id未知的对象,就会抛出odb::object_not_persistent
异常。例如,我们的应用程序可以通过获取有效的对象id并重试来纠正此错误。每个数据库函数可能引发的硬错误和相应的ODB异常在本章的其余部分进行了描述,第3.14节“ODB异常”提供了所有ODB异常的快速参考。
The second group of ODB exceptions signify soft or recoverable errors. Such errors are temporary failures which normally can be corrected by simply re-executing the transaction. ODB defines three such exceptions: odb::connection_lost
, odb::timeout
, and odb::deadlock
. All recoverable ODB exceptions are derived from the common odb::recoverable
base exception which can be used to handle all the recoverable conditions with a single catch
block.
第二组ODB异常表示软错误或可恢复错误。这些错误是暂时的故障,通常可以通过简单地重新执行事务来纠正。ODB定义了三个这样的异常:ODB::connection_lost、ODB::timeout和ODB::deadlock。所有可恢复的ODB异常都派生自公共ODB::recoverable base exception,该异常可用于使用单个catch块处理所有可恢复条件。
The odb::connection_lost
exception is thrown if a connection to the database is lost in the middle of a transaction. In this situation the transaction is aborted but it can be re-tried without any changes. Similarly, the odb::timeout
exception is thrown if one of the database operations or the whole transaction has timed out. Again, in this case the transaction is aborted but can be re-tried as is.
odb::connection_lost
如果在事务的中间丢失了与数据库的连接,则抛出连接丢失的异常。在这种情况下,事务被中止,但可以在不做任何更改的情况下重新尝试。类似地,如果其中一个数据库操作或整个事务超时,将引发odb::timeout异常。同样,在这种情况下,事务被中止,但可以按原样重新尝试。
If two or more transactions access or modify more than one object and are executed concurrently by different applications or by different threads within the same application, then it is possible that these transactions will try to access objects in an incompatible order and deadlock. The canonical example of a deadlock are two transactions in which the first has modified object1
and is waiting for the second transaction to commit its changes to object2
so that it can also update object2
. At the same time the second transaction has modified object2
and is waiting for the first transaction to commit its changes to object1
because it also needs to modify object1
. As a result, none of the two transactions can be completed.
如果两个或多个事务访问或修改多个对象,并且由不同的应用程序或同一应用程序中的不同线程并发执行,则这些事务可能会尝试以不兼容的顺序访问对象并导致死锁。死锁的典型示例是两个事务,其中第一个事务修改了object1,并等待第二个事务将其更改提交给object2,以便它也可以更新object2。同时,第二个事务已经修改了object2,并且正在等待第一个事务将其更改提交给object1,因为它还需要修改object1。因此,这两项交易都无法完成。
The database system detects such situations and automatically aborts the waiting operation in one of the deadlocked transactions. In ODB this translates to the odb::deadlock
recoverable exception being thrown from one of the database functions.
数据库系统检测到这种情况,并自动中止其中一个死锁事务中的等待操作。在ODB中,这将转换为从一个数据库函数引发的odb::deadlock可恢复异常。
The following code fragment shows how to handle the recoverable exceptions by restarting the affected transaction:
以下代码片段显示了如何通过重新启动受影响的事务来处理可恢复异常:
const unsigned short max_retries = 5; for (unsigned short retry_count (0); ; retry_count++) { try { transaction t (db.begin ()); ... t.commit (); break; } catch (const odb::recoverable& e) { if (retry_count > max_retries) throw retry_limit_exceeded (e.what ()); else continue; } }
3.8 Making Objects Persistent 使对象持久化
A newly created instance of a persistent class is transient. We use the database::persist()
function template to make a transient instance persistent. This function has four overloaded versions with the following signatures:
新创建的持久类实例是暂时的。我们使用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);
Here and in the rest of the manual, object_traits<T>::pointer_type
and object_traits<T>::const_pointer_type
denote the unrestricted and constant object pointer types (Section 3.3, "Object and View Pointers"), respectively. Similarly, object_traits<T>::id_type
denotes the object id type. The odb::object_traits
template is part of the database support code generated by the ODB compiler.
在本手册的此处和其余部分中,object_traits<T>::pointer_type
和object_traits<T>::const_pointer_type
分别表示无限制和常量对象指针类型(第3.3节,“对象和视图指针”)。类似地,object_traits<T>::id_type表示对象id类型。odb::object_traits模板是odb编译器生成的数据库支持代码的一部分。
The first persist()
function expects a constant reference to an instance being persisted. The second function expects a constant object pointer. Both of these functions can only be used on objects with application-assigned object ids (Section 14.4.2, "auto
").
第一个persist()函数需要一个对正在持久化的实例的常量引用。第二个函数需要一个常量对象指针。这两种功能只能用于具有应用程序指定对象ID的对象(第14.4.2节“自动”)。
The second and third persist()
functions are similar to the first two except that they operate on unrestricted references and object pointers. If the identifier of the object being persisted is assigned by the database, these functions update the id member of the passed instance with the assigned value. All four functions return the object id of the newly persisted object.
第二个和第三个persist()函数与前两个类似,只是它们在不受限制的引用和对象指针上操作。如果要持久化的对象的标识符由数据库分配,则这些函数将使用分配的值更新传递的实例的id成员。所有四个函数都返回新持久化对象的对象id。
If the database already contains an object of this type with this identifier, the persist()
functions throw the odb::object_already_persistent
exception. This should never happen for database-assigned object ids as long as the number of objects persisted does not exceed the value space of the id type.
如果数据库已包含具有此标识符的此类型的对象,则persist()函数将抛出odb::object_already_persistent异常。只要持久化的对象数不超过id类型的值空间,数据库分配的对象id就永远不会发生这种情况。
When calling the persist()
functions, we don't need to explicitly specify the template type since it will be automatically deduced from the argument being passed. The following example shows how we can call these functions:
调用persist()函数时,我们不需要显式指定模板类型,因为它将从传递的参数自动推断出来。以下示例显示了如何调用这些函数:
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;
Notice that in the above code fragment we have created instances that we were planning to make persistent before starting the transaction. Likewise, we printed Jane's id after we have committed the transaction. As a general rule, you should avoid performing operations within the transaction scope that can be performed before the transaction starts or after it terminates. An active transaction consumes both your application's resources, such as a database connection, as well as the database server's resources, such as object locks. By following the above rule you make sure these resources are released and made available to other threads in your application and to other applications as soon as possible.
请注意,在上面的代码片段中,我们创建了一些实例,我们计划在启动事务之前将这些实例持久化。同样,我们在提交交易后打印了Jane的id。一般来说,您应该避免在事务范围内执行可以在事务开始之前或终止之后执行的操作。活动事务既消耗应用程序的资源(如数据库连接),也消耗数据库服务器的资源(如对象锁)。通过遵循上述规则,您可以确保尽快释放这些资源,并将其提供给应用程序中的其他线程和其他应用程序。
Some database systems support persisting multiple objects with a single underlying statement execution which can result in significantly improved performance. For such database systems ODB provides bulk persist()
functions. For details, refer to Section 15.3, "Bulk Database Operations".
一些数据库系统支持使用单个底层语句执行持久化多个对象,这可以显著提高性能。对于这样的数据库系统,ODB提供了大容量persist()函数。有关详细信息,请参阅第15.3节“批量数据库操作”。
3.9 Loading Persistent Objects 加载持久对象
Once an object is made persistent, and you know its object id, it can be loaded by the application using the database::load()
function template. This function has two overloaded versions with the following signatures:
一旦一个对象被持久化,并且您知道它的对象id,应用程序就可以使用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);
Given an object id, the first function allocates a new instance of the object class in the dynamic memory, loads its state from the database, and returns the pointer to the new instance. The second function loads the object's state into an existing instance. Both functions throw odb::object_not_persistent
if there is no object of this type with this id in the database.
给定对象id,第一个函数在动态内存中分配对象类的新实例,从数据库加载其状态,并返回指向新实例的指针。第二个函数将对象的状态加载到现有实例中。如果数据库中没有具有此id的此类对象,则这两个函数都会抛出odb::object_not_persistent。
When we call the first load()
function, we need to explicitly specify the object type. We don't need to do this for the second function because the object type will be automatically deduced from the second argument, for example:
调用第一个load()函数时,需要显式指定对象类型。对于第二个函数,我们不需要这样做,因为对象类型将从第二个参数自动推断,例如:
transaction t (db.begin ()); auto_ptr<person> jane (db.load<person> (jane_id)); db.load (jane_id, *jane); t.commit ();
In certain situations it may be necessary to reload the state of an object from the database. While this is easy to achieve using the second load()
function, ODB provides the database::reload()
function template that has a number of special properties. This function has two overloaded versions with the following signatures:
在某些情况下,可能需要从数据库重新加载对象的状态。虽然使用第二个load()函数很容易实现,但ODB提供了具有许多特殊属性的database::reload()函数模板。此函数有两个重载版本,具有以下签名:
template <typename T> void reload (T& object); template <typename T> void reload (const object_traits<T>::pointer_type& object);
The first reload()
function expects an object reference, while the second expects an object pointer. Both functions expect the id member in the passed object to contain a valid object identifier and, similar to load()
, both will throw odb::object_not_persistent
if there is no object of this type with this id in the database
第一个reload()函数需要一个对象引用,而第二个函数需要一个对象指针。这两个函数都希望传递的对象中的id成员包含有效的对象标识符,并且与load()类似,如果数据库中没有具有此id的此类对象,则这两个函数都将抛出odb::object_not_persistent。
The first special property of reload()
compared to the load()
function is that it does not interact with the session's object cache (Section 11.1, "Object Cache"). That is, if the object being reloaded is already in the cache, then it will remain there after reload()
returns. Similarly, if the object is not in the cache, then reload()
won't put it there either.
与load()函数相比,reload()的第一个特殊属性是它不与会话的对象缓存交互(第11.1节“对象缓存”)。也就是说,如果正在重新加载的对象已经在缓存中,那么在reload()返回后,它将保留在缓存中。类似地,如果对象不在缓存中,则reload()也不会将其放在缓存中。
The second special property of the reload()
function only manifests itself when operating on an object with the optimistic concurrency model. In this case, if the states of the object in the application memory and in the database are the same, then no reloading will occur. For more information on optimistic concurrency, refer to Chapter 12, "Optimistic Concurrency".
reload()函数的第二个特殊属性仅在对具有开放式并发模型的对象进行操作时才显示出来。在这种情况下,如果应用程序内存和数据库中对象的状态相同,则不会重新加载。有关开放式并发的更多信息,请参阅第12章“开放式并发”。
If we don't know for sure whether an object with a given id is persistent, we can use the find()
function instead of load()
, for example:
如果我们不确定具有给定id的对象是否是持久的,我们可以使用find()函数而不是load(),例如:
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);
If an object with this id is not found in the database, the first find()
function returns a NULL
pointer while the second function leaves the passed instance unmodified and returns false
.
如果在数据库中找不到具有此id的对象,则第一个find()函数将返回空指针,而第二个函数将不修改传递的实例并返回false。
If we don't know the object id, then we can use queries to find the object (or objects) matching some criteria (Chapter 4, "Querying the Database"). Note, however, that loading an object's state using its identifier can be significantly faster than executing a query.
如果我们不知道对象id,那么我们可以使用查询来查找与某些条件匹配的对象(或多个对象)(第4章,“查询数据库”)。但是,请注意,使用对象的标识符加载对象的状态可能比执行查询快得多。
3.10 Updating Persistent Objects 更新持久对象
If a persistent object has been modified, we can store the updated state in the database using the database::update()
function template. This function has three overloaded versions with the following signatures:
如果修改了持久对象,我们可以使用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);
The first update()
function expects an object reference, while the other two expect object pointers. If the object passed to one of these functions does not exist in the database, update()
throws the odb::object_not_persistent
exception (but see a note on optimistic concurrency below).
第一个update()函数需要一个对象引用,而其他两个函数需要对象指针。如果传递给其中一个函数的对象在数据库中不存在,update()将抛出odb::object_not_persistent异常(但请参见下面关于乐观并发性的说明)。
Below is an example of the funds transfer that we talked about in the earlier section on transactions. It uses the hypothetical bank_account
persistent class:
下面是我们在前面的交易部分中讨论的资金转移示例。它使用假设的银行账户持久类:
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 (); }
The same can be accomplished using dynamically allocated objects and the update()
function with object pointer argument, for example:
使用动态分配的对象和带有对象指针参数的update()函数也可以实现这一点,例如:
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 ();
If any of the update()
functions are operating on a persistent class with the optimistic concurrency model, then they will throw the odb::object_changed
exception if the state of the object in the database has changed since it was last loaded into the application memory. Furthermore, for such classes, update()
no longer throws the object_not_persistent
exception if there is no such object in the database. Instead, this condition is treated as a change of object state and object_changed
is thrown instead. For a more detailed discussion of optimistic concurrency, refer to Chapter 12, "Optimistic Concurrency".
如果任何update()函数都在具有乐观并发模型的持久类上运行,那么如果数据库中的对象自上次加载到应用程序内存后状态发生了更改,则它们将抛出odb::object_changed异常。此外,对于这样的类,如果数据库中没有这样的对象,update()不再抛出object_not_persistent异常。相反,此条件被视为对象状态的更改,而抛出object_changed
。有关乐观并发的更详细讨论,请参阅第12章“乐观并发”。
In ODB, persistent classes, composite value types, as well as individual data members can be declared read-only (see Section 14.1.4, "readonly
(object)", Section 14.3.6, "readonly
(composite value)", and Section 14.4.12, "readonly
(data member)").
在ODB中,可以将持久类、复合值类型以及单个数据成员声明为只读(请参阅第14.1.4节“只读(对象)”,第14.3节)。6,“只读(复合值)”和第14.4节。12,“只读(数据成员)”。
If an individual data member is declared read-only, then any changes to this member will be ignored when updating the database state of an object using any of the above update()
functions. A const
data member is automatically treated as read-only. If a composite value is declared read-only then all its data members are treated as read-only.
如果将单个数据成员声明为只读,则在使用上述任何update()函数更新对象的数据库状态时,将忽略对此成员所做的任何更改。常量数据成员将自动视为只读。如果复合值声明为只读,则其所有数据成员都将被视为只读。
If the whole object is declared read-only then the database state of this object cannot be changed. Calling any of the above update()
functions for such an object will result in a compile-time error.
如果整个对象声明为只读,则无法更改此对象的数据库状态。为此类对象调用上述任何update()函数都将导致编译时错误。
Similar to persist()
, for database systems that support this functionality, ODB provides bulk update()
functions. For details, refer to Section 15.3, "Bulk Database Operations".
与persist()类似,对于支持此功能的数据库系统,ODB提供了bulk update()函数。有关详细信息,请参阅第15.3节“批量数据库操作”。
3.11 Deleting Persistent Objects 删除持久对象
To delete a persistent object's state from the database we use the database::erase()
or database::erase_query()
function templates. If the application still has an instance of the erased object, this instance becomes transient. The erase()
function has the following overloaded versions:
要从数据库中删除持久对象的状态,我们使用database::erase()或database::erase_query()函数模板。如果应用程序仍有已擦除对象的实例,则该实例将变为临时的。erase()函数具有以下重载版本:
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);
The first erase()
function uses an object itself, in the form of an object reference, to delete its state from the database. The next two functions accomplish the same result but using object pointers. Note that all three functions leave the passed object unchanged. It simply becomes transient. The last function uses the object id to identify the object to be deleted. If the object does not exist in the database, then all four functions throw the odb::object_not_persistent
exception (but see a note on optimistic concurrency below).
第一个erase()函数使用对象本身(以对象引用的形式)从数据库中删除其状态。接下来的两个函数使用对象指针实现相同的结果。请注意,所有三个函数都保持传递的对象不变。它只是变得短暂。最后一个函数使用对象id标识要删除的对象。如果对象在数据库中不存在,那么所有四个函数都会抛出odb::object_not_persistent
异常(但请参见下面关于乐观并发性的说明)。
We have to specify the object type when calling the last erase()
function. The same is unnecessary for the first three functions because the object type will be automatically deduced from their arguments. The following example shows how we can call these functions:
调用最后一个erase()函数时,必须指定对象类型。前三个函数也不需要这样做,因为对象类型将自动从它们的参数中推导出来。以下示例显示了如何调用这些函数:
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 ();
If any of the erase()
functions except the last one are operating on a persistent class with the optimistic concurrency model, then they will throw the odb::object_changed
exception if the state of the object in the database has changed since it was last loaded into the application memory. Furthermore, for such classes, erase()
no longer throws the object_not_persistent
exception if there is no such object in the database. Instead, this condition is treated as a change of object state and object_changed
is thrown instead. For a more detailed discussion of optimistic concurrency, refer to Chapter 12, "Optimistic Concurrency".
如果除最后一个函数外的任何erase()函数都在具有乐观并发模型的持久类上运行,那么如果数据库中对象的状态自上次加载到应用程序内存以来发生了更改,则它们将抛出odb::object_changed异常。此外,对于此类类,如果数据库中没有此类对象,则erase()不再抛出object_not_persistent
异常。相反,此条件被视为对象状态的更改,而抛出object_changed
。有关乐观并发的更详细讨论,请参阅第12章“乐观并发”。
Similar to persist()
and update()
, for database systems that support this functionality, ODB provides bulk erase()
functions. For details, refer to Section 15.3, "Bulk Database Operations".
与persist()和update()类似,对于支持此功能的数据库系统,ODB提供了批量erase()
函数。有关详细信息,请参阅第15.3节“批量数据库操作”。
The erase_query()
function allows us to delete the state of multiple objects matching certain criteria. It uses the query expression of the database::query()
function (Chapter 4, "Querying the Database") and, because the ODB query facility is optional, it is only available if the --generate-query
ODB compiler option was specified. The erase_query()
function has the following overloaded versions:
erase_query() 函数的作用是:删除符合特定条件的多个对象的状态。它使用database::query()函数(第4章,“查询数据库”)的查询表达式,并且由于ODB查询功能是可选的,因此只有在指定--generate query ODB compiler选项时才可用。erase_query()函数具有以下重载版本:
template <typename T> unsigned long long erase_query (); template <typename T> unsigned long long erase_query (const odb::query<T>&);
The first erase_query()
function is used to delete the state of all the persistent objects of a given type stored in the database. The second function uses the passed query instance to only delete the state of objects matching the query criteria. Both functions return the number of objects erased. When calling the erase_query()
function, we have to explicitly specify the object type we are erasing. For example:
第一个erase_query()函数用于删除数据库中存储的给定类型的所有持久对象的状态。第二个函数使用传递的查询实例仅删除与查询条件匹配的对象的状态。这两个函数都返回已擦除的对象数。调用erase_query()函数时,必须显式指定要擦除的对象类型。例如:
typedef odb::query<person> query; transaction t (db.begin ()); db.erase_query<person> (query::last == "Doe" && query::age < 30); t.commit ();
Unlike the query()
function, when calling erase_query()
we cannot use members from pointed-to objects in the query expression. However, we can still use a member corresponding to a pointer as an ordinary object member that has the id type of the pointed-to object (Chapter 6, "Relationships"). This allows us to compare object ids as well as test the pointer for NULL
. As an example, the following transaction makes sure that all the employee
objects that reference an employer
object that is about to be deleted are deleted as well. Here we assume that the employee
class contains a pointer to the employer
class. Refer to Chapter 6, "Relationships" for complete definitions of these classes.
与query()函数不同,在调用erase_query()时,我们不能在查询表达式中使用指向对象的成员。但是,我们仍然可以将指针对应的成员用作具有指向对象id类型的普通对象成员(第6章“关系”)。这允许我们比较对象ID以及测试指针是否为空。例如,下面的事务确保引用将要删除的雇主对象的所有employee对象也被删除。这里我们假设employee类包含指向employer类的指针。有关这些类别的完整定义,请参阅第6章“关系”。
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 ();
3.12 Executing Native SQL Statements 执行本机SQL语句
In some situations we may need to execute native SQL statements instead of using the object-oriented database API described above. For example, we may want to tune the database schema generated by the ODB compiler or take advantage of a feature that is specific to the database system we are using. The database::execute()
function, which has three overloaded versions, provides this functionality:
在某些情况下,我们可能需要执行本机SQL语句,而不是使用上述面向对象的数据库API。例如,我们可能希望优化ODB编译器生成的数据库模式,或者利用特定于我们正在使用的数据库系统的特性。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)
The first execute()
function expects the SQL statement as a zero-terminated C-string. The last version expects the explicit statement length as the second argument and the statement itself may contain '\0'
characters, for example, to represent binary data, if the database system supports it. All three functions return the number of rows that were affected by the statement. For example:
函数的第一个execute()函数希望SQL语句是以零结尾的C字符串。最后一个版本要求将显式语句长度作为第二个参数,并且语句本身可能包含“\0”字符,例如,用于表示二进制数据(如果数据库系统支持)。这三个函数都返回受语句影响的行数。例如:
transaction t (db.begin ()); db.execute ("DROP TABLE test"); db.execute ("CREATE TABLE test (n INT PRIMARY KEY)"); t.commit ();
While these functions must always be called within a transaction, it may be necessary to execute a native statement outside a transaction. This can be done using the connection::execute()
functions as described in Section 3.6, "Connections".
虽然必须始终在事务内调用这些函数,但可能需要在事务外执行本机语句。这可以使用第3.6节“连接”中描述的connection::execute()函数来完成。
3.13 Tracing SQL Statement Execution 跟踪SQL语句执行
Oftentimes it is useful to understand what SQL statements are executed as a result of high-level database operations. For example, we can use this information to figure out why certain transactions don't produce desired results or why they take longer than expected.
通常,了解高级数据库操作所执行的SQL语句是有用的。例如,我们可以使用这些信息来找出为什么某些事务不能产生预期的结果,或者为什么它们花费的时间比预期的要长。
While this information can usually be obtained from the database logs, ODB provides an application-side SQL statement tracing support that is both more convenient and finer-grained. For example, in a typical situation that calls for tracing we would like to see the SQL statements executed as a result of a specific transaction. While it may be difficult to extract such a subset of statements from the database logs, it is easy to achieve with ODB tracing support:
虽然这些信息通常可以从数据库日志中获得,但ODB提供了一种应用程序端SQL语句跟踪支持,这种支持更方便、粒度更细。例如,在需要跟踪的典型情况下,我们希望看到SQL语句作为特定事务的结果执行。虽然从数据库日志中提取这样的语句子集可能很困难,但使用ODB跟踪支持很容易实现:
transaction t (db.begin ());
t.tracer (stderr_tracer);
...
t.commit ();
ODB allows us to specify a tracer on the database, connection, and transaction levels. If specified for the database, then all the statements executed on this database will be traced. On the other hand, if a tracer is specified for the connection, then only the SQL statements executed on this connection will be traced. Similarly, a tracer specified for a transaction will only show statements that are executed as part of this transaction. All three classes (odb::database
, odb::connection
, and odb::transaction
) provide the identical tracing API:
ODB允许我们在数据库、连接和事务级别上指定跟踪程序。如果为数据库指定,则将跟踪在此数据库上执行的所有语句。另一方面,如果为连接指定了跟踪程序,则只跟踪在此连接上执行的SQL语句。类似地,为事务指定的跟踪程序将仅显示作为该事务一部分执行的语句。所有三个类(odb::database、odb::connection和odb::transaction)都提供相同的跟踪API:
void tracer (odb::tracer&); void tracer (odb::tracer*); odb::tracer* tracer () const;
The first two tracer()
functions allow us to set the tracer object with the second one allowing us to clear the current tracer by passing a NULL
pointer. The last tracer()
function allows us to get the current tracer object. It returns a NULL
pointer if there is no tracer in effect. Note that the tracing API does not manage the lifetime of the tracer object. The tracer should be valid for as long as it is being used. Furthermore, the tracing API is not thread-safe. Trying to set a tracer from multiple threads simultaneously will result in undefined behavior.
前两个tracer()函数允许我们设置tracer对象,第二个函数允许我们通过传递空指针清除当前跟踪器。最后一个tracer()函数允许我们获取当前跟踪对象。如果没有有效的跟踪程序,则返回空指针。请注意,跟踪API不管理跟踪对象的生存期。跟踪器在使用期间应一直有效。此外,跟踪API不是线程安全的。尝试同时从多个线程设置跟踪程序将导致未定义的行为。
The odb::tracer
class defines a callback interface that can be used to create custom tracer implementations. The odb::stderr_tracer
and odb::stderr_full_tracer
are built-in tracer implementations provided by the ODB runtime. They both print SQL statements being executed to the standard error stream. The full tracer, in addition to tracing statement executions, also traces their preparations and deallocations. One situation where the full tracer can be particularly useful is if a statement (for example a custom query) contains a syntax error. In this case the error will be detected during preparation and, as a result, the statement will never be executed. The only way to see such a statement is by using the full tracing.
跟踪程序类定义了一个回调接口,可用于创建自定义跟踪程序实现。odb::stderr_tracer
和odb::stderr_full_tracer
是odb运行时提供的内置跟踪器实现。它们都将正在执行的SQL语句打印到标准错误流中。完整跟踪程序除了跟踪语句执行外,还跟踪它们的准备和释放。如果语句(例如自定义查询)包含语法错误,则完整跟踪程序可能特别有用。在这种情况下,将在准备过程中检测到错误,因此,该语句将永远不会执行。查看此类语句的唯一方法是使用完整跟踪。
The odb::tracer
class is defined in the <odb/tracer.hxx>
header file which you will need to include in order to make this class available in your application. The odb::tracer
interface provided the following callback functions:
在<odb/tracer.hxx>头文件中定义了odb::tracer类,您需要将其包括在内,以使该类在应用程序中可用。odb::tracer接口提供了以下回调函数:
namespace odb { class tracer { public: virtual void prepare (connection&, const statement&); virtual void execute (connection&, const statement&); virtual void execute (connection&, const char* statement) = 0; virtual void deallocate (connection&, const statement&); }; }
The prepare()
and deallocate()
functions are called when a prepared statement is created and destroyed, respectively. The first execute()
function is called when a prepared statement is executed while the second one is called when a normal statement is executed. The default implementations for the prepare()
and deallocate()
functions do nothing while the first execute()
function calls the second one passing the statement text as the second argument. As a result, if all you are interested in are the SQL statements being executed, then you only need to override the second execute()
function.
分别在创建和销毁准备好的语句时调用prepare()和deallocate()函数。第一个execute()函数在执行准备好的语句时调用,第二个函数在执行普通语句时调用。prepare()和deallocate()函数的默认实现不执行任何操作,而第一个execute()函数调用第二个函数,并将语句文本作为第二个参数传递。因此,如果您感兴趣的只是正在执行的SQL语句,那么您只需要重写第二个execute()函数。
In addition to the common odb::tracer
interface, each database runtime provides a database-specific version as odb::<database>::tracer
. It has exactly the same interface as the common version except that the connection
and statement
types are database-specific, which gives us access to additional, database-specific information.
除了常见的odb::tracer接口外,每个数据库运行时还提供一个特定于数据库的版本,即odb::<database>::tracer。它的接口与普通版本完全相同,只是连接和语句类型是特定于数据库的,这使我们能够访问其他特定于数据库的信息。
As an example, consider a more elaborate, PostgreSQL-specific tracer implementation. Here we rely on the fact that the PostgreSQL ODB runtime uses names to identify prepared statements and this information can be obtained from the odb::pgsql::statement
object:
作为一个例子,考虑一个更详细的、PostgreSQL特定的示踪剂实现。这里我们依赖于这样一个事实,即PostgreSQL ODB运行时使用名称来标识准备好的语句,并且可以从ODB::pgsql::statement对象获取此信息:
#include <odb/pgsql/tracer.hxx> #include <odb/pgsql/database.hxx> #include <odb/pgsql/connection.hxx> #include <odb/pgsql/statement.hxx> class pgsql_tracer: public odb::pgsql::tracer { virtual void prepare (odb::pgsql::connection& c, const odb::pgsql::statement& s) { cerr << c.database ().db () << ": PREPARE " << s.name () << " AS " << s.text () << endl; } virtual void execute (odb::pgsql::connection& c, const odb::pgsql::statement& s) { cerr << c.database ().db () << ": EXECUTE " << s.name () << endl; } virtual void execute (odb::pgsql::connection& c, const char* statement) { cerr << c.database ().db () << ": " << statement << endl; } virtual void deallocate (odb::pgsql::connection& c, const odb::pgsql::statement& s) { cerr << c.database ().db () << ": DEALLOCATE " << s.name () << endl; } };
Note also that you can only set a database-specific tracer object using a database-specific database instance, for example:
还要注意,只能使用特定于数据库的数据库实例设置特定于数据库的跟踪对象,例如:
pgsql_tracer tracer; odb::database& db = ...; db.tracer (tracer); // Compile error. odb::pgsql::database& db = ...; db.tracer (tracer); // Ok.
3.14 ODB Exceptions ODB异常
In the previous sections we have already mentioned some of the exceptions that can be thrown by the database functions. In this section we will discuss the ODB exception hierarchy and document all the exceptions that can be thrown by the common ODB runtime.
在前面的部分中,我们已经提到了数据库函数可能引发的一些异常。在本节中,我们将讨论ODB异常层次结构,并记录公共ODB运行时可以引发的所有异常。
The root of the ODB exception hierarchy is the abstract odb::exception
class. This class derives from std::exception
and has the following interface:
ODB异常层次结构的根是抽象的ODB::exception类。此类派生自std::exception,并具有以下接口:
namespace odb { struct exception: std::exception { virtual const char* what () const throw () = 0; }; }
Catching this exception guarantees that we will catch all the exceptions thrown by ODB. The what()
function returns a human-readable description of the condition that triggered the exception.
捕获此异常保证我们将捕获ODB引发的所有异常。函数的作用是:返回触发异常的条件的可读描述。
The concrete exceptions that can be thrown by ODB are presented in the following listing:
ODB可以引发的具体异常如下所示:
namespace odb { struct null_pointer: exception { virtual const char* what () const throw (); }; // Transaction exceptions. // struct already_in_transaction: exception { virtual const char* what () const throw (); }; struct not_in_transaction: exception { virtual const char* what () const throw (); }; struct transaction_already_finalized: exception { virtual const char* what () const throw (); }; // Session exceptions. // struct already_in_session: exception { virtual const char* what () const throw (); }; struct not_in_session: exception { virtual const char* what () const throw (); }; struct session_required: exception { virtual const char* what () const throw (); }; // Database operations exceptions. // struct recoverable: exception { }; struct connection_lost: recoverable { virtual const char* what () const throw (); }; struct timeout: recoverable { virtual const char* what () const throw (); }; struct deadlock: recoverable { virtual const char* what () const throw (); }; struct object_not_persistent: exception { virtual const char* what () const throw (); }; struct object_already_persistent: exception { virtual const char* what () const throw (); }; struct object_changed: exception { virtual const char* what () const throw (); }; struct result_not_cached: exception { virtual const char* what () const throw (); }; struct database_exception: exception { }; // Polymorphism support exceptions. // struct abstract_class: exception { virtual const char* what () const throw (); }; struct no_type_info: exception { virtual const char* what () const throw (); }; // Prepared query support exceptions. // struct prepared_already_cached: exception { const char* name () const; virtual const char* what () const throw (); }; struct prepared_type_mismatch: exception { const char* name () const; virtual const char* what () const throw (); }; // Schema catalog exceptions. // struct unknown_schema: exception { const std::string& name () const; virtual const char* what () const throw (); }; struct unknown_schema_version: exception { schema_version version () const; virtual const char* what () const throw (); }; // Section exceptions. // struct section_not_loaded: exception { virtual const char* what () const throw (); }; struct section_not_in_object: exception { virtual const char* what () const throw (); }; // Bulk operation exceptions. // struct multiple_exceptions: exception { ... virtual const char* what () const throw (); }; }
The null_pointer
exception is thrown when a pointer to a persistent object declared non-NULL
with the db not_null
or db value_not_null
pragma has the NULL
value. See Chapter 6, "Relationships" for details.
当指向声明为非null且带有db not_null或db value not_null pragma的持久对象的指针具有null值时,将引发null_pointer
异常。详见第6章“关系”。
The next three exceptions (already_in_transaction
, not_in_transaction
, transaction_already_finalized
) are thrown by the odb::transaction
class and are discussed in Section 3.5, "Transactions".
接下来的三个异常(已在事务中、未在事务中、事务已完成)由odb::transaction类抛出,并在第3.5节“事务”中讨论。
The next two exceptions (already_in_session
, and not_in_session
) are thrown by the odb::session
class and are discussed in Chapter 11, "Session".
接下来的两个异常(已在会话中,而不是会话中)由odb::session类引发,在第11章“会话”中讨论。
The session_required
exception is thrown when ODB detects that correctly loading a bidirectional object relationship requires a session but one is not used. See Section 6.2, "Bidirectional Relationships" for more information on this exception.
当ODB检测到正确加载双向对象关系需要会话但未使用会话时,将引发session_required异常。有关此异常的更多信息,请参见第6.2节“双向关系”。
The recoverable
exception serves as a common base for all the recoverable exceptions, which are: connection_lost
, timeout
, and deadlock
. The connection_lost
exception is thrown when a connection to the database is lost. Similarly, the timeout
exception is thrown if one of the database operations or the whole transaction has timed out. The deadlock
exception is thrown when a transaction deadlock is detected by the database system. These exceptions can be thrown by any database function. See Section 3.7, "Error Handling and Recovery" for details.
可恢复异常作为所有可恢复异常的公共基础,这些异常包括:连接丢失、超时和死锁。当与数据库的连接丢失时,将引发connection_lost异常。类似地,如果其中一个数据库操作或整个事务超时,将引发超时异常。当数据库系统检测到事务死锁时,将引发死锁异常。这些异常可以由任何数据库函数引发。有关详细信息,请参见第3.7节“错误处理和恢复”。
The object_already_persistent
exception is thrown by the persist()
database function. See Section 3.8, "Making Objects Persistent" for details.
persist()数据库函数引发object_already_persistent
异常。有关详细信息,请参见第3.8节“使对象持久化”。
The object_not_persistent
exception is thrown by the load()
, update()
, and erase()
database functions. Refer to Section 3.9, "Loading Persistent Objects", Section 3.10, "Updating Persistent Objects", and Section 3.11, "Deleting Persistent Objects" for more information.
load()、update()和erase()数据库函数引发bject_not_persistent
异常。有关更多信息,请参阅第3.9节“加载持久对象”、第3.10节“更新持久对象”和第3.11节“删除持久对象”。
The object_changed
exception is thrown by the update()
database function and certain erase()
database functions when operating on objects with the optimistic concurrency model. See Chapter 12, "Optimistic Concurrency" for details.
当对具有乐观并发模型的对象进行操作时,update()数据库函数和某些erase()数据库函数会引发object_changed异常。有关详细信息,请参见第12章“乐观并发”。
The result_not_cached
exception is thrown by the query result class. Refer to Section 4.4, "Query Result" for details.
查询结果类引发result_not_cached
异常。详见第4.4节“查询结果”。
The database_exception
exception is a base class for all database system-specific exceptions that are thrown by the database system-specific runtime library. Refer to Part II, "Database Systems" for more information.
database_exception exception是数据库系统特定运行库引发的所有数据库系统特定异常的基类。有关更多信息,请参阅第二部分“数据库系统”。
The abstract_class
exception is thrown by the database functions when we attempt to persist, update, load, or erase an instance of a polymorphic abstract class. For more information on abstract classes, refer to Section 14.1.3, "abstract
".
当我们试图持久化、更新、加载或删除多态抽象类的实例时,数据库函数会引发abstract_class
异常。有关抽象类的更多信息,请参阅第14.1节。3,“摘要”。
The no_type_info
exception is thrown by the database functions when we attempt to persist, update, load, or erase an instance of a polymorphic class for which no type information is present in the application. This normally means that the generated database support code for this class has not been linked (or dynamically loaded) into the application or the discriminator value has not been mapped to a persistent class. For more information on polymorphism support, refer to Section 8.2, "Polymorphism Inheritance".
当我们试图持久化、更新、加载或擦除应用程序中不存在类型信息的多态类的实例时,数据库函数会引发no_type_info异常。这通常意味着为此类生成的数据库支持代码尚未链接(或动态加载)到应用程序中,或者鉴别器值尚未映射到持久类。有关多态性支持的更多信息,请参阅第8.2节“多态性继承”。
The prepared_already_cached
exception is thrown by the cache_query()
function if a prepared query with the specified name is already cached. The prepared_type_mismatch
exception is thrown by the lookup_query()
function if the specified prepared query object type or parameters type does not match the one in the cache. Refer to Section 4.5, "Prepared Queries" for details.
如果已缓存具有指定名称的已准备查询,则cache_query()
函数将引发prepared_already_cached
异常。如果指定的准备好的查询对象类型或参数类型与缓存中的类型不匹配,则lookup_query()
函数将引发prepared_type_mismatch
异常。有关详细信息,请参阅第4.5节“准备好的查询”。
The unknown_schema
exception is thrown by the odb::schema_catalog
class if a schema with the specified name is not found. Refer to Section 3.4, "Database" for details. The unknown_schema_version
exception is thrown by the schema_catalog
functions that deal with database schema evolution if the passed version is unknow. Refer to Chapter 13, "Database Schema Evolution" for details.
如果找不到具有指定名称的架构,odb::schema_catalog
类将引发unknown_schema
异常。有关详细信息,请参阅第3.4节“数据库”。如果传递的版本未知,则处理数据库模式演变的模式目录函数会引发 unknown_schema_version
异常。有关详细信息,请参阅第13章“数据库模式演变”。
The section_not_loaded
exception is thrown if we attempt to update an object section that hasn't been loaded. The section_not_in_object
exception is thrown if the section instance being loaded or updated does not belong to the corresponding object. See Chapter 9, "Sections" for more information on these exceptions.
如果我们试图更新尚未加载的对象节,将section_not_loaded
异常。如果正在加载或更新的节实例不属于相应的对象,则会引发ection_not_in_object
异常。有关这些例外情况的更多信息,请参见第9章“章节”。
The multiple_exceptions
exception is thrown by the bulk API functions. Refer to Section 15.3, "Bulk Database Operations" for details.
multiple_exceptions
_异常由批量API函数引发。有关详细信息,请参阅第15.3节“批量数据库操作”。
The odb::exception
class is defined in the <odb/exception.hxx>
header file. All the concrete ODB exceptions are defined in <odb/exceptions.hxx>
which also includes <odb/exception.hxx>
. Normally you don't need to include either of these two headers because they are automatically included by <odb/database.hxx>
. However, if the source file that handles ODB exceptions does not include <odb/database.hxx>
, then you will need to explicitly include one of these headers.
异常类在<odb/exception.hxx>头文件中定义。所有具体的ODB异常都在<ODB/exceptions.hxx>中定义。也包括<odb/exception.hxx>
。通常,您不需要包含这两个头中的任何一个,因为它们是由<odb/database.hxx>自动包含的。但是,如果处理ODB异常的源文件不包含<ODB/database.hxx>,则需要显式包含其中一个标头。