【开源库学习】libodb库学习(八)
9. 节
- ODB 节是一种优化机制,它允许我们将持久类的数据成员划分为可以单独加载和/或更新的组。例如,如果一个对象包含加载或更新数据成员(如BLOB或容器)的成本很高,并且访问或修改不频繁,那么这可能很有用。例如:
#include <odb/section.hxx>
#pragma db object
class person
{
...
#pragma db load(lazy) update(manual)
odb::section keys_;
#pragma db section(keys_) type("BLOB")
char public_key_[1024];
#pragma db section(keys_) type("BLOB")
char private_key_[1024];
};
transaction t (db.begin ());
auto_ptr<person> p (db.load<person> (...)); // Keys are not loaded.
if (need_keys)
{
db.load (*p, p->keys_); // Load keys.
...
}
db.update (*p); // Keys are not updated.
if (update_keys)
{
...
db.update (*p, p->keys_); // Update keys.
}
t.commit ();
-
odb-examples
的section
目录中提供了一个完整的示例,显示了如何使用节。 -
为什么我们需要将数据成员分组到不同的节?为什么在必要时不能独立加载和更新每个数据成员?这一要求的原因是,用单个数据库语句加载或更新一组数据成员比用单独的语句加载或升级每个数据成员要高效得多。因为ODB准备并缓存用于加载和更新持久对象的语句,所以为需要一起加载或更新的特定数据成员集生成自定义语句也不是一种可行的方法。为了解决这个问题,ODB允许我们将经常更新和/或加载在一起的数据成员分组到各个节中。为了达到最佳性能,我们应该在节太多数据成员太少 和 数据成员太多节太少 之间找到平衡。我们可以使用应用程序的访问和修改模式作为此决策的基础。
-
为了在持久类中添加一个新的节,我们声明了一个
odb::section
类型的新数据成员。此时,我们还需要分别使用db-load
和db-update
pragmas指定此节的加载和更新行为。 -
一个节的加载行为可以是渴望的,也可以是懒惰的。急切加载的节始终作为对象加载的一部分加载。延迟加载的节不会作为对象加载的一部分加载,必要时必须使用
database::load()
函数(如下所述)显式加载。 -
节的更新行为可以是
always
,change
, 或者manual
。始终更新的节始终作为对象更新的一部分进行更新,前提是它已被加载。如果已加载并标记为已更改,则更改更新节仅作为对象更新的一部分进行更新。手动更新的节永远不会作为对象更新的一部分进行更新,并且必须在必要时使用database::update()
函数(如下所述)进行显式更新。 -
如果没有明确指定加载行为,则假定为急加载节。类似地,如果没有指定更新行为,则假定为始终更新的节。一个急于加载、总是更新的节是毫无意义的,因此是非法的。只有具有对象id的持久类才能有节。
-
为了指定数据成员属于某个节,我们使用
db section
pragma,并将节的成员名称作为其单个参数。除了对象id和乐观并发版本等特殊数据成员外,持久类的任何直接、非瞬态成员都可以属于一个部分,包括复合值、容器和指向对象的指针。例如:
#pragma db value
class text
{
std::string data;
std::string lang;
};
#pragma db object
class person
{
...
#pragma db load(lazy)
odb::section extras_;
#pragma db section(extras_)
text bio_;
#pragma db section(extras_)
std::vector<std::string> nicknames_;
#pragma db section(extras_)
std::shared_ptr<person> emergency_contact_;
};
-
空节毫无意义,因此是非法的,除非在抽象或多态类中,数据成员可以通过派生类添加到节中(见第9.1节,“部分和继承”)。
-
odb::section
类在<odb/section.hxx>
头文件中定义,并具有以下接口:
namespace odb
{
class section
{
public:
// Load state.
//
bool
loaded () const;
void
unload ();
// Change state.
//
bool
changed () const;
void
change ();
// User data.
//
unsigned char
user_data () const;
void
user_data (unsigned char);
};
}
-
loaded()
访问器可用于确定节是否已加载。unload()
修饰符将已加载的节标记为未加载。例如,如果您不希望在对象重新加载期间重新加载该节,这可能很有用。 -
changed()
访问器可用于查询节的更改状态。change()
修饰符将该节标记为已更改。对于未加载(或瞬态)的节调用此修饰符是有效的,但是,一旦加载了节(或对象),状态将重置为不变。更改状态仅与具有更改更新行为的节相关,对所有其他节都忽略。 -
节类的大小为一个字节,其中四个位可用于通过
user_data()
访问器和修饰符存储自定义状态。 -
odb::database类提供了
load()
和update()
函数的特殊版本,允许我们加载和更新持久类的节。他们的签名如下:
template <typename T>
void
load (T& object, section&);
template <typename T>
void
update (const T& object, const section&);
-
在调用
load()
函数之前,对象本身必须已经加载。如果该节已加载,则调用load()
将重新加载其数据成员。显式加载渴望加载的节是非法的。 -
在调用
section-update()
函数之前,该节(以及对象)必须处于已加载状态。如果未加载该节,则抛出odb::section_not_looaded
异常。section-update()
函数不会检查,但会清除该节的更改状态。换句话说,sectionupdate()
将始终更新数据库中的section数据成员并清除更改标志。还要注意,任何节,即始终、更改或手动更新,都可以使用此函数显式更新。 -
与数据库的其他操作一样,
load()
和update()
都必须在事务中执行。还请注意,load()
和update()
都期望引用该节作为其第二个参数。此引用必须引用作为第一个参数传递的对象中的数据成员。如果它引用了节类的其他实例,例如本地副本或临时副本,则抛出odb::section_not_in_object
异常。例如:
#pragma db object
class person
{
public:
...
odb::section
keys () const {return keys_;}
private:
odb::section keys_;
...
};
auto_ptr<person> p (db.load<person> (...));
section s (p->keys ());
db.load (*p, s); // Throw section_not_in_object, copy.
db.update (*p, p->keys ()); // Throw section_not_in_object, copy.
- 乍一看,为了防止此类错误的发生,使
section
类不可复制似乎更合适。然而,期望能够复制(或分配)节作为对象复制(或指派)的一部分是完全合理的。因此,节是可复制和可分配的,但是,此功能不应用于访问器或修饰符。相反,节访问器和修饰符应始终通过引用。以下是我们如何修复前面的示例:
#pragma db object
class person
{
public:
...
const odb::section&
keys () const {return keys_;}
odb::section&
keys () {return keys_;}
private:
odb::section keys_;
...
};
auto_ptr<person> p (db.load<person> (...));
section& s (p->keys ());
db.load (*p, s); // Ok, reference.
db.update (*p, p->keys ()); // Ok, reference.
-
其他几个数据库操作会影响部分。瞬态对象中某个节的状态未定义。也就是说,在调用对象
persist()
或load()
函数之前,或者在调用对象erase()
函数之后,section::loaded()
和section::changed()
访问器返回的值是未定义的。 -
在调用
persist()
后,所有节,包括渴望加载的节,都被标记为已加载且未更改。如果我们使用load()
调用或作为查询的结果加载一个对象,那么急切加载的节将被加载并标记为已加载且未更改,而延迟加载的节则被标记为已卸载。如果稍后使用sectionload()
调用加载延迟加载的节,则将其标记为已加载且未更改。 -
当我们使用
update()
调用更新对象时,手动更新的节会被忽略,而始终更新的节在加载时会被更新。仅当已加载并标记为已更改时,更改更新的节才会更新。更新后,这些节将重置为未更改的状态。当我们使用reload()
调用重新加载对象时,加载的节会自动重新加载并重置为未更改的状态。 -
为了进一步说明一个节的状态转换,请考虑以下示例:
#pragma db object
class person
{
...
#pragma db load(lazy) update(change)
odb::section keys_;
...
};
transaction t (db.begin ());
person p ("John", "Doe"); // Section state is undefined (transient).
db.persist (p); // Section state: loaded, unchanged.
auto_ptr<person> l (
db.load<person> (...)); // Section state: unloaded, unchanged.
db.update (*l); // Section not updated since not loaded.
db.update (p); // Section not updated since not changed.
p.keys_.change (); // Section state: loaded, changed.
db.update (p); // Section updated, state: loaded, unchanged.
db.update (*l, l->keys_); // Throw section_not_loaded.
db.update (p, p.keys_); // Section updated even though not changed.
db.reload (*l); // Section not reloaded since not loaded.
db.reload (p); // Section reloaded, state: loaded, unchanged.
db.load (*l, l->keys_); // Section loaded, state: loaded, unchanged.
db.load (p, p.keys_); // Section reloaded, state: loaded, unchanged.
db.erase (p); // Section state is undefined (transient).
t.commit ();
- 当使用更改更新行为时,我们有责任在属于该节的任何数据成员被修改时将该节标记为已更改。将截面标记为已更改的自然位置是截面数据成员的修饰符,例如:
#pragma db object
class person
{
...
typedef std::array<char, 1024> key_type;
const key_type&
public_key () const {return public_key_;}
void
public_key (const key_type& k)
{
public_key_ = k;
keys_.change ();
}
const key_type&
private_key () const {return private_key_;}
void
private_key (const key_type& k)
{
private_key_ = k;
keys_.change ();
}
private:
#pragma db load(lazy) update(change)
odb::section keys_;
#pragma db section(keys_) type("BLOB")
key_type public_key_;
#pragma db section(keys_) type("BLOB")
key_type private_key_;
...
};
- 更改更新节的一个有趣方面是,当执行对象或节更新的事务稍后回滚时会发生什么。在这种情况下,虽然节的更改状态已重置(更新后),但实际更改并未提交到数据库。更改更新节通过自动注册回滚回调来处理这种情况,然后,如果调用了回滚回调,则恢复原始更改状态。以下代码说明了这种语义(继续前面的示例):
auto_ptr<person> p;
try
{
transaction t (db.begin ());
p = db.load<person> (...);
db.load (*p, p->keys_);
p->private_key (new_key); // The section is marked changed.
db.update (*p); // The section is reset to unchanged.
throw failed (); // Triggers rollback.
t.commit ();
}
catch (const failed&)
{
// The section is restored back to changed.
}
9.1 节和继承
- 通过重用和多态继承(第8章“继承”),可以向派生类添加新的节。还可以将派生类中的数据成员添加到基中声明的节中。例如:
#pragma db object polymorphic
class person
{
...
virtual void
print ();
#pragma db load(lazy)
odb::section print_;
#pragma db section(print_)
std::string bio_;
};
#pragma db object
class employee: public person
{
...
virtual void
print ();
#pragma db section(print_)
std::vector<std::string> employment_history_;
};
transaction t (db.begin ());
auto_ptr<person> p (db.load<person> (...)); // Person or employee.
db.load (*p, p->print_); // Load data members needed for print.
p->print ();
t.commit ();
-
当一个节的数据成员分布在重用继承层次结构中的多个类上时,节加载和更新都是通过单个数据库语句执行的。相比之下,使用多态继承,节加载是用一条语句执行的,而更新则需要为添加到节中的每个类分别使用一条语句。
-
还要注意,在多态继承中,节与对象的关联是静态的。或者,换言之,只有当某个对象的静态类型确实包含该节时,您才能通过该对象加载该节。以下示例将有助于进一步说明这一点:
#pragma db object polymorphic
class person
{
...
};
#pragma db object
class employee: public person
{
...
#pragma db load(lazy)
odb::section extras_;
...
};
#pragma db object
class manager: public employee
{
...
};
auto_ptr<manager> m (db.load<manager> (...));
person& p (*m);
employee& e (*m);
section& s (m->extras_);
db.load (p, s); // Error: extras_ is not in person.
db.load (e, s); // Ok: extras_ is in employee.
9.2 节与乐观并发
- 当在具有乐观并发模型的类中使用节时(第12章,“乐观并发”),节更新和加载操作都会将对象版本与数据库中的版本进行比较,如果不匹配,则抛出
odb::object_changed
异常。此外,节更新操作会递增版本,以指示对象状态已更改。例如:
#pragma db object optimistic
class person
{
...
#pragma db version
unsigned long long version_;
#pragma db load(lazy)
odb::section extras_;
#pragma db section(extras_)
std::string bio_;
};
auto_ptr<person> p;
{
transaction t (db.begin ());
p = db.load<person> (...);
t.commit ();
}
{
transaction t (db.begin ());
try
{
db.load (*p, p->extras_); // Throws if object state has changed.
}
catch (const object_changed&)
{
db.reload (*p);
db.load (*p, p->extras_); // Cannot fail.
}
t.commit ();
}
-
还要注意,如果对象更新触发了一个或多个节更新,那么每次这样的更新都会增加对象版本。因此,对包含节的对象进行更新可能会导致版本增量超过一个。
-
当节与乐观并发和继承一起使用时,可能需要额外的步骤来启用此功能。如果计划向派生类添加新节,则必须使用
db-sectionable
pragma将层次结构的根类(声明版本数据成员的根类)声明为可分段。例如:
#pragma db object polymorphic sectionable
class person
{
...
#pragma db version
unsigned long long version_;
};
#pragma db object
class employee: public person
{
...
#pragma db load(lazy)
odb::section extras_;
#pragma db section(extras_)
std::vector<std::string> employment_history_;
};
- 这一要求与需要在根类中生成额外的版本增量代码有关,这些代码将由派生类中添加的节使用。如果您忘记将根类声明为可分段,然后在其中一个派生类中添加一个节,则ODB编译器将发出诊断。
9.3 节与懒惰指针
- 如果一个懒惰指针(第6.4节,“懒惰指针”)属于一个懒惰加载的节,那么我们最终会有两个级别的懒惰加载。具体来说,当加载该节时,惰性指针会用对象id初始化,但对象本身不会被加载。例如:
#pragma db object
class employee
{
...
#pragma db load(lazy)
odb::section extras_;
#pragma db section(extras_)
odb::lazy_shared_ptr<employer> employer_;
};
transaction t (db.begin ());
auto_ptr<employee> e (db.load<employee> (...)); // employer_ is NULL.
db.load (*e, e->extras_); // employer_ contains valid employer id.
e->employer_.load (); // employer_ points to employer object.
t.commit ();
9.4 节和更改跟踪容器
- 如果变更跟踪容器(第5.4节,“变更跟踪容器”)属于变更更新节,则在对象更新之前,ODB将检查容器是否已更改,如果已更改,则自动将该节标记为已更改。例如:
#pragma db object
class person
{
...
#pragma db load(lazy) update(change)
odb::section extras_;
#pragma db section(extras_)
odb::vector<std::string> nicknames_;
};
transaction t (db.begin ());
auto_ptr<person> p (db.load<person> (...));
db.load (*p, p->extras_);
p->nicknames_.push_back ("JD");
db.update (*p); // Section is automatically updated even
// though it was not marked as changed.
t.commit ();
原文地址:https://blog.csdn.net/weixin_45125631/article/details/140539891
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!