当前位置: 首页 > news >正文

CppCon 2014 学习:ODB, Advanced Weapons and Tactics

#Schema Evolution 是数据库持久化技术中的一个重要概念,特别是在使用像 ODB 这样的 C++ ORM 框架时。
展示的代码片段正是 ODB 支持的**模式演化(Schema Evolution)**语法示例。

什么是 Schema Evolution?

Schema Evolution 指的是:

当数据库结构(例如类的字段)发生变化时,如何保持与旧版本数据的兼容性并正确迁移已有数据。

提供的代码解释

#pragma db model version(1, 2)
#pragma db object
class user
{std::string first_;std::string last_;
};

表示:在数据库模型版本 1 到 2 之间,类 user 有两个字段:first_last_

#pragma db model version(1, 3)
#pragma db object
class user
{std::string name_;
};

表示:从模型版本 3 开始,类 user 结构发生变化:

  • 合并了 first_last_ → 变成一个 name_ 字段。

ODB 如何支持这种演化?

ODB 提供以下功能支持 Schema Evolution:

功能说明
#pragma db model version(X, Y)定义模型适用的版本范围
自动 schema 迁移ODB 会根据版本差异生成 SQL 迁移脚本
--generate-schema--schema-format控制生成的迁移 SQL
支持 升级脚本(SQL ALTER TABLE)让数据库数据跟上结构变化

需要注意的问题

  1. 数据迁移逻辑需要开发者介入
    例如 first_ + " " + last_name_ 的合并,不是 ODB 自动能做的,需要手动写 SQL 脚本或 C++ 代码协助迁移。
  2. 版本必须明确声明
    否则 ODB 无法知道哪些类在哪些版本中被引入/修改。
  3. 旧数据要保留向后兼容性?
    如果数据不打算迁移,还需考虑是否保留旧字段。

总结一句话

展示的是 ODB 的 schema evolution 技术,通过 #pragma db model version(X, Y) 配合类结构变化,描述数据库模式如何在不同版本间演变。实际使用时,还需要你写一些数据迁移逻辑(如字段合并等)来完成完整升级。

展示的代码片段是 ODB(C++ ORM)中的数据迁移(Data Migration)机制的一个实际用法,它是在数据库模式升级过程中,将旧字段数据迁移到新字段的一种方式。

示例代码解读

schema_catalog::data_migration_function (3,[] (database& db){for (bug& b: db.query<bug> ()){b.name (b.first () + " " + b.last ());db.update (b);}});

背景说明

这个 lambda 函数传给了 data_migration_function()

  • 第一个参数 3 是目标 schema 版本号
  • 第二个参数是 执行数据迁移的函数
    这表示:在数据库 schema 升级到版本 3 时,要执行这个逻辑来把旧数据结构中的 firstlast 字段,合并为新的 name 字段。

每一行解析

schema_catalog::data_migration_function (3,  // 表示这是升级到第3版时要执行的逻辑
[] (database& db) {  // 匿名函数,用于数据迁移,接受 database 实例
for (bug& b: db.query<bug> ())  // 遍历 bug 表中所有记录
b.name (b.first () + " " + b.last ());  // 把 first + last 合并成新的 name 字段
db.update (b);  // 更新数据库中的这条记录
}
);

ODB 数据迁移机制总结

步骤说明
1. 定义模型版本变化使用 #pragma db model version(x, y)
2. 注册迁移函数使用 schema_catalog::data_migration_function(version, func)
3. 在函数中处理老数据查询老对象、处理字段、新字段赋值、调用 update()
4. ODB 自动在升级到指定版本时调用

注意事项

  • 迁移函数只在 版本升级发生时执行。
  • 如果数据库中数据量大,这段迁移代码可能需要优化(批量提交 / 并发)
  • 要在代码中定义好旧字段(first_, last_)和新字段(name_)的兼容读取方式。

举个现实类比

你可以把这段逻辑理解成 Excel 中的批量操作:

原来每行有「名」和「姓」两列,现在换成「全名」,你写了个脚本,自动合并旧数据,然后更新每行。

展示的是 ODB 中两个高级功能:**版本化命名空间(Versioned Namespace)软删除(Soft-Delete)**的用法和设计思路,尤其是它们在 schema 演化中的应用。下面详细说明:

1. Versioned Namespace (版本化命名空间)

namespace version2
{#pragma db objectclass user{std::string first_;std::string last_;};
}
  • 通过把类放进 version2 命名空间,可以在同一个项目中同时存在不同版本的类定义,比如 version1::userversion2::user,方便管理不同版本的 schema。
  • 这是版本控制的一种策略,允许平行维护多个版本,避免破坏兼容性。
  • 对应数据库迁移时,可以映射不同的表结构,或让程序同时支持多版本数据访问。

2. Soft-Delete (软删除)

软删除是数据库设计中一种常见策略:不是真的删除数据,而是逻辑上标记为“删除”状态,保留数据以备恢复或审计
ODB 中通过 #pragma db deleted(version) 来标记某字段在某版本后不再使用(被“软删除”),但数据依然在数据库里。

你给出的几种软删除写法说明:

写法一:
#pragma db model version(1, 3)
#pragma db object
class user
{#pragma db deleted(3)std::string first_;#pragma db deleted(3)std::string last_;std::string name_;
};
  • 表示 first_last_ 字段从版本 3 开始被标记为删除(不再用),取而代之的是 name_ 字段。
  • 数据库迁移时会把旧字段标记为废弃,但数据依然存留(软删除),程序新版本使用 name_ 字段。
写法二(把被删除字段包在子结构里):
#pragma db object
class user
{std::string name_;#pragma db valuestruct deleted_data{#pragma db deleted(3)std::string first_;#pragma db deleted(3)std::string last_;};#pragma db column("")std::unique_ptr<deleted_data> dd_;
};
  • 这里把 first_last_ 封装到一个 deleted_data 子结构里,并用 unique_ptr 指向。
  • #pragma db deleted(3) 标记表示这些字段从版本 3 开始被“软删除”。
  • #pragma db column("") 表示 dd_ 不对应新的表列,而是跟父类的表列共用存储(一般是复合映射)。
  • 这种设计使得新版本代码更整洁,将旧字段封装,方便管理和维护。

