自学内容网 自学内容网

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

4 查询数据库

如果我们不知道我们正在寻找的对象的标识符,我们可以使用查询在数据库中搜索符合特定条件的对象。ODB查询功能是可选的,我们需要使用--generate-query ODB编译器选项显式请求生成必要的数据库支持代码。

  • ODB提供了一个灵活的查询API,它从数据库系统查询语言(如SQL)提供了两个不同的抽象级别。在较高的层次上,我们介绍了一种易于使用但功能强大的面向对象查询语言,称为ODB查询语言。这种查询语言是模仿C++的,并集成到C++中,使我们能够编写外观和感觉都像普通C++的富有表现力和安全的查询。我们在介绍章节中已经看到了这些查询的示例。下面是另一个更有趣的例子:
  typedef odb::query<person> query;
  typedef odb::result<person> result;

  unsigned short age;
  query q (query::first == "John" && query::age < query::_ref (age));

  for (age = 10; age < 100; age += 10)
  {
    result r (db.query<person> (q));
    ...
  }
  • 在底层,可以使用数据库系统本机查询语言(如SQL SELECT语句中的WHERE谓词)将查询编写为谓词。这种语言将被称为本机查询语言。在这个级别,ODB仍然负责将查询参数从C++转换为数据库系统格式。下面是使用SQL作为本机查询语言对上述示例的重新实现:
  query q ("first = 'John' AND age = " + query::_ref (age));
  • 请注意,在这个级别,我们失去了查询表达式的静态类型。例如,如果我们写这样的东西:
query q (query::first == 123 && query::agee < query::_ref (age));
  • 在C++编译过程中,我们会遇到两个错误。第一个选项表示我们无法将query::first与整数进行比较,第二个选项将选择query::agee中的拼写错误。另一方面,如果我们写这样的东西:
 query q ("first = 123 AND agee = " + query::_ref (age));
  • 它编译良好,只有在数据库系统执行时才会触发错误。

  • 我们还可以将这两种查询语言组合在一个查询中,例如:

query q ("first = 'John' AND" + (query::age < query::_ref (age)));

4.1 ODB查询语言

  • ODB查询是一个表达式,它告诉数据库系统任何给定对象是否符合所需的条件。因此,查询表达式的计算结果总是true或false。在更高的层次上,表达式由其他表达式和逻辑运算符组成,如&& (AND), || (OR)和! (NOT)。例如:
  typedef odb::query<person> query;

  query q (query::first == "John" || query::age == 31);
  • 每个查询表达式的核心都是涉及一个或多个对象成员、值或参数的简单表达式。要引用对象成员,我们使用上面的query::first等表达式。查询类中的成员名称是通过删除常见的成员名称装饰(如前导和尾随下划线、m_前缀等)从对象类中的数据成员名称派生而来的。

  • 在一个简单的表达式中,可以使用许多预定义的运算符和函数将对象成员与值、参数或另一个成员进行比较。下表概述了可用表达式:
    在这里插入图片描述

  • in()函数最多接受五个参数。如果需要比较五个以上的值,请使用in_range()函数。此函数接受一对标准C++迭代器,并比较从开始位置(包括开始位置)到结束位置(不包括结束位置)的所有值。以下代码片段展示了如何使用这些函数:

  std::vector<string> names;

  names.push_back ("John");
  names.push_back ("Jack");
  names.push_back ("Jane");

  query q1 (query::first.in ("John", "Jack", "Jane"));
  query q2 (query::first.in_range (names.begin (), names.end ()));
  • 请注意,like()函数不执行SQL like运算符的数据库系统特定扩展的任何转换。因此,如果您希望您的应用程序在各种数据库系统之间可移植,请将模式中使用的特殊字符限制为%(匹配零个或多个字符)和_(仅匹配一个字符)。也可以将转义字符指定为like()函数的第二个参数。然后,此字符可用于转义模式中的特殊字符(%_)。例如,以下查询将匹配由下划线分隔的任意两个字符:
  query q (query::name.like ("_!__", "!"));
  • 查询表达式中的运算符优先级与等效的C++运算符相同。我们可以使用括号来确保表达式按照所需的顺序计算。例如:
  query q ((query::first == "John" || query::first == "Jane") &&
           query::age < 31);

