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 复制代码
/*
 在 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文件:

c 复制代码
//将上文的头文件包含
#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 只影响紧随其后的类、数据成员或访问器
  • 顺序:多个属性可以写在同一行,也可以分开写
  • 访问控制:ODB 需要访问私有成员,通常使用 friend class odb::access

注意2:

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

视图映射

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

视图的核心特点

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

视图的两种定义方式
方式一:基于对象关系的视图

这种视图使用ODB的查询语言来定义表之间的关联:

c 复制代码
#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:

c 复制代码
#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类:实现数据库的增删改查操作

构造函数

c 复制代码
// MySQL
odb::mysql::database(
   const std::string& user,
   const std::string& password,
   const std::string& database,
   const std::string& host = "",
   unsigned int port = 0
);

成员函数

事务管理

c 复制代码
odb::transaction begin();//获取并开启事务
//odb::transaction类
void commit();//事务提交
void rollback();//事务回滚

查询操作

c 复制代码
result<T> query(const odb::query<T>&);
result<T> query(const odb::query<T>&, const odb::query_params&);

单对象操作

c 复制代码
void persist(T& object);  // 插入 
void update(const T& object);  // 更新   
void erase(const T& object);  // 删除

odb::result 类 :查询结果集

c 复制代码
// 迭代器 
iterator begin(); 
iterator end();

// 容量查询 
bool empty() const; 
std::size_t size() const;

// 遍历操作 
T& front(); // 获取第一个对象 
void next(); // 移动到下一个对象
bool done() const;  // 检查是否遍历完成

测试示例

数据库操作流程:

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

Student.hpp

cpp 复制代码
#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
{
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;
};

test.cc

c 复制代码
#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, "最大的连接池数");

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;
}

makefile

复制代码
test : test.cc student_classes-odb.cxx
	c++ -g $^ -o $@  -lodb-mysql -lodb -lodb-boost -lgflags

非常感谢您能耐心读完这篇文章。倘若您从中有所收获,还望多多支持呀!

相关推荐
一直向钱2 小时前
Ubuntu 服务器的无法使用WinSCP低版本连接登录
linux·服务器·ubuntu
终生成长者2 小时前
MongoDB 操作命令
数据库·mongodb·oracle
WX-bisheyuange2 小时前
基于Spring Boot的社团服务系统的设计与实现
数据库
wind_one12 小时前
8.基础--SQL--DDL-表操作-修改&删除
数据库·sql
歪歪1002 小时前
解决多 Linux 客户端向 Windows 服务端的文件上传、持久化与生命周期管理问题
linux·运维·服务器·开发语言·前端·数据库·windows
檀越剑指大厂2 小时前
从3周到3天?金仓KReplay如何重塑数据库迁移测试
数据库
麦嘟学编程2 小时前
快速上手配置Zookeeper
linux·分布式·zookeeper
SimonKing2 小时前
为什么0.1 + 0.2不等于0.3?一次讲透计算机的数学“Bug”
java·数据库·后端
一匹电信狗2 小时前
【C++11】右值引用+移动语义+完美转发
服务器·c++·算法·leetcode·小程序·stl·visual studio