11 会话
会话是应用程序的工作单元,可能包含多个数据库事务。在这个版本的ODB中,会话只是一个对象缓存。在未来的版本中,它可能会提供额外的功能,例如延迟数据库操作和自动对象状态更改跟踪。如第11.2节“自定义会话”稍后所述,也可以提供提供这些或其他功能的自定义会话实现。
会话支持是可选的,可以使用
db Session
pragma根据每个对象启用或禁用,例如:
#pragma db object session
class person
{
...
};
- 我们还可以在命名空间级别启用或禁用一组对象的会话支持:
#pragma db namespace session
namespace accounting
{
#pragma db object // Session support is enabled.
class employee
{
...
};
#pragma db object session(false) // Session support is disabled.
class employer
{
...
};
}
- 最后,我们可以传递
--generate session
ODB编译器选项,以默认启用会话支持。使用此选项,将为所有持久类启用会话支持,但使用db session
显式禁用的类除外。具有相同效果的此方法的另一种方法是启用全局命名空间的会话支持:
#pragma db namespace() session
- 应用程序中的每个执行线程一次只能有一个活动会话。会话是通过创建
odb::session
类的实例来启动的,当该实例被销毁时,会话会自动终止。您需要包含<odb/session.hxx>
头文件,以便在您的应用程序中使用此类。例如:
#include <odb/database.hxx>
#include <odb/session.hxx>
#include <odb/transaction.hxx>
using namespace odb::core;
{
session s;
// First transaction.
//
{
transaction t (db.begin ());
...
t.commit ();
}
// Second transaction.
//
{
transaction t (db.begin ());
...
t.commit ();
}
// Session 's' is terminated here.
}
session
类具有以下接口:
namespace odb
{
class session
{
public:
session (bool make_current = true);
~session ();
// Copying or assignment of sessions is not supported.
//
private:
session (const session&);
session& operator= (const session&);
// Current session interface.
//
public:
static session&
current ();
static bool
has_current ();
static void
current (session&);
static void
reset_current ();
static session*
current_pointer ();
static void
current_pointer (session*);
// Object cache interface.
//
public:
template <typename T>
struct cache_position {...};
template <typename T>
cache_position<T>
cache_insert (database&,
const object_traits<T>::id_type&,
const object_traits<T>::pointer_type&);
template <typename T>
object_traits<T>::pointer_type
cache_find (database&, const object_traits<T>::id_type&) const;
template <typename T>
void
cache_erase (const cache_position<T>&);
template <typename T>
void
cache_erase (database&, const object_traits<T>::id_type&);
};
}
会话构造函数创建一个新会话,如果
make_current
参数为true
,则将其设置为该线程的当前会话。如果我们试图让一个会话成为当前会话,而这个线程已经有另一个会话生效,那么构造函数会抛出odb::already_in_session
异常。如果此会话是当前会话,析构函数将清除此线程的当前会话。静态
current()
访问器返回此线程的当前活动会话。如果没有活动会话,此函数将抛出odb::not_in_session
异常。我们可以使用has_current()
静态函数检查此线程中是否有有效的会话。static
current()
修饰符允许我们为此线程设置当前会话。reset_current()
静态函数清除当前会话。这两个函数允许更高级的用例,例如在同一线程上复用两个或多个会话。静态
current_pointer()
重载函数提供了相同的功能,但使用了指针。具体来说,current_pointer()
访问器可用于测试是否存在当前会话,并通过一次调用获得指向所有会话的指针。我们通常不直接使用对象缓存接口。然而,在某些情况下,它可能很有用,例如,找出对象是否已经加载。请注意,在调用
cache_insert()
、cache_find()
或cache_erase()
的第二个版本时,您需要显式指定模板参数(对象类型)。也可以直接访问底层缓存数据结构。例如,如果您想迭代缓存中存储的对象,这可能很有用。有关此直接访问的更多详细信息,请参阅ODB运行时头文件。
11.1 对象缓存
- 会话是一个对象缓存。每次通过调用
database::persist()
函数(第3.8节,“使对象持久化”)使启用会话的对象持久化,通过调用database::load()
或database::load()
函数加载(第3.9节,“加载持久对象”),或通过迭代查询结果加载(第4.4节,“查询结果”),指向持久对象的指针(以规范对象指针的形式)(第3.3节,“对象和视图指针”)都会存储在会话中。只要会话有效,任何后续加载同一对象的调用都将返回缓存的实例。当使用database::erase()
函数从数据库中删除对象的状态时(第3.11节,“删除持久对象”),缓存的对象指针将从会话中删除。例如:
shared_ptr<person> p (new person ("John", "Doe"));
session s;
transaction t (db.begin ());
unsigned long id (db.persist (p)); // p is cached in s.
shared_ptr<person> p1 (db.load<person> (id)); // p1 same as p.
t.commit ();
每个对象的缓存策略取决于对象指针的类型(第6.5节,“使用自定义智能指针”)。具有唯一指针(如
std::auto_ptr
或std::unique_ptr
)作为对象指针的对象永远不会被缓存,因为不可能有两个这样的指针指向同一个对象。当一个对象通过指针持久化或作为动态分配的实例加载时,具有原始指针和共享指针作为对象指针的对象会被缓存。如果一个对象被持久化为引用或加载到预分配的实例中,则只有当其对象指针是原始指针时,该对象才会被缓存。还要注意,当我们将一个对象持久化为常量引用或常量指针时,会话会将这样的对象缓存为
unrestricted
(非const
)。如果被持久化的对象实际上是作为const
创建的,并且后来在会话缓存中被发现并用作非const
,这可能会导致未定义的行为。因此,在使用会话时,建议将所有持久对象创建为非const
实例。以下代码片段说明了这一点:
void save (database& db, shared_ptr<const person> p)
{
transaction t (db.begin ());
db.persist (p); // Persisted as const pointer.
t.commit ();
}
session s;
shared_ptr<const person> p1 (new const person ("John", "Doe"));
unsigned long id1 (save (db, p1)); // p1 is cached in s as non-const.
{
transaction t (db.begin ());
shared_ptr<person> p (db.load<person> (id1)); // p == p1
p->age (30); // Undefined behavior since p1 was created const.
t.commit ();
}
shared_ptr<const person> p2 (new person ("Jane", "Doe"));
unsigned long id2 (save (db, p2)); // p2 is cached in s as non-const.
{
transaction t (db.begin ());
shared_ptr<person> p (db.load<person> (id2)); // p == p2
p->age (30); // Ok, since p2 was not created const.
t.commit ();
}
11.2 自定义会话
ODB可以使用自定义会话实现,而不是默认的
odb::session
。应用程序提供自己的会话可能有多种原因。例如,应用程序可能已经包含对象缓存或注册表的概念,ODB可以重复使用。自定义会话还可以提供其他功能,例如自动更改跟踪、延迟数据库操作或对象驱逐。最后,odb::session
使用的每线程会话方法可能并不适用于所有应用程序。例如,有些可能需要一个可以在多个线程之间共享的线程安全会话。有关通过保留对象的原始副本来实现自动更改跟踪的自定义会话的示例,请参阅odb-tests
中的common/session/custom
测试。要使用自定义会话,我们需要使用
--session-type
ODB编译器命令行选项指定其类型。我们还需要将其定义包含在生成的头文件中。这可以通过--hxx-prologue
选项来实现。例如,如果我们的自定义会话名为app::session
,并且在app/session.hxx
头文件中定义,那么相应的ODB编译器选项如下:
odb --hxx-prologue "#include \"app/session.hxx\"" \
--session-type ::app::session ...
- 自定义会话应提供以下接口:
class custom_session
{
public:
template <typename T>
struct cache_position
{
...
};
// Cache management functions.
//
template <typename T>
static cache_position<T>
_cache_insert (odb::database&,
const typename odb::object_traits<T>::id_type&,
const typename odb::object_traits<T>::pointer_type&);
template <typename T>
static typename odb::object_traits<T>::pointer_type
_cache_find (odb::database&,
const typename odb::object_traits<T>::id_type&);
template <typename T>
static void
_cache_erase (const cache_position<T>&);
// Notification functions.
//
template <typename T>
static void
_cache_persist (const cache_position<T>&);
template <typename T>
static void
_cache_load (const cache_position<T>&);
template <typename T>
static void
_cache_update (odb::database&, const T& obj);
template <typename T>
static void
_cache_erase (odb::database&,
const typename odb::object_traits<T>::id_type&);
};
cache_position
类模板表示插入对象在缓存中的位置。它应该是默认的、可复制的、可分配的。默认构造函数应创建一个特殊的空/NULL
位置。调用任何具有空/NULL
位置的缓存管理或通知函数都将被忽略。_cache_insert()
函数应将对象添加到对象缓存中并返回其位置。_cache_find()
函数在给定id的对象缓存中查找对象。如果找不到对象,则返回NULL指针。_cache_erase()
缓存管理函数应将对象从缓存中删除。如果导致插入对象的数据库操作(例如加载)失败,则会调用它。另请注意,插入后,对象状态为未定义。您只能从下面讨论的通知函数之一访问对象状态(例如,复制或清除标记)。通知函数分别在对象被持久化、加载、更新或擦除后调用。如果您的会话实现不需要某些通知,您仍然需要提供它们的功能,但是,您可以将它们的实现留空。
还要注意,所有缓存管理和通知功能都是静态的。这样做是为了允许对当前会话进行自定义。通常,非空实现执行的第一步是查找当前会话。