跳到主要内容
C++ ODB ORM 实战指南:从环境搭建到数据库映射 | 极客日志
C++
C++ ODB ORM 实战指南:从环境搭建到数据库映射 C++ ODB ORM 库通过编译期代码生成实现对象与数据库表的映射,无需运行时反射。本文涵盖 ODB 的安装配置、核心指令用法、视图定义及完整 CRUD 示例。重点讲解 #pragma db 注解规范、多表关联查询以及事务管理流程,帮助开发者快速上手基于 MySQL 的 C++ 持久化开发,避免手写 SQL 带来的维护成本。
kaikai 发布于 2026/3/26 更新于 2026/7/6 37 浏览C++ ODB ORM 实战指南
ODB 是一个针对 C++ 的对象关系映射(ORM)库,它允许开发者以面向对象的方式操作数据库。通过它将 C++ 对象与数据库表进行映射,从而避免直接编写 SQL 语句,简化数据库操作。
核心特点
对象 - 关系映射 :将 C++ 类映射到数据库表,类的成员变量映射到表的字段。对象的创建、修改、删除等操作会自动转换为对应的数据库操作(如 INSERT、UPDATE、DELETE)。
代码生成机制 :ODB 不依赖运行时反射(C++ 本身不支持),而是通过编译期代码生成实现映射。开发者使用特殊的注解(如 #pragma db object)标记需要持久化的类,然后通过 ODB 编译器生成与数据库交互的代码。
支持多数据库 :兼容主流数据库,如 MySQL、PostgreSQL、SQLite、Oracle 等。切换数据库时无需修改核心业务代码,只需调整配置。
查询能力 :提供类似 SQL 的查询接口(通过 C++ 表达式构建),例如通过 query 类组合条件,实现复杂查询逻辑,避免手写 SQL。
事务支持 :内置事务管理机制,确保数据库操作的原子性、一致性、隔离性和持久性(ACID)。
框架安装
1. 安装 build2
首先安装构建工具链。在 Linux 环境下执行:
curl -sSfO https://download.build2.org/0.17.0/build2-install-0.17.0.sh
sh build2-install-0.17.0.sh
2. 安装 odb-compiler
注意这里的 gcc 版本需要根据实际环境调整。以 gcc-11 为例:
sudo apt-get install gcc-11-plugin-dev
mkdir odb-build && cd odb-build
bpkg create -d odb-gcc-N cc \
config.cxx=g++ \
config.cc.coptions=-O3 \
config.bin.rpath=/usr/lib \
config.install.root=/usr/ \
config.install.sudo=sudo
cd odb-gcc-N
bpkg build odb@https://pkg.cppget.org/1/beta
bpkg test odb test odb-2.5.0-b.25+1/tests/testscript{testscript} tested odb/2.5.0-b.25+1
bpkg install odb
如果提示找不到 odb 命令,请添加环境变量:
sudo echo 'export PATH=${PATH}:/usr/local/bin' >> ~/.bashrc
export PATH=${PATH} :/usr/local/bin
odb --version
3. 安装运行时库及依赖
cd ..
bpkg create -d libodb-gcc-N cc \
config.cxx=g++ \
config.cc.coptions=-O3 \
config.install.root=/usr/ \
config.install.sudo=
libodb-gcc-N
bpkg add https://pkg.cppget.org/1/beta
bpkg fetch
bpkg build libodb
bpkg build libodb-mysql
bpkg build libodb-boost
bpkg install --all --recursive
sudo
cd
常见操作
核心头文件 在 C++ 中,要使用 ODB 将类声明为持久化类,需要包含 ODB 的核心头文件,并使用 #pragma db object 指令。
#include <cstddef>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>
类型映射 通过指令 #pragma 声明 C++ 类与数据库表之间的映射关系,必须放在它们所描述的 C++ 实体(类、数据成员、访问器)之前。
object:表示该类将被映射到数据库。
table():默认生成的表名就是类名,使用该参数可以指定表名。
id:表示该成员将被作为主键。
auto:表示该成员有自增属性。
unique:表示唯一键索引字段。
index:为该字段创建普通索引。
null / not_null:控制字段是否允许为空。
default():指定默认值。
column():指定该成员映射到数据库表后的列名。
type():指定该成员映射到数据库表中的字段类型。
#pragma db object table("person" )
class Person {
public :
private :
friend class odb ::access;
#pragma db id auto
unsigned int _id;
#pragma db column("user_age" ) default(18)
unsigned short _age;
#pragma db not_null
std::string _name;
#pragma db unique type("varchar(20)" )
std::string _phone;
};
odb -d mysql --generate-query --generate-schema --profile boost/date-time person.hxx
执行后会自动生成一个 SQL 脚本文件(如 person.sql)。你可以查看该文件内容,并通过以下命令构建 MySQL 数据库表:
mysql -u root -p < person.sql
作用域 :#pragma db 只影响紧随其后的类、数据成员或访问器。
顺序 :多个属性可以写在同一行,也可以分开写。
访问控制 :ODB 需要访问私有成员,通常使用 friend class odb::access。
列名规则 :数据库表的列名默认为成员变量名,但会去掉成员名的前缀下划线。
视图映射 ODB 视图是一个只读的、虚拟的投影,它基于一个或多个持久化类(数据库表),通过查询组合出你需要的数据结构。视图本身不对应数据库中的实际表,而是在查询时动态生成的结果集。
这种视图使用 ODB 的查询语言来定义表之间的关联:
#pragma db view object(Student)\
object(Classes: Student::classes_id == Classes::id)
struct StudentClassView {
#pragma db column(Student::name) std::string student_name;
#pragma db column(Student::age) unsigned short student_age;
#pragma db column(Classes::name) std::string class_name;
};
SELECT Student.name, Student.age, Classes.name
FROM Student JOIN Classes ON Student.classes_id = Classes.id
#pragma db view query("SELECT s.name, s.age, c.name FROM student s "
"JOIN classes c ON s.classes_id = c.id "
"WHERE s.age > ?" )
struct AdultStudentView {
std::string student_name;
unsigned short student_age;
std::string class_name;
};
使用 query("原生 SQL") 定义。
? 是参数占位符,可以在查询时传入具体值。
适合复杂的、对象关系无法表达的查询。
ODB 类与接口
odb::database 类 :实现数据库的增删改查操作,包括构造函数、事务管理、查询操作及单对象操作。
odb::result 类 :用于处理查询结果集。
测试示例 完整的数据库操作流程通常包括:构造连接池对象 -> 构造数据操作 database 对象 -> 获取事务对象开启事务 -> 执行数据库操作 -> 提交事务(若无添加,在销毁前会进行事务回滚)。
模型定义 (student_classes.hxx) #pragma once
#include <string>
#include <cstddef>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>
class Student {
public :
Student () : _id(0 ), _sn("" ), _name("" ), _age(0 ), _classes_id(0 ) {}
Student (unsigned long id, const std::string& sn, const std::string& name,
unsigned short age, unsigned long classes_id)
: _id(id), _sn(sn), _name(name), _age(age), _classes_id(classes_id) {}
private :
friend class odb ::access;
unsigned long _id;
std::string _sn;
std::string _name;
unsigned short _age;
unsigned long _classes_id;
};
class Classes {
public :
Classes () : _id(0 ), _name("" ) {}
Classes (const std::string& name) : _id(0 ), _name(name) {}
private :
friend class odb ::access;
unsigned long _id;
std::string _name;
};
#pragma db view object(Student)\
object(Classes classes: Student._classes_id == Classes._id)\
query((?))
struct classes_student {
#pragma db column(Student::_id) unsigned long _id;
#pragma db column(Student::_sn) std::string _sn;
#pragma db column(Student::_name) std::string _name;
#pragma db column(Student::_age) unsigned short _age;
#pragma db column(Classes::_name) std::string _classes_name;
};
#pragma db view query("select name from Student " + (?))
struct all_name {
std::string _name;
};
测试代码 (test.cc) #include <gflags/gflags.h>
#include <odb/database.hxx>
#include <odb/mysql/database.hxx>
#include "student_classes.hxx"
#include "student_classes-odb.hxx"
DEFINE_string (host,"127.0.0.1" ,"主机号" );
DEFINE_uint32 (port,3306 ,"端口号" );
DEFINE_string (db,"qsy_test" ,"mysql 数据库名" );
DEFINE_string (user,"root" ,"mysql 用户名" );
DEFINE_string (pswd,"your_password" ,"mysql 用户密码" );
DEFINE_string (cset,"utf8" ,"mysql 客户端字符集" );
DEFINE_int32 (max_pool,3 ,"最大的连接池数" );
void insert_student (odb::mysql::database &db) {
try {
odb::transaction trans (db.begin()) ;
Student s1 (1 ,"张三" ,18 ,1 ) ;
Student s2 (2 ,"李四" ,19 ,1 ) ;
Student s3 (3 ,"王五" ,17 ,1 ) ;
Student s4 (4 ,"赵六" ,21 ,2 ) ;
Student s5 (5 ,"田七" ,20 ,2 ) ;
Student s6 (6 ,"孙八" ,16 ,2 ) ;
Student s7 (7 ,"罗九" ,26 ,2 ) ;
db.persist (s1);
db.persist (s2);
db.persist (s3);
db.persist (s4);
db.persist (s5);
db.persist (s6);
db.persist (s7);
trans.commit ();
} catch (std::exception &e) {
std::cout << "数据插入失败:" << e.what () << std::endl;
}
}
void insert_classes (odb::mysql::database& db) {
try {
odb::transaction trans (db.begin()) ;
Classes c1 ("1 班" ) ;
Classes c2 ("2 班" ) ;
db.persist (c1);
db.persist (c2);
trans.commit ();
} catch (const std::exception& e) {
std::cout << "数据插入失败" << e.what () << '\n' ;
}
}
void remove_student (odb::mysql::database& db) {
try {
odb::transaction trans (db.begin()) ;
typedef odb::query<Student> query;
db.erase_query <Student>(query::id == 4 );
trans.commit ();
} catch (const std::exception &e) {
std::cerr << "删除失败:" << e.what () << '\n' ;
}
}
Student select_student (odb::mysql::database &db) {
Student ret;
try {
odb::transaction trans (db.begin()) ;
typedef odb::query<Student> query;
typedef odb::result<Student> result;
result r (db.query<Student>(query::name == "张三" )) ;
if (r.size () != 1 ) {
std::cout << "查询失败" << std::endl;
return Student ();
}
ret = *r.begin ();
trans.commit ();
} catch (const std::exception &e) {
std::cerr << "查询失败" << e.what () << '\n' ;
}
return ret;
}
void select_student_classes (odb::mysql::database& db) {
try {
odb::transaction trans (db.begin()) ;
typedef odb::query<struct classes_student > query;
typedef odb::result<struct classes_student > result;
result r (db.query<struct classes_student>(query::classes::id == 1 )) ;
for (auto it = r.begin (); it != r.end (); it++) {
std::cout << it->name << " " ;
std::cout << it->age << " " ;
std::cout << it->sn << " " ;
std::cout << it->classes_name << " " ;
std::cout << std::endl;
}
trans.commit ();
} catch (const std::exception &e) {
std::cout << "查询失败:" << e.what () << std::endl;
}
}
void update_student (odb::mysql::database &db, Student &stu) {
try {
odb::transaction trans (db.begin()) ;
db.update (stu);
trans.commit ();
} catch (const std::exception &e) {
std::cerr << "更新失败:" << e.what () << '\n' ;
}
}
int main (int argc, char * argv[]) {
google::ParseCommandLineFlags (&argc, &argv, true );
std::unique_ptr<odb::mysql::connection_pool_factory> cpf (
new odb::mysql::connection_pool_factory(FLAGS_max_pool, 0 )) ;
odb::mysql::database db (FLAGS_user, FLAGS_pswd, FLAGS_db, FLAGS_host,
FLAGS_port, "" , FLAGS_cset, 0 , std::move(cpf)) ;
select_student_classes (db);
return 0 ;
}
Makefile test : test.cc student_classes-odb.cxx
c++ -g $^ -o $@ -lodb-mysql -lodb -lodb-boost -lgflags
以上就是 ODB 的基本用法和完整示例。在实际开发中,记得根据项目需求调整连接池大小和事务策略,同时注意敏感信息不要硬编码在代码里。
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online