总结

功能作用ODB 语法和用法示例
版本化命名空间多版本类管理,保持兼容与清晰分离namespace version2 { #pragma db object class user { ... }; }
软删除字段逻辑废弃但数据保留,防止数据丢失#pragma db deleted(3) 标记字段软删除
字段封装软删除把废弃字段封装成结构体,保持代码整洁struct deleted_data { ... }; std::unique_ptr<deleted_data> dd_;

ODB 中的 Soft-Add(软添加)Soft-Delete(软删除) 机制,结合你给出的代码,这里详细说明:

1. Soft-Add(软添加)

软添加指的是在新的 schema 版本中向已有对象添加新的字段,这个字段在旧版本里不存在,新增的字段默认需要初始化或赋默认值。

代码示例

class user
{#pragma db added(3)std::string name_;#pragma db valuestruct deleted_data{#pragma db deleted(3)std::string first_;#pragma db deleted(3)std::string last_;};#pragma db column("")std::unique_ptr<deleted_data> dd_;
};
  • #pragma db added(3) 表示字段 name_ 是从版本 3 软添加的,即版本 < 3 的数据库没有这个字段。
  • 结合 deleted_data 结构体软删除旧字段,完成从旧字段到新字段的平滑过渡。

2. 迁移函数补充默认值

因为新添加的字段在老数据中是“空”的(数据库里没数据),所以需要用迁移函数给它赋默认值,否则新版本程序读取会遇到未定义状态。

schema_catalog::data_migration_function (2,[] (database& db){for (bug& b: db.query<bug> ()){b.platform ("Unknown");  // 新增字段 platform 赋默认值db.update(b);}});
  • 当数据库升级到版本 2 时,执行这个迁移函数。
  • 为所有旧的 bug 记录里的 platform 字段赋默认值 "Unknown",保证程序能正常使用。

3. 软添加与软删除结合使用

  • 软删除(deleted) 表示字段废弃但数据留存。
  • 软添加(added) 表示新字段加入。
  • 结合使用可以保证版本升级时的平滑转换,旧字段和新字段共存一段时间,数据通过迁移函数同步。

总结

机制作用示例
Soft-Delete标记字段废弃,旧数据保留#pragma db deleted(3) std::string first_;
Soft-Add新字段添加,老版本无该字段#pragma db added(3) std::string name_;
数据迁移赋默认值,迁移旧数据到新字段schema_catalog::data_migration_function(2, ... )

简单比喻

就像软件升级,你先保留旧功能(软删除的字段),再加新功能(软添加的字段),升级时自动帮你把旧数据迁移到新字段,同时给新字段赋默认值,避免程序崩溃。

ODB 中如何定义和操作带有 容器成员(std::vector) 的持久化类,以及事务中的加载和更新流程。

1. 容器成员声明示例

#pragma db object
class bug
{// ...#pragma db id autounsigned long long id_;   // 主键,自增长status status_;           // 其他普通字段std::string summary_;std::string description_;std::vector<std::string> comments_;  // 容器成员,持久化保存多条评论
};
  • comments_std::vector<std::string> 类型,ODB 支持自动映射标准容器到数据库表结构。
  • 通过 ODB,容器中的每个元素都会被存储为独立的行,和所属 bug 对象通过外键关联。

2. 操作示例:事务里添加评论并更新

transaction t (db.begin ());
std::shared_ptr<bug> b (db.load<bug> (id));  // 加载 bug 对象b->add_comment("I also have this problem! Help me!");  // 增加评论
db.update(b);  // 更新数据库t.commit ();   // 提交事务
  • 先开启一个事务,保证数据完整性和一致性。
  • 加载指定 id 的 bug 对象。
  • 通过自定义的 add_comment 函数向 comments_ 容器添加新字符串。
  • 调用 db.update() 保存改动。
  • 提交事务,确保所有更改原子地写入数据库。

3. 关键点

  • 容器内元素会被拆分存储,但对开发者来说用法像普通成员变量一样简单。
  • 事务机制确保数据的完整性,支持回滚和并发控制。
  • #pragma db id auto 是主键自增长,加载和更新操作需要依赖它。

4. 小结

特性说明
容器成员持久化支持 std::vector 及其他 STL 容器
事务支持通过 transaction 保证数据一致性
加载更新先 load,再修改成员,最后 update 保存

ODB 提供的 Change-Tracking Containers,它们是普通容器的“替代品”,专门用来优化持久化对象的变更跟踪。

1. Change-Tracking Containers 概念

  • 作用:代替普通 STL 容器(如 std::vector),自动记录元素的增删改变化,避免每次调用 update() 时都重新写入整个容器数据,提高性能。
  • 特性
    • Drop-in replacement:接口和用法与标准容器几乎一致,换用时改动极小。
    • 维护每个元素的状态(新增、修改、删除)用 2-bit 标记,内存开销极小。
    • 仅更新实际发生变动的元素,减少数据库写操作。

2. 常见实现示例

ODB 容器对应标准容器
odb::vector<T>替代 std::vector<T>
QOdbList<T>替代 QList<T>

3. 优势总结

优点说明
自动变更追踪只更新修改的元素,数据库操作更高效
兼容性好API 与标准容器兼容,易于迁移和维护
轻量级开销仅 2-bit/元素 的额外开销,几乎无性能负担

4. 典型用法示例

#include <odb/vector.hxx>
class bug
{#pragma db id autounsigned long long id_;odb::vector<std::string> comments_;  // 变更跟踪容器
};
  • 直接用 odb::vector 代替 std::vector,增加变更跟踪。
  • 操作和普通 std::vector 类似。

ODB 中的容器(Containers)和对象缓存(Object Cache) 概念,结合你的代码片段,详细说明:

1. Containers — 使用 odb::vector

#pragma db object
class bug
{// ...odb::vector<std::string> comments_;  // 变更跟踪的容器,替代 std::vector
};
  • odb::vector 是 ODB 专门设计的容器,支持变更追踪,比普通的 std::vector 更高效。
  • 适合存储多条评论这类动态增长、修改频繁的容器数据。

2. Object Cache — 双向关联与 weak_ptr 避免循环引用

#pragma db object
class user
{// ...#pragma db inverse(reporter_)std::vector<std::weak_ptr<bug>> reported_bugs_;  // 指向报告的 bug 集合
};#pragma db object
class bug
{// ...std::shared_ptr<user> reporter_;  // 指向报告者 user
};
  • userbug 之间是双向关联:
    • bug 持有指向 usershared_ptr(强引用)。
    • user 持有指向 bugweak_ptr(弱引用),用 #pragma db inverse(reporter_) 声明它是 bugreporter_ 成员的反向关联。
  • 这样设计避免了循环引用导致的内存泄漏。

3. Object Cache — 事务与 Session

transaction t (db.begin ());
session s;  // 启用会话缓存
std::shared_ptr<user> u (db.load<user> (email));  // 从数据库加载 user 对象
t.commit ();
  • transaction 控制数据库操作的原子性。
  • session 是 ODB 的对象缓存机制
    • 在 session 生命周期内,同一个对象只加载一次,重复加载返回同一内存地址的实例。
    • 减少数据库访问次数,优化性能。
  • 一般在事务中创建 session,加载/操作对象后提交事务。

4. 总结

概念作用
odb::vector变更跟踪容器,提升容器元素的持久化效率
双向关联#pragma db inverse 定义对象间相互引用关系
weak_ptr防止循环引用,安全管理关联对象生命周期
Session 缓存事务内缓存对象实例,减少重复加载数据库

ODB 中的 Lazy Pointers(延迟指针),它们用于更细粒度控制关联对象的加载时机。

1. Lazy Pointers 概念

  • 作用:普通的智能指针(shared_ptrweak_ptr)会在对象加载时立即加载关联对象,可能导致性能开销大(比如递归加载大量数据)。
  • Lazy Pointers 延迟加载关联对象,只有在明确调用 .load() 时才真正从数据库加载,提高性能和响应速度。
  • 每种支持的智能指针类型(shared_ptrweak_ptr 等)都有对应的延迟版本,如 odb::lazy_shared_ptrodb::lazy_weak_ptr

2. 代码示例

#pragma db object
class user
{// ...#pragma db inverse(reporter_)std::vector<odb::lazy_weak_ptr<bug>> reported_bugs_;  // 使用延迟弱指针
};
  • reported_bugs_ 不是普通 weak_ptr,而是 odb::lazy_weak_ptr
  • 这样 user 对象加载时不会自动加载所有 bug,只有显式访问时才加载。

3. 访问延迟指针示例

odb::lazy_weak_ptr<bug> lb = ...;  // 获得延迟指针
std::shared_ptr<bug> b = lb.load();  // 明确调用 load(),加载并锁定 bug 对象
  • lb.load() 触发数据库加载,将延迟指针升级为普通的 shared_ptr
  • 允许按需加载关联对象,避免无谓开销。

4. 总结

特性说明
延迟加载关联对象只有在显式调用 .load() 时加载
性能优化避免一开始加载所有关联对象,减少数据库查询
完整智能指针接口与普通指针互换方便,行为类似

ODB 的 Object Sections(对象分区),它允许你将对象划分成多个部分,分别控制加载和更新行为,常用于延迟加载和减少数据库访问。

1. Object Sections 基本用法

#pragma db object
class bug
{// ...#pragma db load(lazy) update(change)odb::section details_;  // 定义一个分区,名称是 details_#pragma db section(details_)std::string description_;#pragma db section(details_)odb::vector<std::string> comments_;
};
  • details_ 是对象的一个逻辑分区(section)。
  • 使用 #pragma db section(details_) 标记成员属于该分区。
  • 通过分区,可以控制这些成员的延迟加载 (load(lazy)) 和按需更新 (update(change))。
  • 这样对象加载时,description_comments_ 不会自动加载,只有调用 db.load(obj, obj.details_) 时才加载。

2. 代码示例:按需加载和更新分区

transaction t (db.begin ());
for (bug& b: db.query<bug> (query::status == open))
{if (is_interesting(b)){db.load(b, b.details_); // 仅加载 details_ 分区数据b.comments_.push_back("I am working on a fix."); // 修改 comments_b.details_.change(); // 标记 details_ 分区已变更}db.update(b);  // 只更新已变更的分区字段
}
t.commit ();
  • 先用 db.load(b, b.details_) 延迟加载 details_ 分区的字段。
  • 修改分区中的字段后调用 b.details_.change(),告诉 ODB 这个分区已变更。
  • db.update(b) 只会更新被标记为已变更的分区,避免不必要的写操作。

3. 多分区示例(以 user 类为例)

#pragma db object
class user
{// ...#pragma db load(lazy)odb::section details_;  // 延迟加载分区#pragma db section(details_)std::vector<odb::lazy_weak_ptr<bug>> reported_bugs_;  // 延迟加载关联对象
};
  • reported_bugs_ 被放在 details_ 分区,默认延迟加载。
  • 访问时根据需要显式加载。

4. 主要优势

优点说明
延迟加载减少初次加载时数据库访问,提升性能
分区更新精准控制哪些字段被更新,减少写操作
逻辑分离代码上分区,结构更清晰,维护更方便
支持复杂数据结构和关联对象分区内可包含容器和延迟关联指针等复杂成员

你说的是 ODB 的 Views 概念,主要用于从数据库中加载和处理对象的部分数据,或者关联多个表的数据,甚至执行更复杂的 SQL 查询。下面帮你梳理一下:

1. 基本用法示例

typedef odb::query<bug> query;transaction t(db.begin());
for (const bug& b : db.query<bug>(query::status == open))
{const user& r = b.reporter();std::cout << b.id() << " "<< b.summary() << " "<< r.first() << " "<< r.last() << std::endl;
}
t.commit();
  • 这是从 bug 表里查询状态为 open 的 bug。
  • 通过 b.reporter() 访问关联的 user 对象(报错者)。
  • 打印 bug 的ID、摘要,以及报错者的名字。

2. Views 的主要功能和特点

功能说明
选择部分数据成员只加载需要的字段,避免不必要的数据加载
多表关联查询通过关联关系 join 多张表,方便一次性查询相关对象数据
复杂查询支持支持执行自定义的 SQL 查询,如聚合函数、存储过程调用等
只读视图通常用于数据展示,避免对数据库对象的修改

3. 为什么用 Views?

  • 性能优化:避免加载整个对象,特别是大型对象或包含大字段(比如文本、二进制数据)时。
  • 方便聚合和分析:一次查询多个表,减少数据库交互次数。
  • 灵活性强:支持复杂 SQL 语句,满足多样化需求。

4. 举个简单的“视图”用法

比如只想查询 bug 的 ID 和摘要,可以定义一个专门的视图类(非持久化对象),用 SQL 自定义查询,然后映射结果。

ODB 中“视图(Views)”的声明与使用,结合了多个数据库对象,并映射到一个结构体上,从而简化查询和结果处理。具体说明如下:

1. 声明 Views

#pragma db view object(bug) object(user)
struct bug_summary
{unsigned long long id;std::string summary;std::string first;std::string last;
};
  • #pragma db view 声明一个视图结构 bug_summary,它是由 buguser 两个表的数据组成。
  • 它包含 bugidsummary,以及关联的 userfirstlast 名字字段。
  • ODB 会根据声明自动生成对应的 SQL 语句来关联这两个对象。

2. 使用 Views

typedef odb::query<bug_summary> query;
for (const bug_summary& b : db.query<bug_summary>(query::bug::status == open))
{std::cout << b.id << " "<< b.summary << " "<< b.first << " "<< b.last << std::endl;
}
  • 你通过 db.query<bug_summary> 直接查询这个视图类型。
  • 查询条件写的是 query::bug::status == open,即从 bug 表筛选 statusopen 的记录。
  • 遍历查询结果,访问结构体的成员字段。

3. 生成的 SQL 语句

ODB 编译时会自动生成相应 SQL,如:

SELECT bug.id, bug.summary, user.first, user.last
FROM bug
LEFT JOIN user ON bug.reporter = user.email
WHERE bug.status = ?
  • 这里用 LEFT JOIN 连接两个表。
  • bug.status = $1 作为过滤条件。

总结

  • 视图(View)是面向查询的轻量结构,方便查询多个对象字段。
  • 无需加载完整对象,提高效率。
  • 自动生成 SQL 连接和过滤,简化代码编写。

ODB 中使用“聚合视图(Aggregate Views)”的用法,能对数据库进行聚合查询(例如计数)并返回结构化结果。

1. 简单聚合视图示例:统计 Bug 数量

#pragma db view object(bug)
struct bug_stats
{#pragma db column("COUNT(" + bug::id_ + ")")std::size_t count;
};
  • bug_stats 结构体只包含一个字段 count,用来统计 bug 表中符合条件的记录数。
  • #pragma db column 里用 COUNT(bug::id_) 来生成 SQL 聚合函数。

使用:

typedef odb::query<bug_stats> query;
bug_stats bs = *db.query<bug_stats>(query::status == closed).begin();
std::cout << "Closed bugs count: " << bs.count << std::endl;
  • 查询状态为 closed 的 bug 数量。
  • db.query 返回结果迭代器,使用 begin() 取第一个结果。

2. 多表聚合视图示例:按用户统计

#pragma db view object(user) object(bug) \query ((?) + "GROUP BY" + user::email_)
struct user_stats
{std::string first;std::string last;#pragma db column("COUNT(" + bug::id_ + ")")std::size_t count;
};
  • 这里视图结合了 userbug 两个表。
  • query ((?) + "GROUP BY" + user::email_) 表示会以用户邮箱分组(GROUP BY)。
  • 统计每个用户对应的 bug 数量。
  • 返回用户的 first, last 及其对应的 bug 数量。

使用示例:

for (const user_stats& us : db.query<user_stats>(query::user::last == "Doe" &&query::bug::status == open))
{std::cout << us.first << " " << us.last << ": " << us.count << std::endl;
}
  • 查询姓氏是 "Doe" 且 Bug 状态为 open 的统计数据。
  • 遍历结果打印每位用户的名字和对应的 Bug 数量。

总结

  • 聚合视图让复杂的聚合和分组查询变得类型安全且简洁。
  • 利用 #pragma db column 自定义 SQL 聚合函数。
  • 支持多表联合查询及分组。
  • 用法和普通视图类似,返回的是聚合结果而非完整对象。

ODB 中调用**存储过程(Stored Procedure)**并将结果映射到结构体的用法。

1. 存储过程视图定义示例

#pragma db view query("EXEC analyze_bugs (?)")
struct report
{unsigned long long id;std::string result;
};
  • #pragma db view query("EXEC analyze_bugs (?)")
    这里定义了一个视图,视图背后是执行数据库的存储过程 analyze_bugs,它接受一个参数(? 表示参数占位符)。
  • 结果会被映射到 report 结构体,结构体成员对应存储过程返回的列。

2. 调用存储过程查询示例

typedef odb::query<report> query;
auto results = db.query<report>(query::_val("abc") + "," + query::_val(123));
  • query::_val(...) 用于给存储过程传递实际参数,参数以逗号分隔组合成调用语句。
  • 这里调用了 analyze_bugs 存储过程,传入两个参数 "abc"123
  • 返回值是 report 对象集合。

3. 总结

  • 通过 #pragma db view query("SQL语句") 可以直接调用存储过程或执行任意SQL查询。
  • query::_val 提供参数,拼接参数时用字符串拼接符号(+ "," +)分隔。
  • 返回值映射到对应结构体,方便在C++中操作。

ODB 中的 Native Query 使用示例,能让你直接执行原生 SQL 查询,并将结果映射到 C++ 结构体。

1. 定义视图结构体

#pragma db view
struct sequence_value
{unsigned long long value;
};
  • 这里定义了一个简单的视图结构体 sequence_value,用来映射数据库查询结果。
  • 结构体成员对应查询结果中的列名(默认匹配字段名或别名)。

2. 执行原生 SQL 查询

sequence_value sv (*db.query<sequence_value>("SELECT nextval('my_sequence')").begin());
  • 使用 db.query<sequence_value>(SQL字符串) 直接执行 SQL 查询。
  • 该 SQL 是 PostgreSQL 中获取序列下一个值的标准语句。
  • 通过解引用 begin() 迭代器获取查询结果的第一个元素。

3. 总结

  • Native Query 允许直接写 SQL,绕过 ODB 的自动查询生成,灵活性更高。
  • 结果映射到预定义结构体,方便后续操作。
  • 适合执行数据库特定的函数或复杂 SQL 语句。

ODB 中乐观并发控制(Optimistic Concurrency) 的多个示例与总结。以下是完整的深入理解:

什么是乐观并发控制?

乐观并发控制是一种假设“冲突不常发生”的并发策略:

  • 多个事务可以同时读取数据。
  • 当事务提交更新时,系统会检查数据是否在读取之后被别人修改过
  • 如果数据被其他事务更改过,当前事务将失败并抛出异常,从而防止数据被覆盖。

ODB 实现机制:版本控制字段

ODB 使用 #pragma db version#pragma db object version() 指定的字段来进行版本控制。
例如:

#pragma db object
class bug
{#pragma db id autounsigned long long id_;status status_;#pragma db versionunsigned int version_;
};

在每次更新时,ODB 会:

  • 检查数据库中的 version_ 是否等于内存中对象的版本。
  • 如果不一致,抛出 odb::optimistic_concurrency 异常。

示例代码解释

示例 1:事务封装完整更新流程

transaction t (db.begin ());
std::shared_ptr<bug> b (db.load<bug> (id));cout << "current status: " << b->status() << endl<< "enter new status: ";
status s;
cin >> s;b->status(s);db.update(b);
t.commit();

示例 2:延迟修改,分两次事务执行

std::shared_ptr<bug> b;
{transaction t (db.begin ());b = db.load<bug> (id);t.commit ();
}cout << "current status: " << b->status() << endl<< "enter new status: ";
status s;
cin >> s;
b->status(s);{transaction t (db.begin ());db.update(b);  // 此处可能触发版本冲突t.commit ();
}

这种写法适合做用户交互型程序,但会更容易触发版本冲突。

总结提示(提供的总结句子)

“Hope for the best, prepare for the worst”
→ 假设不会冲突,但需要做好异常处理。

ODB uses object versioning
→ ODB 利用 version 字段实现乐观锁。

Works best for low to medium contention levels
→ 适用于“写冲突不频繁”的应用场景。

错误处理建议(建议添加)

你可以加上冲突处理逻辑:

try {transaction t (db.begin ());db.update(b);t.commit();
} catch (const odb::object_changed& e) {std::cerr << "The bug was modified by someone else." << std::endl;// 可以重新加载或提示用户冲突
}

ODB 中使用“乐观并发控制”(Optimistic Concurrency)类的完整声明与使用方式。下面是对其的详细理解与解释:

一、什么是 #pragma db object optimistic

这个指令用于声明一个对象为启用乐观并发控制的持久化类

#pragma db object optimistic
class bug
{#pragma db id autounsigned long long id_;#pragma db versionunsigned long long version_;  // 用于并发检测status status_;std::string summary_;std::string description_;
};

关键点:

  • optimistic 标志告诉 ODB 对此类使用版本检测。
  • #pragma db version 声明了用于版本比较的字段,通常是 unsigned intunsigned long long
  • 每次更新会自动检查该字段是否匹配,匹配才更新。

二、使用方式与异常处理

你展示的使用代码如下:

for (bool done (false); !done;)
{cout << "current status: " << b->status() << endl<< "enter new status: ";cin >> s;b->status(s);transaction t (db.begin());try {db.update(b);     // 尝试提交更改done = true;      // 成功,退出循环}catch (const odb::object_changed&) {db.reload(b);     // 有人已修改此对象 → 重新加载}t.commit();
}

流程解析:

  1. 显示当前状态,接受用户输入的新状态。
  2. 启动一个事务,尝试更新数据库。
  3. 如果没人更改过此记录,更新成功 → 退出循环。
  4. 如果被别人修改过(版本号不匹配),ODB 抛出 object_changed 异常 → 调用 db.reload(b) 重新加载,继续重试。

总结:

项目说明
#pragma db object optimistic声明此类启用乐观并发控制
#pragma db version指定版本字段,ODB会使用它检测冲突
异常类odb::object_changed 表示版本冲突
典型场景多人可能同时编辑数据,但冲突概率较低
优点无需数据库锁,性能高,响应快
缺点冲突时需回退或重试,适用于低并发写的系统

ODB 对象关系映射中“多态继承(Polymorphism)”的实现机制。下面是详细的解析和理解:

一、类继承结构

class issue
{unsigned long long id_;status status_;std::string summary_;std::string description_;
};class bug: public issue
{std::string platform_;
};class feature: public issue
{unsigned int votes_;
};

这是一个典型的多态类层次结构

  • issue 是基类。
  • bugfeature 是两个派生类。

二、数据库表结构对应(多表继承)

CREATE TABLE issue (id BIGSERIAL NOT NULL PRIMARY KEY,typeid TEXT NOT NULL,status INTEGER NOT NULL,summary TEXT NOT NULL,description TEXT NOT NULL
);CREATE TABLE bug (id BIGINT NOT NULL PRIMARY KEY,platform TEXT NOT NULL,CONSTRAINT id_fk FOREIGN KEY(id) REFERENCES issue(id)
);CREATE TABLE feature (id BIGINT NOT NULL PRIMARY KEY,votes INTEGER NOT NULL,CONSTRAINT id_fk FOREIGN KEY(id) REFERENCES issue(id)
);

理解点:

  • issue 是主表,存储所有共有字段。
  • bugfeature 是从表,通过外键引用 issue.id
  • 每个对象会存在两张表中(主表 + 子表)。
  • typeid 是内部使用的字段,由 ODB 自动维护,用来记录具体子类型(如 "bug""feature")。

三、ODB 如何使用这种结构支持多态性?

1. 映射为多态对象:

你需要使用:

#pragma db object polymorphic
class issue { ... };#pragma db object
class bug: public issue { ... };#pragma db object
class feature: public issue { ... };

2. 查询时使用基类指针:

std::shared_ptr<issue> i = db.load<issue> (id);// 根据 typeid 自行决定 i 实际是 bug 还是 feature
if (std::shared_ptr<bug> b = std::dynamic_pointer_cast<bug>(i))std::cout << b->platform_ << std::endl;

ODB 在内部通过 typeid 字段决定实际对象类型,并完成动态加载。

总结表格:

项目描述
基类issue,声明为 polymorphic
派生类bug, feature,继承 issue
存储结构多表继承:主表存共享字段,子表存特有字段
ODB 支持自动使用 typeid 判定真实类型并加载
使用方式查询返回 shared_ptr<issue>,可动态转换成子类

ODB 中声明和使用多态(polymorphic)类 的完整流程。下面逐步进行详细解析和“理解”总结:

一、声明多态类(polymorphic

基类:issue

#pragma db object polymorphic
class issue
{
public:virtual ~issue () = 0; // 必须是抽象类才能多态#pragma db id autounsigned long long id_;status status_;std::string summary_;std::string description_;
};

说明:

  • #pragma db object polymorphic:告诉 ODB 这是一个多态基类
  • virtual ~issue() = 0;:C++ 要求多态基类必须有虚析构函数(哪怕纯虚)。
  • 成员字段映射到 issue 表的公共字段。

派生类:bugfeature

#pragma db object
class bug : public issue
{std::string platform_;
};#pragma db object
class feature : public issue
{unsigned int votes_;
};

说明:

  • 不需要 polymorphic,因为继承了 issue
  • 每个派生类都有自己独立的数据库表(以 id 为主键并作为外键关联 issue 表)。

std::shared_ptr<issue> i(new bug(...));
transaction t(db.begin());
db.persist(i);     // 保存 bug
i->status(confirmed);
db.update(i);      // 更新 bug
db.reload(i);      // 重新加载 bug
t.commit();

说明:

  • 即使是通过 issue* 保存的对象,只要是 bug 实例,ODB 也能正确存储到两个表中。
  • 这是典型的运行时多态行为 + ORM 映射

三、查询多态对象

typedef odb::query<issue> query;
transaction t(db.begin());std::shared_ptr<issue> i = db.load<issue>(id); // 可加载 bug 或 featurefor (const issue& i: db.query<issue>(query::status == open)) {// i 是 bug 或 feature,实际类型由 ODB 确定
}db.query<issue>(query::status == open);    // 查询所有(多态)
db.query<bug>(query::status == open);      // 仅查询 bug
db.query<feature>(query::status == open);  // 仅查询 featuret.commit();

说明:

  • 查询 issue 会返回所有派生类对象(由 typeid 判定类型)。
  • 可以安全地使用 dynamic_cast 判断对象实际类型。
  • 可选择性查询 bugfeature 子类表。

总结

概念说明
#pragma db object polymorphic声明为多态基类
虚析构函数必须有,允许 RTTI 和安全释放
存储结构多表继承,主表为基类,子表保存派生特有字段
查询方式可统一查 issue,也可单查 bug / feature
类型识别ODB 自动维护 typeid 字段决定派生类类型

ODB 中的批量操作(Bulk Operations)和相关异常处理机制,下面进行逐条解释和总结以帮助你理解。

1. Bulk Operations(批量操作)

基本函数原型:

template <typename I>
void persist(I begin, I end);template <typename I>
void update(I begin, I end);template <typename I>
void erase(I begin, I end);

理解:

这些模板函数允许你对一批对象进行持久化、更新或删除。例如:

std::vector<bug> bugs = {...};
db.persist(bugs.begin(), bugs.end());   // 批量插入

这种方式比一个个插入更高效,尤其是当对象数量较多时。

2. 设置每批最大记录数(bulk()

#pragma db object oracle:bulk(5000) mssql:bulk(7000)
class bug
{...
};

理解:

  • 对于不同数据库后端(如 Oracle、SQL Server),可以指定一次批量操作最多处理多少条记录。
  • bulk(n) 表示一次操作 最多插入/更新/删除 n 条记录,超过会自动分批处理。

3. Bulk Exceptions(批量异常)

catch (const multiple_exceptions& mex)
{for (const auto& e: mex){cerr << "exception at " << e.position();try{throw e.exception(); // 抛出对应异常}catch (const odb::...){}catch (const odb::...){}}
}

理解:

  • 如果某条记录出错,ODB 抛出 multiple_exceptions,里面包含每个失败记录的位置和具体异常。
  • 使用 e.position() 可以知道哪个对象出错(在容器中的位置)。
  • e.exception() 是对具体异常对象的包装,可以重新抛出再 catch。

总结表

特性描述
persist(begin, end)批量插入对象
update(begin, end)批量更新对象
erase(begin, end)批量删除对象
#pragma db ... bulk(n)设置最大每批记录数
multiple_exceptions捕获批量操作中多个异常
e.position()异常对应的容器索引
e.exception()捕获并处理具体异常

Pimpl Idiom(Pointer to Implementation)在 ODB 中的使用。以下是对其概念和你提供代码的完整解释:

什么是 Pimpl Idiom(实现隐藏技术)

Pimpl(Pointer to Implementation) 是 C++ 中的一种设计模式,用来隐藏类的实现细节,从而降低编译依赖和提高封装性。

结构简述:

class bug
{
public:// 公有接口unsigned long long id() const;void id(unsigned long long);const std::string& summary() const;void summary(std::string);private:class impl;                      // 前向声明std::unique_ptr<impl> pimpl_;   // 指向实际实现的智能指针
};

ODB 与 Pimpl Idiom

在使用 ODB 进行对象-关系映射时,Pimpl 可以用来将数据库映射字段与实际的数据实现解耦

说明:

  • #pragma db object 告诉 ODB 这是一个可持久化对象。
  • 但是类中并没有 id_summary_ 这样的字段,而是通过 getter/setter 操作 pimpl_ 中的数据。
  • 实际数据保存在 impl 这个私有类里。

为什么在 ODB 中使用 Pimpl

目的说明
实现隐藏不暴露类的数据成员,保护实现细节
降低依赖头文件中不需要包含 <string> 等复杂类型
编译隔离修改 impl 不会导致依赖 bug 类的文件重新编译
数据映射可以通过 getter/setter 实现和数据库字段的映射

注意事项:

在使用 Pimpl Idiom 的类中,ODB 只能访问到通过 getter/setter 提供的字段,因此你必须为需要映射的成员提供合适的访问函数。

如果你使用的是这种方式,还要通过额外的配置告诉 ODB 如何映射 getter/setter 到数据库字段(通常用 #pragma db member(...) 来实现,例子如下)。

进阶使用示例(带映射):

class bug
{
public:unsigned long long id() const;void id(unsigned long long);const std::string& summary() const;void summary(const std::string&);private:class impl;std::unique_ptr<impl> pimpl_;
};// 配合 pragma 指定映射方式
#pragma db object
#pragma db member(id) access(id)
#pragma db member(summary) access(summary)
class bug
{ ... };

总结

项目内容
技术名Pimpl Idiom
用途隐藏类的实现细节(尤其是数据成员)
ODB 作用保持接口简洁,分离数据库字段和实现
实现方式将实际成员封装在 impl 中,通过 getter/setter 暴露
ODB 要求使用 #pragma db member(...) access(...) 明确告诉 ODB 如何访问字段

制。

你这段是关于 ODB 中的 Virtual Data Members(虚拟数据成员) 的用法。以下是逐步详细解释,帮助你完全理解其机制与用途。

什么是 Virtual Data Members(虚拟数据成员)

虚拟数据成员 允许你将类的成员函数(getter/setter)映射到数据库字段,而不是直接映射到类中的成员变量。

示例解释:

#pragma db object
class bug
{
public:unsigned long long id () const;void id (unsigned long long);const std::string& summary () const;void summary (std::string);private:class impl;//  将 getter/setter 视为虚拟成员并绑定到数据库字段#pragma db member(id) virtual(unsigned long long)#pragma db member(summary) virtual(std::string)//  ODB 不处理这个成员(它只是用来存储实际数据)#pragma db transientstd::unique_ptr<impl> pimpl_;
};

每行含义解析

代码含义
#pragma db object声明该类为可持久化对象
#pragma db member(id) virtual(...)id() / id(...) getter/setter 与数据库字段绑定
#pragma db member(summary) virtual(...)同样绑定 summary() 与字段
#pragma db transient告诉 ODB 忽略 pimpl_,它不直接映射数据库
std::unique_ptr<impl> pimpl_使用 Pimpl Idiom 保存实际数据

使用场景

适用情况原因
使用 Pimpl 模式数据不在主类中而在 impl 中,需要通过函数访问
增加封装性不暴露任何成员变量给外部或 ODB
灵活控制访问可以在 getter/setter 中添加逻辑,比如缓存或验证

对比:普通成员 vs 虚拟成员

类型映射方式代码复杂度封装性
普通成员ODB 直接映射变量简单
虚拟成员ODB 通过函数映射变量稍复杂高(适用于大型/模块化项目)

小提醒:

  • 如果你不使用 #pragma db transient,ODB 会尝试序列化 pimpl_,这通常会失败。
  • Getter/Setter 的命名风格必须符合常规(或用显式绑定方式指明)。

总结

项目内容
功能通过 getter/setter 实现数据库字段映射
用途支持实现隐藏(如 Pimpl)和更高封装性
关键语法#pragma db member(xxx) virtual(type)
注意搭配 #pragma db transient 使用,避免 impl 被 ODB 管理

ODB 的 Accessor/Modifier Expressions(访问器/修改器表达式) 的示例,主要用于将复杂成员类型(如结构体)映射到类的多个 getter/setter 成员函数上。

下面是详细解释:

代码结构回顾

1. 值类型结构体 name

#pragma db value
struct name
{name (std::string, std::string);std::string first, last;
};
  • #pragma db value:声明该类型为 值类型(value type),不是数据库对象(object)。
  • firstlast:表示用户的名字组成部分。

2. 对象类 user

#pragma db object
class user
{const std::string& first () const;const std::string& last () const;void first (std::string);void last (std::string);private:// 映射表达式:#pragma db get(name (this.first (), this.last ())) \set(this.first ((?).first); this.last ((?).last))name name_;
};

Accessor / Modifier Expressions 是什么?

ODB 允许你使用 get(...)set(...) 表达式将对象成员的访问与类的 getter/setter 进行绑定。

解释这两行:

#pragma db get(name (this.first (), this.last ())) \set(this.first ((?).first); this.last ((?).last))
get:
  • 表达式:name(this.first(), this.last())
  • 意思是:当 ODB 需要获取 name_ 的值时,实际调用的是 first()last() 组合起来构建一个 name 实例。
set:
  • 表达式:this.first((?).first); this.last((?).last)
  • 意思是:当 ODB 设置 name_ 的值时,它会从 name 类型的变量中提取 firstlast,并传给 first(...)last(...) 函数。

整体作用

你不希望 ODB 直接访问 name_,而是通过封装的接口(如 first()last())进行数据访问与修改。这样你可以:

  • 保持封装性
  • 在 getter/setter 中添加逻辑(验证、转换、缓存等)
  • 灵活映射嵌套对象或结构体

小细节

内容
?表示传入值(即要设置的整个 name 对象)
this.first()调用当前对象的 getter
this.first(...)调用 setter 设置值

使用场景

  • 你有嵌套结构体但不希望直接暴露它
  • 你有多个字段组成一个逻辑属性(如姓名由 first/last 构成)
  • 你需要使用 getter/setter 而不是 public 成员变量

总结

内容
功能将结构体类型映射到多个访问器函数
优势更好的封装性与灵活性
关键语法#pragma db get(...) set(...)
示例核心get(name(...))set(...) 使用访问器而不是变量

你给出的这段代码展示了如何在 ODB 中为类定义索引(Index)。下面是逐句的解释,帮助你完全理解:

示例代码解析

#pragma db object
class user
{...#pragma db idstd::string email_;                  // 主键(唯一标识用户)#pragma db indexstd::string first_;                 // 默认索引字段(单字段索引)std::string last_;                  // 普通字段#pragma db index("name_i") \unique                            // 联合唯一索引(first_ + last_)method("BTREE")                   // 使用 BTREE 索引方法(适用于 MySQL、PostgreSQL)members(first_, last_)            // 索引作用于 first_ 和 last_
};

各部分含义

语法指令说明
#pragma db object将类声明为数据库对象,ODB 会为它生成持久化代码
#pragma db id声明主键字段(如 email_
#pragma db index(默认)为该字段建立默认索引(这里是 first_
#pragma db index("name_i")定义命名为 "name_i" 的索引
unique这个索引是唯一索引,不能有重复值
method("BTREE")使用 BTREE 作为索引方法(数据库相关)
members(first_, last_)定义联合索引,包含 first_last_ 两个字段

数据库层面的结果(以 PostgreSQL 为例)

会自动生成如下 SQL:

CREATE UNIQUE INDEX name_i ON user USING BTREE (first_, last_);

以及:

CREATE INDEX ON user (first_);

使用场景说明

场景示例
提高查询性能SELECT * FROM user WHERE first_ = 'John'; 会用到索引
强制业务唯一性联合唯一索引确保 first_ + last_ 组合不重复
排序优化BTREE 索引在范围查找与排序中效果很好

总结

项目说明
#pragma db index建立单字段索引
#pragma db index("name_i") + members(...)创建联合索引
unique确保索引字段组合唯一
method("BTREE")控制底层数据库索引结构

ODB 中的 Prepared and Cached Queries(预处理和缓存查询) 示例。下面是详细解释,帮助你理解其工作机制和使用方式。

示例代码回顾

typedef odb::query<bug> query;
typedef odb::prepared_query<bug> prep_query;transaction t (db.begin ());status s;// 查询表达式:动态绑定参数 s
query q (query::status == query::_ref (s));// 创建预处理查询(带名称)
prep_query pq (db.prepare_query<bug>("bug-query", q));// 第一次执行:s == open
s = open;
pq.execute();// 第二次执行:s == confirmed
s = confirmed;
pq.execute();t.commit();

关键概念解释

项目含义
odb::query<T>构造数据库查询的表达式
odb::prepared_query<T>表示一个预处理后的查询对象,可以重复执行
query::_ref(s)创建对外部变量 s 的引用,查询中使用这个引用的值
db.prepare_query<bug>("bug-query", q)将查询 q 作为 "bug-query" 的预处理语句注册到数据库
pq.execute()执行查询,查询表达式自动读取当前 s 的值

工作机制

  1. 创建一个参数化的查询表达式(status == ?)。
  2. 使用 prepare_query() 将其编译为数据库预处理语句,绑定变量 s
  3. 每次调用 pq.execute(),都会使用当前的 s 值重新执行。
  4. 整个查询是 高效 的,因为语句只编译一次。

优点

优点描述
更快语句只准备一次,执行多次,提升性能
参数安全使用引用绑定方式,可以防止 SQL 注入
重复利用一个预处理查询可多次使用,只需改变变量值

使用场景

  • 查询条件重复但值不同的情况(如分页、状态切换)
  • 高性能系统,避免 SQL 重复解析
  • 需要缓存查询计划的数据库,如 PostgreSQL、Oracle

小建议

  1. 事务管理:准备和执行都要在同一个 transaction 中完成。
  2. 类型一致prepared_query<T> 必须与 query<T> 匹配。
  3. 命名缓存:给查询命名可以使其被数据库后端缓存复用。

相关文章:

  • 【Python高阶】面向对象
  • VMvare 创建虚拟机 安装CentOS7,配置静态IP地址
  • |从零开始的Pyside2界面编程|绘图、布局及页面切换
  • 2.2HarmonyOS NEXT高性能开发技术:编译优化、内存管理与并发编程实践
  • tomcat服务器以及接受请求参数的方式
  • 尚硅谷redis7 93-97 springboot整合reids之总体概述
  • LLM推理相关指标
  • python分配方案数 2023年信息素养大赛复赛/决赛真题 小学组/初中组 python编程挑战赛 真题详细解析
  • Go 语言的 GC 垃圾回收
  • 核心机制三:连接管理(三次握手)
  • Day08
  • Hbase
  • Web开发实战:HTML+CSS+JS期末复习全梳理
  • 设计模式——抽象工厂设计模式(创建型)
  • BFD 基本工作原理与实践:如何与 VRRP 联动实现高效链路故障检测?
  • 使用PowerBI个人网关定时刷新数据
  • Springcloud Alibaba自定义负载均衡详解
  • ESP8266常用指令
  • Kerberos面试内容整理-会话密钥的协商与使用
  • 华为OD机试真题——生成哈夫曼树(2025A卷:100分)Java/python/JavaScript/C/C++/GO六种最佳实现
  • 网站标签span/百度关键词seo排名优化
  • 外包公司做的网站/小广告清理
  • 一个网站如何进行推广宣传/网站页面优化方案
  • 上海服装品牌网站建设/跨境电商平台注册开店流程
  • 贵州遵义企业公司网站建设/百度营销推广靠谱吗
  • 网站性能/互联网推广引流是做什么的