C++ ODB ORM 完全指南:从入门到实战应用

C++ ODB ORM 完全指南:从入门到实战应用

文章目录

ODB基本概念

ODB 是一个针对 C++ 的对象关系映射(ORM)库,它允许开发者以面向对象的方式操作数据库,将C++ 对象与数据库表进行映射,从而避免直接编写 SQL 语句,简化数据库操作。

特点:

  • 对象 - 关系映射:将 C++ 类映射到数据库表,类的成员变量映射到表的字段,对象的创建、修改、删除等操作会自动转换为对应的数据库操作(如 INSERTUPDATEDELETE)。
  • 代码生成机制:ODB 不依赖运行时反射(C++ 本身不支持),而是通过编译期代码生成实现映射:开发者使用特殊的注解(如 #pragma db object)标记需要持久化的类,然后通过 ODB 编译器生成与数据库交互的代码(如 SQL 语句CRUD 函数等)。
  • 支持多数据库:兼容主流数据库,如 MySQLPostgreSQLSQLiteOracle 等,切换数据库时无需修改核心业务代码,只需调整配置。
  • 查询能力:提供类似 SQL 的查询接口(通过 C++ 表达式构建),例如通过 query 类组合条件,实现复杂查询逻辑,避免手写 SQL。
  • 事务支持:内置事务管理机制,确保数据库操作的原子性、一致性、隔离性和持久性(ACID)。

ODB框架安装

build2安装:
安装步骤: https://build2.org/install.xhtml#unix

dev@dev-host:~/workspace$ curl -sSfO https://download.build2.org/0.17.0/build2-install-0.17.0.sh
dev@dev-host:~/workspace$ sh build2-install-0.17.0.sh

安装 odb-compiler:

dev@dev-host:~/workspace$ #注意这里的 gcc-11 需要根据你自己版本而定
dev@dev-host:~/workspace$ sudo apt-get install gcc-11-plugin-dev
dev@dev-host:~/workspace$ mkdir odb-build && cd odb-build
dev@dev-host:~/workspace/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

dev@dev-host:~/workspace/odb-build$ cd odb-gcc-N
dev@dev-host:~/workspace/odb-build/odb-gcc-N$ bpkg build odb@https://pkg.cppget.org/1/beta
dev@dev-host:~/workspace/odb-build/odb-gcc-N$ bpkg test odb test odb-2.5.0-b.25+1/tests/testscript{testscript} tested odb/2.5.0-b.25+1
dev@dev-host:~/workspace/odb-build/odb-gcc-N$ bpkg install odb
dev@dev-host:~/workspace/odb-build/odb-gcc-N$ odb --version bash: /usr/bin/odb: No such file or directory
#如果报错了,找不到 odb,那就在执行下边的命令
dev@dev-host:~/workspace/odb-build/odb-gcc-N$ sudo echo 'export PATH=${PATH}:/usr/local/bin' >> ~/.bashrc
dev@dev-host:~/workspace/odb-build/odb-gcc-N$ export PATH=${PATH}:/usr/local/bin
dev@dev-host:~/workspace/odb-build/odb-gcc-N$ odb --version ODB object-relational mapping (ORM) compiler for C++ 2.5.0-b.25 Copyright (c) 2009-2023 Code Synthesis Tools CC. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

安装 ODB 运行时库:

dev@dev-host:~/workspace/odb-build/odb-gcc-N$ cd ..
dev@dev-host:~/workspace/odb-build$ bpkg create -d libodb-gcc-N cc \
config.cxx=g++ \
config.cc.coptions=-O3 \
config.install.root=/usr/ \
config.install.sudo=sudo

dev@dev-host:~/workspace/odb-build$ cd libodb-gcc-N
dev@dev-host:~/workspace/odb-build/libodb-gcc-N$ bpkg add https://pkg.cppget.org/1/beta
dev@dev-host:~/workspace/odb-build/libodb-gcc-N$ bpkg fetch
dev@dev-host:~/workspace/odb-build/libodb-gcc-N$ bpkg build libodb
dev@dev-host:~/workspace/odb-build/libodb-gcc-N$ bpkg build libodb-mysql

安装 boost profile 库:

dev@dev-host:~/workspace/odb-build/libodb-gcc-N$ bpkg build libodb-boost

总体打包安装:

dev@dev-host:~/workspace/odb-build/libodb-gcc-N$ bpkg install --all --recursive

常见操作

提示:接下以映射到mysql数据库为示例演示
核心头文件

/* 在 C++ 中,要使用 ODB 将类声明为持久化类,需要包含 ODB 的核心头文 件,并使用 #pragma db object 指令 #pragma db object 指示 ODB 编译器将 person 类视为一个持久化类。 */#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文件:

//将上文的头文件包含#pragmadb object table("person") class Person { public: private: friend class odb::access;//ODB需要访问私有成员,所以需要做友元声明#pragmadb id autounsignedint _id;#pragmadb column("user_age")default(18)unsignedshort _age;#pragmadb not_null std::string _name;#pragmadb 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 只影响紧随其后的类、数据成员或访问器
  • 顺序:多个属性可以写在同一行,也可以分开写
  • 访问控制:ODB 需要访问私有成员,通常使用 friend class odb::access

注意2:

  • 数据库表的列名默认为成员变量名,但会去掉成员名的前缀下划线。

视图映射
ODB视图是一个只读的、虚拟的投影,它基于一个或多个持久化类(数据库表),通过查询组合出你需要的数据结构。视图本身不对应数据库中的实际表,而是在查询时动态生成的结果集。
视图的核心特点

  • 只读性:视图只能用于查询,不能进行插入、更新、删除操作
  • 虚拟性:不占用实际数据库存储空间
  • 灵活性:可以自由组合多个表的字段,形成新的数据结构
  • 类型安全:在C++编译期就确定了返回类型

视图的两种定义方式
方式一:基于对象关系的视图
这种视图使用ODB的查询语言来定义表之间的关联:

#pragmadb view object(Student)\object(Classes: Student::classes_id == Classes::id)structStudentClassView{#pragmadb column(Student::name) std::string student_name;#pragmadb column(Student::age)unsignedshort student_age;#pragmadb 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:

#pragmadb view query("SELECT s.name, s.age, c.name "\"FROM student s "\"JOIN classes c ON s.classes_id = c.id "\"WHERE s.age > ?")structAdultStudentView{ std::string student_name;unsignedshort student_age; std::string class_name;};

特点:

  • 使用 query("原生SQL") 定义
  • ? 是参数占位符,可以在查询时传入具体值
  • 适合复杂的、对象关系无法表达的查询

ODB类与接口

odb::database类:实现数据库的增删改查操作

构造函数

成员函数

事务管理

查询操作

单对象操作

odb::result 类 :查询结果集

测试示例

数据库操作流程:

  1. 构造连接池对象
  2. 构造数据操作database对象
  3. 获取事务对象,开启事务
  4. 数据库操作
  5. 提交事务(若无添加,在销毁前会进行事务回滚)

Student.hpp

#pragmaonce#include<string>#include<cstddef>// std::size_t#include<boost/date_time/posix_time/posix_time.hpp>#include<odb/nullable.hxx>#include<odb/core.hxx>classStudent{private:unsignedlong _id; std::string _sn; std::string _name;unsignedshort _age;unsignedlong _classes_id;};classClasses{private:unsignedlong _id; std::string _name;};#pragmadb view object(Student)\object(Classes classes:Student._classes_id == Classes._id)\query((?))structclasses_student{#pragmadb column(Student::_id)unsignedlong _id;#pragmadb column(Student::_sn) std::string _sn;#pragmadb column(Student::_name) std::string _name;#pragmadb column(Student::_age)unsignedshort _age;#pragmadb column(Classes::_name) std::string _classes_name;};#pragmadb view query("select name from Student "+(?))structall_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,0,"端口号");DEFINE_string(db,"qsy_test","mysql数据库名");DEFINE_string(user,"root","mysql用户名");DEFINE_string(pswd,"I5sLBKpqjGjPZi","mysql用户密码");DEFINE_string(cset,"utf8","mysql客户端字符集");DEFINE_int32(max_pool,3,"最大的连接池数");voidinsert_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;}}voidinsert_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';}}voidremove_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;returnStudent();} ret =*r.begin(); trans.commit();}catch(const std::exception &e){ std::cerr <<"查询失败"<< e.what()<<'\n';}return ret;}voidselect_student_classes(odb::mysql::database& db){ try { odb::transaction trans(db.begin());typedef odb::query<structclasses_student> query;typedef odb::result<structclasses_student> result; result r(db.query<structclasses_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;}}voidupdata_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';}}intmain(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);return0;}

makefile

test : test.cc student_classes-odb.cxx c++ -g $^ -o $@ -lodb-mysql -lodb -lodb-boost -lgflags 
非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=45sbqgxtzjv

Read more

算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题

算法基础篇:(二十一)数据结构之单调栈:从原理到实战,玩转高效解题

目录 前言 一、什么是单调栈?先打破 “栈” 的常规认知 1.1 单调栈的核心特性 1.2 如何实现一个单调栈? 实现单调递增栈 实现单调递减栈 1.3 核心操作解析:为什么要 “弹出元素”? 二、单调栈能解决什么问题?四大核心场景全覆盖 2.1 场景 1:找左侧最近的 “更大元素” 问题描述 解题思路 代码实现 测试用例验证 2.2 场景 2:找左侧最近的 “更小元素” 问题描述 解题思路 代码实现 测试用例验证 2.3 场景 3:找右侧最近的 “更大元素” 问题描述

By Ne0inhk
【希尔排序算法】详解:原理、实现与优化

【希尔排序算法】详解:原理、实现与优化

【希尔排序算法】详解:原理、实现与优化 * 一、算法概述 * 基本特性 * 二、算法原理详解 * 核心思想 * 增量序列选择 * 三、算法流程图示 * 示例数组:[8, 9, 1, 7, 2, 3, 5, 4, 6, 0] * 初始状态 * 第一轮:gap=5 * 第二轮:gap=2 * 第三轮:gap=1(标准插入排序) * 四、完整Java实现 * 五、算法分析 * 时间复杂度分析 * 空间复杂度 * 稳定性 * 六、实际应用场景 * 七、与其他排序算法的对比 * 八、总结 🌺The Begin�

By Ne0inhk

傅里叶变换 | FFT 与 DFT 原理及算法

注:本文为 “傅里叶变换 | FFT 与 DFT” 相关合辑。 英文引文,机翻未校。 中文引文,略作重排。 图片清晰度受引文原图所限。 如有内容异常,请看原文。 Fast Fourier Transform (FFT) 快速傅里叶变换(FFT) In this section we present several methods for computing the DFT efficiently. In view of the importance of the DFT in various digital signal processing applications, such as linear filtering,

By Ne0inhk
链表进阶核心 | LeetCode 92 区间反转:吃透递归反转与哨兵技巧

链表进阶核心 | LeetCode 92 区间反转:吃透递归反转与哨兵技巧

✨链表进阶核心 | LeetCode 92 区间反转:吃透递归反转与哨兵技巧🎯 * 视频地址 * 🚀 开篇引论:链表反转的进阶之路 * 🔄 基础筑基:链表【前n个节点】递归反转 * 1. 函数定义与核心功能 * 2. 递归实现思路拆解 * 3. 直观调用示例 * 4. 关键代码实现(C++)与详解 * 🎯 实战攻坚:LeetCode 92 链表区间反转 * 1. 题目问题描述 * 2. 神器加持:虚拟头节点(哨兵)技巧 * 3. 整体解题思路 * 4. 完整代码实现(C++)与逐行解析 * 5. 算法复杂度分析 * 📚 算法原理深度剖析 * 1. 递归反转的核心原理 * 2. 虚拟头节点的底层逻辑 * 💡 算法学习核心建议 * 结语 * ✅ 关键点回顾 视频地址

By Ne0inhk