4.2 参数绑定

  • odb::query类的实例封装了关于查询的两部分信息:查询表达式和查询参数。参数可以通过值或引用绑定到C++变量。

  • 如果参数按值绑定,则在查询构造时将此参数的值从C++变量复制到查询实例。另一方面,如果参数是通过引用绑定的,则查询实例会存储对绑定变量的引用。参数的实际值仅在查询执行时提取。例如,考虑以下两个查询:

  string name ("John");

  query q1 (query::first == query::_val (name));
  query q2 (query::first == query::_ref (name));

  name = "Jane";

  db.query<person> (q1); // Find John.
  db.query<person> (q2); // Find Jane.
  • odb::query类提供了两个特殊函数_val()_ref(),它们允许我们分别通过值或引用绑定参数。在ODB查询语言中,如果未显式指定绑定,则默认使用值语义。在本机查询语言中,绑定必须始终明确指定。例如:
  query q1 (query::age < age);                // By value.
  query q2 (query::age < query::_val (age));  // By value.
  query q3 (query::age < query::_ref (age));  // By reference.

  query q4 ("age < " + age);                  // Error.
  query q5 ("age < " + query::_val (age));    // By value.
  query q6 ("age < " + query::_ref (age));    // By reference.
  • 仅具有按值参数的查询不依赖于任何其他变量,并且一旦构造就可以自给自足。在执行查询之前,具有一个或多个引用参数的查询取决于绑定的变量。如果一个这样的变量超出范围,我们执行查询,则行为未定义。

4.3 执行查询

  • 一旦我们准备好查询实例并初始化了引用参数,我们就可以使用database::query()函数模板执行查询。它有两个重载版本:
  template <typename T>
  result<T>
  query (bool cache = true);

  template <typename T>
  result<T>
  query (const odb::query<T>&, bool cache = true);
  • 第一个query()函数用于返回数据库中存储的给定类型的所有持久对象。第二个函数使用传递的查询实例只返回与查询条件匹配的对象。cache参数确定对象的状态是应该缓存在应用程序的内存中,还是应该在迭代结果的过程中由数据库系统逐一返回。下一节将详细讨论结果缓存。

调用query()函数时,我们必须显式指定要查询的对象类型。例如:

  typedef odb::query<person> query;
  typedef odb::result<person> result;

  result all (db.query<person> ());
  result johns (db.query<person> (query::first == "John"));
  • 请注意,在执行之前不需要显式创建命名查询变量。例如,以下两个查询是等效的:
  query q (query::first == "John");

  result r1 (db.query<person> (q));
  result r1 (db.query<person> (query::first == "John"));
  • 通常,如果我们计划多次运行同一个查询,我们会创建一个命名查询实例,并对那些只执行一次的查询使用内联版本(另见第4.5节“准备好的查询”,了解多次重新执行同一查询的最佳方法)。没有任何引用参数的命名查询实例是不可变的,可以在多个线程之间共享,而无需同步。另一方面,具有引用参数的查询实例每次执行时都会被修改。如果这样的查询在多个线程之间共享,那么对该查询实例的访问必须从执行点同步,直到对结果的迭代完成。

  • 还可以通过使用逻辑运算符组合其他查询来创建查询。例如:

result
find_minors (database& db, const query& name_query)
{
  return db.query<person> (name_query && query::age < 18);
}

