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

11 会话

  • 会话是应用程序的工作单元,可能包含多个数据库事务。在这个版本的ODB中,会话只是一个对象缓存。在未来的版本中,它可能会提供额外的功能,例如延迟数据库操作和自动对象状态更改跟踪。如第11.2节"自定义会话"稍后所述,也可以提供提供这些或其他功能的自定义会话实现。

  • 会话支持是可选的,可以使用db Session pragma根据每个对象启用或禁用,例如:

cpp 复制代码
#pragma db object session
class person
{
  ...
};
  • 我们还可以在命名空间级别启用或禁用一组对象的会话支持:
cpp 复制代码
#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显式禁用的类除外。具有相同效果的此方法的另一种方法是启用全局命名空间的会话支持:
cpp 复制代码
#pragma db namespace() session
  • 应用程序中的每个执行线程一次只能有一个活动会话。会话是通过创建odb::session 类的实例来启动的,当该实例被销毁时,会话会自动终止。您需要包含<odb/session.hxx>头文件,以便在您的应用程序中使用此类。例如:
cpp 复制代码
#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 类具有以下接口:
cpp 复制代码
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 对象缓存

cpp 复制代码
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_ptrstd::unique_ptr)作为对象指针的对象永远不会被缓存,因为不可能有两个这样的指针指向同一个对象。当一个对象通过指针持久化或作为动态分配的实例加载时,具有原始指针和共享指针作为对象指针的对象会被缓存。如果一个对象被持久化为引用或加载到预分配的实例中,则只有当其对象指针是原始指针时,该对象才会被缓存。

  • 还要注意,当我们将一个对象持久化为常量引用或常量指针时,会话会将这样的对象缓存为unrestricted(非const)。如果被持久化的对象实际上是作为const创建的,并且后来在会话缓存中被发现并用作非const,这可能会导致未定义的行为。因此,在使用会话时,建议将所有持久对象创建为非const实例。以下代码片段说明了这一点:

cpp 复制代码
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编译器选项如下:

cpp 复制代码
odb --hxx-prologue "#include \"app/session.hxx\"" \
--session-type ::app::session ...
  • 自定义会话应提供以下接口:
cpp 复制代码
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()缓存管理函数应将对象从缓存中删除。如果导致插入对象的数据库操作(例如加载)失败,则会调用它。另请注意,插入后,对象状态为未定义。您只能从下面讨论的通知函数之一访问对象状态(例如,复制或清除标记)。

  • 通知函数分别在对象被持久化、加载、更新或擦除后调用。如果您的会话实现不需要某些通知,您仍然需要提供它们的功能,但是,您可以将它们的实现留空。

  • 还要注意,所有缓存管理和通知功能都是静态的。这样做是为了允许对当前会话进行自定义。通常,非空实现执行的第一步是查找当前会话。

相关推荐
做梦敲代码18 分钟前
达梦数据库-读写分离集群部署
数据库·达梦数据库
奶香臭豆腐27 分钟前
C++ —— 模板类具体化
开发语言·c++·学习
小蜗牛慢慢爬行1 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
hanbarger1 小时前
nosql,Redis,minio,elasticsearch
数据库·redis·nosql
波音彬要多做1 小时前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
微服务 spring cloud1 小时前
配置PostgreSQL用于集成测试的步骤
数据库·postgresql·集成测试
先睡1 小时前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
HelloGitHub1 小时前
跟着 8.6k Star 的开源数据库,搞 RAG!
开源·github
弗罗里达老大爷1 小时前
Redis
数据库·redis·缓存