ODB 2.5 从安装到实战:C++ ORM 工具手把手教程
作为一名 C++ 开发者,你是否还在为手写 SQL、处理数据库连接、维护表结构与代码实体的映射关系而头疼?ODB(Object-Relational Mapping)作为 Code Synthesis 推出的 C++ ORM 工具,能帮我们把 C++ 类直接映射到数据库表,彻底摆脱重复的数据库操作代码。这篇文章会从 ODB 2.5 的安装踩坑,到实战代码解析,带你完整掌握这个工具 —— 文末还附了可直接复用的 DAO 层封装,新手也能快速上手。
一、先搞懂:ODB 到底能帮我们做什么?
简单说,ODB 是 “C++ 类” 和 “数据库表” 之间的桥梁:
- 不用手写 CREATE TABLE,ODB 能根据 C++ 类自动生成数据库 schema;
- 不用写 INSERT/UPDATE/SELECT,调用 ODB 的
persist()/update()/query()就能操作数据; - 自动处理 C++ 类型与 MySQL 类型的映射(比如
std::string对应VARCHAR,boost::posix_time对应TIMESTAMP); - 支持事务、连接池,解决多线程下的数据库操作安全问题。
本文用到的示例代码来自 “比特就业课” 的 C++ 聊天系统项目,完整代码可在 Gitee 获取:cpp-chatsystem。
二、ODB 2.5 安装:避坑指南(Linux 环境)
ODB 安装依赖build2编译工具,整个过程大概 3-5 小时(主要耗在网络下载),重点注意版本兼容性和网络超时问题。
2.1 第一步:安装 build2(编译基础)
build2 是 ODB 的依赖工具,用来构建后续的 ODB 编译器和运行时库。注意:build2 版本会更新(比如从 0.17 升到 0.18),安装前先去build2 官网确认最新步骤,这里以 0.17.0 为例:
- 下载并执行安装脚本(在 PowerShell 或终端执行):
# 进入工作目录(比如~/workspace) cd ~/workspace # 下载安装脚本 curl -sSfO https://download.build2.org/0.17.0/build2-install-0.17.0.sh # 执行脚本(默认安装到/usr/local/bin) sh build2-install-0.17.0.sh - 踩坑点 1:网络超时如果执行脚本时卡在 “下载文件” 步骤,提示 “timeout”,给脚本加
--timeout 1800(超时时间设为 30 分钟):
sh build2-install-0.17.0.sh --timeout 1800 2.2 第二步:安装 ODB 编译器(odb-compiler)
ODB 编译器的作用是:把带#pragma db指令的 C++ 类(比如Person),生成数据库操作的支持代码(比如person-odb.cxx)。
- 先装 gcc 插件开发包(注意替换成你系统的 gcc 版本):
# 查看gcc版本:gcc --version,比如我的是gcc-11 sudo apt-get install gcc-11-plugin-dev - 创建构建目录并初始化编译配置:
# 创建odb-build目录,避免污染源码 mkdir odb-build && cd odb-build # 初始化cc编译环境,指定g++为编译器,优化等级O3,安装路径/usr bpkg create -d odb-gcc-N cc \ config.cxx=g++ \ config.cc.coptions=-O3 \ config.bin.rpath=/usr/lib \ config.install.root=/usr/- 编译并安装 ODB 编译器:
# 进入配置好的环境目录 cd odb-gcc-N # 从官方源编译odb(beta版,稳定可用) bpkg build config.install.sudo=sudo odb@https://pkg.cppget.org/1/beta # 测试编译结果(可选,确保没问题) bpkg test odb # 安装odb bpkg install odb- 踩坑点 2:执行 odb --version 提示 “找不到命令”这是因为 ODB 安装到了
/usr/local/bin,但系统 PATH 没包含这个路径。执行以下命令添加(永久生效):
# 把路径写入bashrc sudo echo 'export PATH=${PATH}:/usr/local/bin' >> ~/.bashrc # 立即生效 export PATH=${PATH}:/usr/local/bin # 验证:成功显示版本号则没问题 odb --version # 正确输出示例: # ODB object-relational mapping (ORM) compiler for C++ 2.5.0-b.25 # Copyright (c) 2009-2023 Code Synthesis Tools CC.2.3 第三步:安装 ODB 运行时库
ODB 运行时库是代码里实际调用的 API(比如odb::database、odb::transaction),需要安装核心库、MySQL 驱动和 boost 支持库。
- 创建运行时库的构建目录并初始化:
# 回到odb-build目录 cd .. # 初始化cc环境,指定安装路径和sudo权限 bpkg create -d libodb-gcc-N cc \ config.cxx=g++ \ config.cc.coptions=-O3 \ config.install.root=/usr/ \ config.install.sudo=sudo- 添加 ODB 官方源并安装依赖库:
# 进入libodb-gcc-N目录 cd libodb-gcc-N # 添加ODB的beta源 bpkg add https://pkg.cppget.org/1/beta # 拉取最新包信息 bpkg fetch # 安装核心库(libodb) bpkg build libodb # 安装MySQL驱动(libodb-mysql) bpkg build libodb-mysql # 安装boost支持库(处理boost日期等类型) bpkg build libodb-boost- 统一安装所有库:
bpkg install --all --recursive 2.4 第四步:配置 MySQL(必做)
ODB 需要 MySQL 环境,包括服务端、客户端开发包,以及字符集配置。
- 安装 MySQL 和开发包:
# 安装MySQL服务端 sudo apt install mysql-server # 安装客户端开发包(编译时需要头文件和库) sudo apt install -y libmysqlclient-dev- 配置 MySQL 字符集(避免中文乱码):
# 编辑my.cnf(有哪个路径用哪个) sudo vim /etc/my.cnf 或者 sudo vim /etc/mysql/my.cnf # 在文件末尾添加以下内容: [client] default-character-set=utf8 [mysql] default-character-set=utf8 [mysqld] character-set-server=utf8 bind-address = 0.0.0.0 # 允许远程连接(可选)- 修改 MySQL root 密码(默认 root 密码可能为空或未知):
# 先查看debian-sys-maint用户的密码(MySQL默认维护用户) sudo cat /etc/mysql/debian.cnf # 输出里会有:password = UWcn9vY0NkrbJMRC(复制这个密码) # 用debian-sys-maint登录MySQL sudo mysql -u debian-sys-maint -p # 输入刚才复制的密码,进入MySQL命令行 # 修改root密码(把xxxxxx换成你的密码,比如123456) ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'xxxxxx'; # 刷新权限 FLUSH PRIVILEGES; # 退出 quit- 重启 MySQL 并设置开机启动:
sudo systemctl restart mysql sudo systemctl enable mysql 三、ODB 实战:从代码到运行
接下来我们用一个Person类的例子,完整走一遍 “定义实体→生成代码→编写业务逻辑→编译运行” 的流程。
3.1 第一步:定义持久化类(Person.hxx)
持久化类就是 “能映射到数据库表的 C++ 类”,核心是通过#pragma db指令告诉 ODB 如何映射。
#pragma once #include <string> #include <cstddef> // 用于std::size_t #include <boost/date_time/posix_time/posix_time.hpp> #include <odb/core.hxx> // ODB核心头文件 // 简化boost时间类型的别名 typedef boost::posix_time::ptime ptime; // 关键指令:声明Person是持久化类(对应数据库一张表) #pragma db object class Person { public: // 自定义构造函数(用于创建对象) Person(const std::string &name, int age, const ptime &update) : _name(name), _age(age), _update(update) {} // getter/setter(封装私有成员) void age(int val) { _age = val; } int age() { return _age; } void name(const std::string& val) { _name = val; } std::string name() { return _name; } void update(const ptime &update) { _update = update; } std::string update() { return boost::posix_time::to_simple_string(_update); } private: // 关键:让odb::access成为友元,ODB需要访问私有成员和默认构造函数 friend class odb::access; // ODB要求的默认构造函数(必须私有,避免外部调用) Person () {} // 关键指令:声明_id是主键,auto表示数据库自动生成(自增) #pragma db id auto unsigned long _id; // 普通成员:会自动映射到表的列(列名默认和成员名一致) unsigned short _age; std::string _name; // 关键指令:指定数据库类型为TIMESTAMP,且不允许为空 #pragma db type("TIMESTAMP") not_null boost::posix_time::ptime _update; }; 核心解析:
#pragma db object:告诉 ODB “这个类要映射到数据库表”,表名默认是person(小写);friend class odb::access:ODB 生成代码时需要访问私有成员(比如_id、_update),必须加这个;#pragma db id auto:标记主键,auto让 MySQL 自动生成自增 ID;#pragma db type("TIMESTAMP") not_null:强制指定数据库列类型为TIMESTAMP,且非空(默认 C++ 成员是 NOT NULL)。
3.2 第二步:生成 ODB 支持代码
ODB 编译器会分析Person.hxx里的#pragma db指令,生成 3 个关键文件:
person-odb.hxx:支持代码的头文件;person-odb.cxx:支持代码的实现;person.sql:自动生成的建表 SQL(CREATE TABLE 语句)。
执行以下命令生成:
# 进入代码目录(比如~/workspace/odb-test) cd ~/workspace/odb-test # 生成支持代码 odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time person.hxx命令参数解析:
-d mysql:指定数据库类型为 MySQL;--std c++11:使用 C++11 标准;--generate-query:生成查询相关的代码(支持odb::query);--generate-schema:生成建表 SQL(person.sql);--profile boost/date-time:支持 boost 的日期时间类型(否则 ODB 不认识ptime)。
执行后用ls查看,会看到 4 个文件:person.hxx、person-odb.hxx、person-odb.cxx、person.sql。
3.3 第三步:编写业务代码(test.cc)
这一步我们写实际的数据库操作:创建连接、插入数据、查询数据,核心是用 ODB 的database、transaction、query类。
#include <string> #include <memory> // 用于std::shared_ptr #include <iostream> #include <odb/database.hxx> // ODB核心数据库类 #include <odb/mysql/database.hxx> // MySQL数据库实现 #include <boost/date_time/posix_time/posix_time.hpp> // boost时间 #include "person.hxx" #include "person-odb.hxx" // ODB生成的支持代码 int main() { // 1. 创建MySQL数据库连接(参数:用户名、密码、数据库名、主机、端口、字符集) std::shared_ptr<odb::core::database> db( new odb::mysql::database( "root", // MySQL用户名(刚才改的root) "123456", // MySQL密码(替换成你的) "mytest", // 数据库名(需要先手动创建:mysql> CREATE DATABASE mytest;) "127.0.0.1", // 主机地址 0, // 端口(0表示默认3306) 0, // 未使用参数 "utf8" // 字符集 ) ); if (!db) { std::cerr << "数据库连接失败!" << std::endl; return -1; } // 2. 准备数据:获取当前时间 ptime current_time = boost::posix_time::second_clock::local_time(); Person zhang("小张", 18, current_time); // 创建Person对象 Person wang("小王", 19, current_time); // 3. 插入数据(必须用事务包裹,保证原子性) { // 开启事务 odb::core::transaction t(db->begin()); // 插入数据:persist()会自动生成INSERT语句 size_t zid = db->persist(zhang); // 返回插入的主键ID size_t wid = db->persist(wang); std::cout << "插入成功!小张的ID:" << zid << ",小王的ID:" << wid << std::endl; // 提交事务(不提交则数据不生效) t.commit(); } // 4. 查询数据:查询update时间在[2024-05-22 09:09:39, 2024-05-22 09:13:29]之间的记录 { // 定义查询时间范围 ptime start_time = boost::posix_time::time_from_string("2024-05-22 09:09:39"); ptime end_time = boost::posix_time::time_from_string("2024-05-22 09:13:29"); // 开启事务(查询也建议用事务,避免脏读) odb::core::transaction t(db->begin()); // 定义查询条件:update在start_time和end_time之间 typedef odb::query<Person> query; typedef odb::result<Person> result; // 执行查询:query::update是ODB自动生成的成员查询变量 result res(db->query<Person>(query::update > start_time && query::update < end_time)); // 遍历查询结果 std::cout << "\n查询结果:" << std::endl; for (result::iterator it = res.begin(); it != res.end(); ++it) { std::cout << "姓名:" << it->name() << ",年龄:" << it->age() << ",更新时间:" << it->update() << std::endl; } // 提交事务 t.commit(); } return 0; } 核心解析:
- 数据库连接:
odb::mysql::database的构造参数要和你的 MySQL 配置匹配,注意mytest数据库需要手动创建(CREATE DATABASE mytest;); - 事务:所有写操作(
persist/update/erase)必须用transaction包裹,查询建议用事务 ——ODB 的事务默认是 “读已提交” 隔离级别,避免数据不一致; - 插入数据:
db->persist(zhang)会生成INSERT INTO person (age, name, update) VALUES (18, '小张', '2024-05-22 09:10:00'),返回主键 ID; - 查询数据:
odb::query<Person>是类型安全的查询方式,query::update对应Person类的_update成员,不用手写 SQL,避免注入风险。
3.4 第四步:编译与运行
编译时需要链接 ODB 的核心库、MySQL 驱动和 boost 库,执行以下命令:
- 编译代码:
# 编译test.cc和生成的person-odb.cxx c++ -o test test.cpp person-odb.cxx -lodb-mysql -lodb -lodb-boost链接参数解析:
-lodb-mysql:ODB 的 MySQL 驱动库;-lodb:ODB 核心库;-lodb-boost:ODB 的 boost 类型支持库。
- 踩坑点 3:运行时提示 “找不到 libodb-2.5.0-b.25.so”这是因为 ODB 库安装在
/usr/local/lib,系统加载共享库时没找到。执行以下命令临时生效(永久生效需写入/etc/``ld.so``.conf):
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH- 运行程序:
./test 正确输出示例:
插入成功!小张的ID:1,小王的ID:2 查询结果: 姓名:小张,年龄:18,更新时间:2024-May-22 09:10:00 姓名:小王,年龄:19,更新时间:2024-May-22 09:10:00 去 MySQL 里验证一下,确实能看到数据:
mysql -u root -p # 输入密码后执行 use mytest; select * from person; # 输出: # +----+-----+--------+---------------------+ # | id | age | name | update | # +----+-----+--------+---------------------+ # | 1 | 18 | 小张 | 2024-05-22 09:10:00 | # | 2 | 19 | 小王 | 2024-05-22 09:10:00 | # +----+-----+--------+---------------------+四、ODB 核心知识点:必背手册
4.1 C++ 类型 ↔ MySQL 类型映射表
| C++ 类型 | MySQL 类型 | 默认 NULL 语义 |
|---|---|---|
| bool | TINYINT(1) | NOT NULL |
| char | CHAR(1) | NOT NULL |
| signed char | TINYINT | NOT NULL |
| unsigned char | TINYINT UNSIGNED | NOT NULL |
| short | SMALLINT | NOT NULL |
| unsigned short | SMALLINT UNSIGNED | NOT NULL |
| int | INT | NOT NULL |
| unsigned int | INT UNSIGNED | NOT NULL |
| long / long long | BIGINT | NOT NULL |
| unsigned long / long long | BIGINT UNSIGNED | NOT NULL |
| float | FLOAT | NOT NULL |
| double | DOUBLE | NOT NULL |
| std::string | TEXT/VARCHAR(255) | NOT NULL |
| char[N] | VARCHAR(N-1) | NOT NULL |
| boost::gregorian::date | DATE | NULL |
| boost::posix_time::ptime | DATETIME | NULL |
| boost::posix_time::time_duration | TIME | NULL |
4.2 常用 #pragma db 指令
| 指令 | 作用 |
|---|---|
#pragma db object | 声明类为持久化类(映射到数据库表) |
#pragma db table("tab_name") | 指定类映射的表名(默认是类名小写),比如#pragma db table("t_person") |
#pragma db id | 声明主键,可加auto(自动生成)、sequence(序列)等 |
#pragma db column("col_name") | 指定成员映射的列名,比如#pragma db column("user_name") |
#pragma db type("mysql_type") | 指定数据库列类型,比如#pragma db type("VARCHAR(100)") |
#pragma db not_null | 强制列非空(默认 C++ 成员是 NOT NULL,除非用nullable<T>) |
#pragma db unique | 给列加唯一约束,比如#pragma db unique(避免重复值) |
#pragma db transient | 声明成员不持久化(不映射到数据库列) |
#pragma db index("idx_name") | 给列加索引,比如#pragma db index("idx_person_age") |
4.3 核心类与方法
| 类名 | 作用 | 关键方法 |
|---|---|---|
odb::database | 数据库连接的抽象类 | begin()(开启事务)、persist()(插入) |
odb::mysql::database | MySQL 的具体实现类 | 构造函数(创建连接) |
odb::transaction | 事务管理类 | commit()(提交)、rollback()(回滚) |
odb::query<T> | 类型安全的查询类 | 支持 &&/ |
odb::result<T> | 查询结果集类 | begin()/end()(遍历)、size()(数量) |
odb::nullable<T> | 支持 NULL 的类型封装(比如nullable<int>) | get()(获取值)、operator->(指针访问) |
五、进阶:DAO 层封装(可直接复用)
实际项目中,我们会把数据库操作封装成 DAO(Data Access Object)层,避免代码重复。这里基于前文的Person例子,封装一个通用的MysqlClient(连接池 + 事务)和PersonDao(具体业务操作):
#include <odb/database.hxx> #include <odb/mysql/database.hxx> #include <odb/mysql/connection-pool.hxx> // 连接池头文件 #include <memory> #include <vector> // 1. MySQL客户端工具类(单例,管理连接池和事务) class MysqlClient { public: // 创建数据库连接(带连接池) static std::shared_ptr<odb::database> create( const std::string& user, const std::string& passwd, const std::string& db_name, const std::string& host, int port, size_t max_conn = 3 // 连接池最大连接数 ) { // 初始化连接池 std::unique_ptr<odb::mysql::connection_pool_factory> pool( new odb::mysql::connection_pool_factory(max_conn) ); // 创建MySQL连接 return std::make_shared<odb::mysql::database>( user.c_str(), passwd.c_str(), db_name.c_str(), host.c_str(), port, 0, "utf8", 0, std::move(pool) // 传入连接池 ); } // 开启事务 static std::shared_ptr<odb::transaction> begin_transaction(const std::shared_ptr<odb::database>& db) { return std::make_shared<odb::transaction>(db->begin()); } // 提交事务 static void commit(const std::shared_ptr<odb::transaction>& tx) { tx->commit(); } // 回滚事务 static void rollback(const std::shared_ptr<odb::transaction>& tx) { tx->rollback(); } }; // 2. Person的DAO类(封装具体业务操作) class PersonDao { public: PersonDao( const std::string& user, const std::string& passwd, const std::string& db_name, const std::string& host, int port ) : _db(MysqlClient::create(user, passwd, db_name, host, port)) {} // 插入Person bool insert(const Person& person) { try { auto tx = MysqlClient::begin_transaction(_db); _db->persist(person); MysqlClient::commit(tx); return true; } catch (const std::exception& e) { std::cerr << "插入失败:" << e.what() << std::endl; return false; } } // 根据姓名查询Person std::vector<Person> query_by_name(const std::string& name) { std::vector<Person> res; try { auto tx = MysqlClient::begin_transaction(_db); typedef odb::query<Person> query; typedef odb::result<Person> result; // 查询条件:name等于传入值 result rs = _db->query<Person>(query::name == name); for (auto& p : rs) { res.push_back(p); } MysqlClient::commit(tx); } catch (const std::exception& e) { std::cerr << "查询失败:" << e.what() << std::endl; } return res; } // 根据ID删除Person bool delete_by_id(unsigned long id) { try { auto tx = MysqlClient::begin_transaction(_db); // 先查询再删除(也可以用erase_query) Person p; if (_db->load(id, p)) { // load()根据ID查询 _db->erase(p); // erase()删除 MysqlClient::commit(tx); return true; } MysqlClient::commit(tx); return false; } catch (const std::exception& e) { std::cerr << "删除失败:" << e.what() << std::endl; return false; } } private: std::shared_ptr<odb::database> _db; // 数据库连接 }; // 使用示例 int main() { // 初始化DAO PersonDao dao("root", "123456", "mytest", "127.0.0.1", 0); // 插入 ptime now = boost::posix_time::second_clock::local_time(); Person li("小李", 20, now); dao.insert(li); // 查询 auto persons = dao.query_by_name("小李"); for (auto& p : persons) { std::cout << "姓名:" << p.name() << ",年龄:" << p.age() << std::endl; } // 删除(假设ID是3) dao.delete_by_id(3); return 0; }封装优势:
- 连接池:
odb::mysql::connection_pool_factory管理连接,避免频繁创建 / 销毁连接的开销; - 事务封装:
begin_transaction/commit/rollback统一管理,减少重复代码; - 业务与数据分离:DAO 层只负责数据库操作,业务层不用关心连接和事务细节。
六、总结与参考资料
ODB 作为 C++ 的 ORM 工具,虽然学习成本比 Java 的 MyBatis 高,但能极大提升 C++ 项目的数据库操作效率 —— 尤其是中大型项目,避免手写 SQL 带来的维护成本和错误。
关键回顾:
- 安装时注意
build2版本和网络超时,odb命令找不到就配置 PATH; - 持久化类必须加
#pragma db object和友元odb::access; - 所有数据库操作(尤其是写操作)必须用事务包裹;
- 运行时找不到共享库就配置
LD_LIBRARY_PATH。
官方资料:
- ODB 官网:https://codesynthesis.com/products/odb/
- ODB 手册:https://codesynthesis.com/products/odb/doc/manual.xhtml
- build2 安装指南:https://build2.org/install.xhtml#unix
如果在使用中遇到问题,建议先查官方手册 —— 大部分坑都能在手册里找到解决方案。动手试试吧,用 ODB 重构你的 C++ 数据库代码!