result r (find_minors (db, query::first == "John"));
  • 执行查询的结果是零个、一个或多个符合查询条件的对象。query()函数将此结果作为odb::result类模板的实例返回,该模板提供了一个流式接口,将在下一节中详细讨论。

  • 在我们知道查询最多产生一个元素的情况下,我们可以使用database::query_one()database::query_value()快捷方式函数,例如:

  typedef odb::query<person> query;

  auto_ptr<person> p (
    db.query_one<person> (
      query::email == "jon@example.com"));
  • 快捷查询函数具有以下特征:
  template <typename T>
  typename object_traits<T>::pointer_type
  query_one ();

  template <typename T>
  bool
  query_one (T&);

  template <typename T>
  T
  query_value ();

  template <typename T>
  typename object_traits<T>::pointer_type
  query_one (const odb::query<T>&);

  template <typename T>
  bool
  query_one (const odb::query<T>&, T&);

  template <typename T>
  T
  query_value (const odb::query<T>&);
  • query()类似,前三个函数用于返回数据库中存储的给定类型的唯一持久对象。后三个版本使用传递的查询实例只返回与查询条件匹配的对象。

  • database::find() 函数(第3.9节,“加载持久对象”)类似,query_one()可以在动态内存中分配对象类的新实例,也可以将对象的状态加载到现有实例中。query_value()函数按值分配并返回对象。

  • query_one()函数允许我们确定查询结果是包含零个元素还是一个元素。如果在数据库中找不到与查询条件匹配的对象,则query_one()的第一个版本返回NULL指针,而第二个版本返回--false。如果第二个版本返回false,则传递的对象保持不变。例如:

  if (unique_ptr<person> p = db.query_one<person> (
        query::email == "jon@example.com"))
  {
    ...
  }

  person p;
  if (db.query_one<person> (query::email == "jon@example.com", p))
  {
    ...
  }
  • 如果使用query_one()query_value()执行的查询返回多个元素,则这些函数会因断言而失败。此外,如果查询未返回任何元素,则query_value()也会因断言而失败。

  • 我们可以使用快捷方式函数的常见情况是,查询条件使用具有唯一约束的数据成员(最多返回一个元素;请参阅第14.7节“索引定义规范”)以及聚合查询(只返回一个元件;请参见第10章“视图”)。

4.4 查询结果

  • database::query() 函数将执行查询的结果作为odb::result类模板的实例返回,例如:
  typedef odb::query<person> query;
  typedef odb::result<person> result;

  result johns (db.query<person> (query::first == "John"));
  • 最好将odb::result的实例视为流的句柄,例如文件流。虽然我们可以复制一个结果或将一个结果分配给另一个结果,但这两个实例将引用相同的结果流。在一个实例中推进当前位置也会在另一个实例中将其推进。结果实例仅在创建它的事务中可用。在事务终止后试图操纵结果会导致未定义的行为。

  • odb::result类模板符合标准C++序列要求,并具有以下接口:

namespace odb
{
  template <typename T>
  class result
  {
  public:
    typedef odb::result_iterator<T> iterator;

  public:
    result ();

    result (const result&);

    result&
    operator= (const result&);

    void
    swap (result&)

  public:
    iterator
    begin ();

    iterator
    end ();

  public:
    void
    cache ();

    bool
    empty () const;

    std::size_t
    size () const;
  };
}
  • 默认构造函数创建一个空结果集。cache()函数将返回的对象的状态缓存在应用程序的内存中。在讨论查询执行时,我们已经提到了结果缓存。您可能还记得,除非调用者指示不这样做,否则database::query() 函数会缓存结果。cache()函数允许我们在稍后阶段缓存结果,如果它在查询执行期间尚未缓存。

  • 如果结果被缓存,则所有返回对象的数据库状态都将存储在应用程序的内存中。请注意,在结果迭代期间,实际对象仍然只在需要时实例化。它是缓存在内存中的原始数据库状态。相比之下,对于未缓存的结果,随着迭代的进行,数据库系统会一次发送一个对象的状态。

  • 在结果中有大量对象的情况下,或者如果我们只检查返回对象的一小部分,未缓存的结果可以提高应用程序和数据库系统的性能。然而,未缓存的结果有许多局限性。事务中只能有一个未缓存的结果。通过调用database::query()创建另一个结果(缓存或未缓存)将使现有的未缓存结果无效。此外,调用任何其他数据库函数,如update()erase(),也会使未缓存的结果无效。因此,未缓存的结果不能用于具有容器的对象(第5章,“容器”),因为加载容器会使未缓存结果无效。

  • 如果结果中没有对象,则empty()函数返回true,否则返回false。只能对缓存的结果调用size()函数。它返回结果中的对象数量。如果我们对未缓存的结果调用此函数,则会抛出odb::result_not_cached异常。

  • 为了迭代结果中的对象,我们使用begin()end()函数以及odb::result<T>::iterator类型,例如:

  result r (db.query<person> (query::first == "John"));

  for (result::iterator i (r.begin ()); i != r.end (); ++i)
  {
    ...
  }
  • 在C++11中,我们可以使用auto类型的variabe,而不是显式拼写迭代器类型,例如:
  for (auto i (r.begin ()); i != r.end (); ++i)
  {
    ...
  }
  • 基于C++11范围的for循环可用于进一步简化迭代:
  for (person& p: r)
  {
    ...
  }
  • 结果迭代器是一个输入迭代器,这意味着它只支持两个位置操作,即移动到下一个对象并确定是否已到达结果流的末尾。事实上,结果迭代器只能处于两种状态:当前位置和结束位置。如果我们有两个迭代器指向当前位置,然后我们推进其中一个,另一个也会推进。例如,这意味着存储一个指向结果流中某个感兴趣对象的迭代器,并在迭代结束后对其进行解引用,是没有意义的。相反,我们需要存储对象本身。

  • 结果迭代器具有以下解引用函数,可用于访问指向的对象:

