C++ ODB ORM 入门与实战应用
ODB 基本概念
ODB 是一个针对 C++ 的对象关系映射(ORM)库,它允许开发者以面向对象的方式操作数据库,将 C++ 对象与数据库表进行映射,从而避免直接编写 SQL 语句,简化数据库操作。
C++ ODB ORM 是一种对象关系映射库,通过编译期代码生成实现 C++ 对象与数据库表的映射。支持 MySQL、PostgreSQL 等多种数据库,无需运行时反射。 ODB 的安装步骤、类型映射规则、视图定义方式以及基本的增删改查操作示例。涵盖构建配置、Schema 生成及事务处理流程,帮助开发者快速上手 ODB 进行数据库持久化开发。

ODB 是一个针对 C++ 的对象关系映射(ORM)库,它允许开发者以面向对象的方式操作数据库,将 C++ 对象与数据库表进行映射,从而避免直接编写 SQL 语句,简化数据库操作。
特点:
INSERT、UPDATE、DELETE)。#pragma db object)标记需要持久化的类,然后通过 ODB 编译器生成与数据库交互的代码(如 SQL 语句、CRUD 函数 等)。MySQL、PostgreSQL、SQLite、Oracle 等,切换数据库时无需修改核心业务代码,只需调整配置。query 类组合条件,实现复杂查询逻辑,避免手写 SQL。curl -sSfO https://download.build2.org/0.17.0/build2-install-0.17.0.sh
sh build2-install-0.17.0.sh
# 注意这里的 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 --version
如果报错找不到 odb,执行以下命令:
sudo echo 'export PATH=${PATH}:/usr/local/bin' >> ~/.bashrc
export PATH=${PATH}:/usr/local/bin
odb --version
cd ..
bpkg create -d libodb-gcc-N cc \
config.cxx=g++ \
config.cc.coptions=-O3 \
config.install.root=/usr/ \
config.install.sudo=sudo
cd 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
提示:接下以映射到 MySQL 数据库为示例演示。
在 C++ 中,要使用 ODB 将类声明为持久化类,需要包含 ODB 的核心头文件,并使用 #pragma db object 指令。
#include<cstddef>// std::size_t
#include<boost/date_time/posix_time/posix_time.hpp>
#include<odb/nullable.hxx>
#include<odb/core.hxx>
通过指令 #pragma 声明 C++ 类与数据库表之间的映射关系,必须放在它们所描述的 C++ 实体(类、数据成员、访问器)之前。格式:
#pragma db 指令 [参数...]
指令及参数:
object:表示该类将被映射到数据库。
table():默认生成的表名就是类名,使用该参数可以指定表名。id:表示该成员将被作为主键auto:表示该成员有自增属性unique:表示唯一键索引字段index:为该字段创建普通索引null:该字段允许为空not_null:该字段不允许为空default():指定默认值column():指定该成员映射到数据库表后的列名type():指定该成员映射到数据库表中的字段类型示例 person.hxx 文件:
// 将上文的头文件包含
#pragma db object table("person")
class Person {
public:
private:
friend class odb::access; // ODB 需要访问私有成员,所以需要做友元声明
#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 脚本文件,通过指令 cat person.sql 查看该文件。

通过该文件构建 MySQL 数据库表:
mysql -u root -p 库名 < person.sql
登陆 MySQL 数据库查看表属性:

注意 1:
#pragma db 只影响紧随其后的类、数据成员或访问器friend class odb::access注意 2:
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;
};
工作方式:
object(Student):指定主表object(Classes: Student::classes_id == Classes::id):指定关联表及连接条件相当于 SQL:
SELECT Student.name, Student.age, Classes.name FROM Student JOIN Classes ON Student.classes_id = Classes.id
方式二:基于原生 SQL 的视图 当需要复杂查询时,可以直接使用原生 SQL:
#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") 定义? 是参数占位符,可以在查询时传入具体值实现数据库的增删改查操作。
persist(const T&):保存对象到数据库。erase(const T&):从数据库中删除对象。update(const T&):更新已存在的对象。query<T>(const Query&):执行查询并返回结果集。begin():开始一个新的事务。odb::transaction 包装 database 对象,确保操作原子性。查询结果集,支持迭代器遍历和大小检查。
数据库操作流程:
#pragma once
#include<string>
#include<cstddef>// std::size_t
#include<boost/date_time/posix_time/posix_time.hpp>
#include<odb/nullable.hxx>
#include<odb/core.hxx>
class Student {
public:
Student(unsigned long id, const std::string& name, unsigned short age, unsigned long cid)
: _id(id), _sn(""), _name(name), _age(age), _classes_id(cid) {}
private:
unsigned long _id;
std::string _sn;
std::string _name;
unsigned short _age;
unsigned long _classes_id;
};
class Classes {
private:
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;
};
#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,0,"端口号");
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 updata_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));
// 插入
// insert_student(db);
// insert_classes(db);
// 更新
// Student stu = select_student(db);
// stu.age(5);
// updata_student(db,stu);
// 删除
// remove_student(db);
// 查询
select_student_classes(db);
return 0;
}
test : test.cc student_classes-odb.cxx
c++ -g $^ -o $@ -lodb-mysql -lodb -lodb-boost -lgflags

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online