namespace odb
{
  template <typename T>
  class result_iterator
  {
  public:
    T*
    operator-> () const;

    T&
    operator* () const;

    typename object_traits<T>::pointer_type
    load ();

    void
    load (T& x);

    typename object_traits<T>::id_type
    id ();
  };
}
  • 当我们调用*->运算符时,迭代器将在动态内存中分配对象类的新实例,从数据库状态加载其状态,并返回指向新实例的引用或指针。迭代器保持返回对象的所有权,并在后续调用这些运算符时返回相同的指针,直到它前进到下一个对象或我们调用第一个load()函数(见下文)。例如:
  result r (db.query<person> (query::first == "John"));

  for (result::iterator i (r.begin ()); i != r.end ();)
  {
    cout << i->last () << endl; // Create an object.
    person& p (*i);             // Reference to the same object.
    cout << p.age () << endl;
    ++i;                        // Free the object.
  }
  • 重载的result_iterator::load()函数类似于database::load()。第一个函数返回当前对象的动态分配实例。作为一种优化,如果迭代器由于之前对*->运算符的调用而已经拥有一个对象,那么它就会放弃这个对象的所有权并返回它。这允许我们编写这样的代码,而不必担心双重分配:
  result r (db.query<person> (query::first == "John"));

  for (result::iterator i (r.begin ()); i != r.end (); ++i)
  {
    if (i->last == "Doe")
    {
      auto_ptr p (i.load ());
      ...
    }
  }
  • 然而,请注意,由于这种优化,对*->运算符的后续load()调用会导致分配一个新对象。

  • 第二个load()函数允许我们将当前对象的状态加载到现有实例中。例如:

  result r (db.query<person> (query::first == "John"));

  person p;
  for (result::iterator i (r.begin ()); i != r.end (); ++i)
  {
    i.load (p);
    cout << p.last () << endl;
    cout << i.age () << endl;
  }
  • id()函数返回当前对象的对象id。虽然我们可以通过加载对象并获取其id来实现同样的目的,但这个函数更有效,因为它实际上并没有创建对象。当我们只需要对象的标识符时,这可能很有用。例如:
  std::set<unsigned long> set = ...; // Persons of interest.

  result r (db.query<person> (query::first == "John"));

  for (result::iterator i (r.begin ()); i != r.end (); ++i)
  {
    if (set.find (i.id ()) != set.end ()) // No object loaded.
    {
      cout << i->first () << endl; // Object loaded.
    }
  }

4.5 准备好的查询

  • 大多数现代关系数据库系统都有预处理语句的概念。准备好的语句允许我们执行一次解析SQL、准备查询执行计划等可能代价高昂的任务,然后多次执行同一查询,每次执行时可能使用不同的参数值。

  • 在ODB中,所有非查询数据库操作,如persist()load()update()等,都是根据缓存和重用的准备好的语句来实现的。虽然query()query_one()数据库操作也使用预处理语句,但默认情况下这些语句不会被缓存或重用,因为ODB不知道查询是执行多次还是只执行一次。相反,ODB提供了一种称为“准备好的查询”的机制,允许我们准备一次查询并多次执行。换句话说,ODB准备的查询是底层数据库准备语句功能的一个薄包装。

  • 在大多数情况下,ODB保护应用程序开发人员免受数据库连接管理和多线程问题的影响。然而,当涉及到准备好的查询时,需要对ODB如何管理这些方面有基本的了解。从概念上讲,odb::database类表示一个特定的数据库,即数据存储。然而,在下面,它维护着与此数据库的一个或多个连接。一个连接一次只能由一个线程使用。当我们启动一个事务时(通过调用database::begin()),事务实例会获得一个连接并保持它,直到事务被提交或回滚。在此期间,没有其他线程可以使用此连接。当事务释放连接时,它可能会被关闭或被此线程或另一个线程中的另一个事务重用。连接释放后的具体情况取决于odb::database实例使用的连接工厂。有关连接工厂的更多信息,请参阅第二部分“数据库系统”。

  • 在一个连接上准备的查询无法在另一个连接中执行。换句话说,准备好的查询与连接相关联。此限制的一个重要含义是,如果不确保两个事务使用相同的连接,我们就不能在一个事务中准备查询,然后在另一个事务中将其执行。

  • 为了启用准备好的查询功能,我们需要指定--generate prepared ODB编译器选项。如果我们计划始终准备查询,那么我们可以通过指定--miss prepared选项来禁用一次性查询执行支持。

为了准备查询,我们使用prepare_query()函数模板。此函数可以在odb::databaseodb::connection实例上调用。odb::database版本只是获取当前活动事务使用的连接,并调用相应的odb::connection版本。如果当前没有活动的事务,则此函数抛出odb::not_in_transaction异常(第3.5节,“事务”)。prepare_query()函数具有以下签名:

  template <typename T>
  prepared_query<T>
  prepare_query (const char* name, const odb::query<T>&);
  • prepare_query()函数的第一个参数是准备好的查询名称。此名称用作准备好的查询缓存(稍后讨论)的密钥,并且必须是唯一的。对于某些数据库,特别是PostgreSQL,它也被用作底层预处理语句的名称。名称“object_query”(例如“person_query”)保留给database::query() 函数执行的一次性查询。请注意,prepare_query()函数仅对此参数进行浅层复制,这意味着该名称必须在返回的prepared_query实例的生命周期内有效。

  • prepare_query()函数的第二个参数是查询条件。它与第4.3节“执行查询”中讨论的query()功能具有相同的语义。与query()类似,我们还必须明确指定要查询的对象类型。例如:

typedef odb::query<person> query;
typedef odb::prepared_query<person> prep_query;

prep_query pq (
  db.prepare_query<person> ("person-age-query", query::age > 50));
  • 执行prepare_query()函数的结果是表示准备好的查询的prepared_query实例。最好将prepared_query视为底层prepared语句的句柄。虽然我们可以复制它或将一个prepared_query分配给另一个,但这两个实例将引用相同的prepared语句。一旦引用特定预处理语句的prepared_query的最后一个实例被销毁,该语句就会被释放。prepared_query类模板具有以下接口:
namespace odb
{
  template <typename T>
  struct prepared_query
  {
    prepared_query ();

    prepared_query (const prepared_query&)
    prepared_query& operator= (const prepared_query&)

    result<T>
    execute (bool cache = true);

    typename object_traits<T>::pointer_type
    execute_one ();

    bool
    execute_one (T& object);

    T
    execute_value ();

    const char*
    name () const;

    statement&
    statement () const;

    operator unspecified_bool_type () const;
  };
}
  • 默认构造函数创建一个空的prepared_query实例,即一个不引用prepared语句的实例,因此无法执行。创建非空准备查询的唯一方法是调用上面讨论的prepare_query()函数。为了测试准备好的查询是否为空,我们可以使用布尔类型的隐式转换运算符。例如:
  prepared_query<person> pq;

  if (pq)
  {
    // Not empty.
    ...
  }
  • execute()函数执行查询并返回结果实例。cache参数指示是否应缓存结果,其语义与query()函数中的语义相同。事实上,从概念上讲,prepare_query()execute()只是一分为二的query()函数:prepare_query()接受第一个query()参数(查询条件),execute()接受第二个参数(缓存标志)。还要注意,重新执行准备好的查询会使之前的执行结果无效,无论是缓存的还是未缓存的。

  • execute_one()execute_value()函数可以用作执行查询的快捷方式,已知查询最多只能返回一个或恰好返回一个对象。这些函数中的参数和返回值与query_one()query_value()中的语义相同。与上面的execute()类似,prepare_query()execute_one/value()可以被看作是query_one/value()函数被一分为二:prepare_query()接受第一个query_one/Values()参数(查询条件),而execute_one/value()接受第二个参数(如果有的话)并返回结果。还要注意,execute_one/value()从不缓存其结果,而是使同一准备好的查询上的任何先前execute()调用的结果无效。

  • name()函数返回准备好的查询名称。这与prepare_query()调用中作为第一个参数传递的名称相同。statement()函数返回对底层预处理语句的引用。还要注意,在空的prepared_query实例上调用这些函数中的任何一个都会导致未定义的行为。

  • 准备好的查询最简单的用例是需要在单个事务中多次执行同一查询。考虑以下示例,查询年龄超过多个不同年龄段的人。此代码片段和后续代码片段取自odb-examples中准备好的示例。

typedef odb::query<person> query;
typedef odb::prepared_query<person> prep_query;
typedef odb::result<person> result;

transaction t (db.begin ());

unsigned short age;
query q (query::age > query::_ref (age));
prep_query pq (db.prepare_query<person> ("person-age-query", q));

for (age = 90; age > 40; age -= 10)
{
  result r (pq.execute ());
  ...
}

t.commit ();
  • 另一种情况是需要在同时执行的多个事务中重用相同的查询。如上所述,在这种情况下,我们需要确保准备好的查询和所有事务使用相同的连接。考虑上述示例的另一个版本,该版本在单独的事务中执行每个查询:
connection_ptr conn (db.connection ());

unsigned short age;
query q (query::age > query::_ref (age));
prep_query pq (conn->prepare_query<person> ("person-age-query", q));

for (age = 90; age > 40; age -= 10)
{
  transaction t (conn->begin ());

  result r (pq.execute ());
  ...

  t.commit ();
}
  • 请注意,使用这种方法,我们会保持数据库连接,直到执行了所有涉及准备好的查询的事务。特别是,这意味着当我们忙的时候,连接不能被另一个线程重用。因此,只有当所有交易都在彼此接近的情况下执行时,才建议使用这种方法。还要注意,一旦我们释放了准备查询的连接,未缓存(见下文)的准备查询就会失效。

  • 如果我们需要在不同时间(可能在不同线程中)执行的事务中重用准备好的查询,那么建议的方法是在连接上缓存准备好的请求。为了支持此功能,odb::databaseodb::connection类提供了以下函数模板。与prepare_query()类似,以下函数的odb::database版本使用当前活动的事务调用相应的odb::connection版本来解析连接。

  template <typename T>
  void
  cache_query (const prepared_query<T>&);

  template <typename T, typename P>
  void
  cache_query (const prepared_query<T>&,
               std::[auto|unique]_ptr<P> params);

  template <typename T>
  prepared_query<T>
  lookup_query (const char* name) const;

  template <typename T, typename P>
  prepared_query<T>
  lookup_query (const char* name, P*& params) const;
  • cache_query()函数将传递的准备好的查询缓存在连接上。cache_query()的第二个重载版本也需要一个指向按引用查询参数的指针。在C++98/03中,它应该是std::auto_ptr,而在C++11中,可以使用std::auto-ptrstd::unique_ptrcache_query()函数假定传递的params参数的所有权。如果此连接上已经缓存了同名的准备好的查询,则抛出odb::prepared_already_cached异常。

  • lookup_query()函数查找给定名称的先前缓存的准备好的查询。lookup_query()的第二个重载版本也返回一个指向引用查询参数的指针。如果尚未缓存具有此名称的准备好的查询,则返回一个空的prepared_query实例。如果已缓存具有此名称的准备好的查询,但对象类型或参数类型与缓存的不匹配,则抛出odb::prepared_type_mismatch异常。

  • 作为准备好的查询缓存功能的第一个示例,考虑不使用任何引用参数的情况:

for (unsigned short i (0); i < 5; ++i)
{
  transaction t (db.begin ());

  prep_query pq (db.lookup_query<person> ("person-age-query"));

  if (!pq)
  {
    pq = db.prepare_query<person> (
      "person-val-age-query", query::age > 50);
    db.cache_query (pq);
  }

  result r (pq.execute ());
  ...

  t.commit ();

  // Do some other work.
  //
  ...
}
  • 以下示例显示了如何对包含引用参数的查询执行相同的操作。在这种情况下,参数与准备好的查询一起缓存。
for (unsigned short age (90); age > 40; age -= 10)
{
  transaction t (db.begin ());

  unsigned short* age_param;
  prep_query pq (
    db.lookup_query<person> ("person-age-query", age_param));

  if (!pq)
  {
    auto_ptr<unsigned short> p (new unsigned short);
    age_param = p.get ();
    query q (query::age > query::_ref (*age_param));
    pq = db.prepare_query<person> ("person-age-query", q);
    db.cache_query (pq, p); // Assumes ownership of p.
  }

  *age_param = age; // Initialize the parameter.
  result r (pq.execute ());
  ...

  t.commit ();

  // Do some other work.
  //
  ...
}
  • 从上述示例中可以明显看出,当我们使用准备好的查询缓存时,执行查询的每个事务还必须包括准备和缓存此查询的代码(如果尚未完成)。如果在应用程序中的单个位置使用准备好的查询,那么这通常不是问题,因为所有相关代码都保存在一个位置。但是,如果在应用程序中的几个不同位置使用相同的查询,那么我们最终可能会复制相同的准备和缓存代码,这使得维护变得困难。

  • 为了解决这个问题,ODB允许我们注册一个准备好的查询工厂,在调用lookup_query()时调用该工厂来准备和缓存查询。要注册工厂,我们使用database::query_factory() 函数。在C++98/03中,它具有以下签名:

  void
  query_factory (const char* name,
                 void (*factory) (const char* name, connection&));
  • 在C++11中,它使用std::函数类模板:
  void
  query_factory (const char* name,
                 std::function<void (const char* name, connection&)>);
  • query_factory()函数的第一个参数是此工厂将被调用以准备和缓存的准备好的查询名称。空名称被视为能够准备任何查询的回退通配符工厂。第二个参数是工厂函数,或者在C++11中是函数对象或lambda。

  • 示例片段显示了如何使用准备好的查询工厂:

struct params
{
  unsigned short age;
  string first;
};

static void
query_factory (const char* name, connection& c)
{
  auto_ptr<params> p (new params);
  query q (query::age > query::_ref (p->age) &&
           query::first == query::_ref (p->first));
  prep_query pq (c.prepare_query<person> (name, q));
  c.cache_query (pq, p);
}

db.query_factory ("person-age-name-query", &query_factory);

for (unsigned short age (90); age > 40; age -= 10)
{
  transaction t (db.begin ());

  params* p;
  prep_query pq (db.lookup_query<person> ("person-age-name-query", p));
  assert (pq);

  p->age = age;
  p->first = "John";
  result r (pq.execute ());
  ...

  t.commit ();
}
  • 在C++11中,我们可以使用lambda函数以及unique_ptr而不是auto_ptr
db.query_factory (
  "person-age-name-query",
  [] (const char* name, connection& c)
  {
    unique_ptr<params> p (new params);
    query q (query::age > query::_ref (p->age) &&
             query::first == query::_ref (p->first));
    prep_query pq (c.prepare_query<person> (name, q));
    c.cache_query (pq, std::move (p));
  });

原文地址:https://blog.csdn.net/weixin_45125631/article/details/140517117

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!