目录
概述
基本工作原理
映射C++对象到数据库表
从数据库中加载对象
持久化C++对象到数据库
ODB常用接口
表创建预处理
#pragma db
Object
table
数据表属性
id
auto
column(“xxx”)
type("xxx")
unique
index
null
default(xxx)
总结
查询预处理
view
query
result
database类
连接管理接口
事务管理接口
对象存取接口
对象查询接口
类型映射与元数据接口
数据库备份与恢复
基本使用
概述
ODB 库的目标是提供一个功能强大且类型安全的 ORM 解决方案,使得 C++ 开发者能够轻松地处理数据库操作,同时保留 C++ 中对象的优势。它通过 C++ 类和数据库表之间的自动映射来简化数据库的持久化操作。
- 对象到数据库的映射:将 C++ 对象映射到数据库中的表
- 数据库到对象的映射:将数据库中的记录自动映射为 C++ 对象,支持一对一、一对多、多对多等关系
优点
- 简化代码:使用 ODB,可以省去手动编写 SQL 查询的繁琐步骤,减少 SQL 注入和数据类型转换的错误
- 类型安全:所有操作都使用 C++ 对象,而不是原始 SQL 字符串,保证了类型的安全性
- 支持复杂数据关系:可以轻松处理对象间的复杂关系(如一对多、多对多等)
- 跨平台支持:支持多种数据库和操作系统,适合跨平台开发
缺点
- 依赖生成工具:需要使用 ODB 提供的工具来生成代码,增加了构建过程的复杂性
- 性能:虽然 ODB 在设计上注重性能,但 ORM 本身的抽象可能会导致一些性能损失,特别是在处理大量数据时
主要特性
- 类型安全:ODB 提供类型安全的映射操作,避免了直接使用 SQL 语句时可能发生的类型转换错误。开发者不需要关心 SQL 的细节,OIB 会自动处理类型映射
- 自动生成 SQL 代码:ODB 会根据 C++ 类和类成员的定义自动生成 SQL 查询语句,开发者不需要显式地编写 SQL 代码,减少了手动编写 SQL 的繁琐性和出错的可能性
- ODB 支持多个关系型数据库
- 复杂数据关系:ODB 支持对象之间复杂的关系映射,如一对多、多对多等关系,并且能够自动管理外键、级联操作等
- 支持继承和多态:ODB 还支持 C++ 类继承和多态。你可以使用 ODB 映射继承层次结构中的类到数据库表,并且支持多态对象的持久化
- 查询功能:ODB 还支持类似于 SQL 的查询功能,开发者可以使用 ODB 提供的查询接口来执行复杂的查询操作
- 性能:ODB 在生成 SQL 查询时会尽量优化性能,支持批量插入、延迟加载、缓存机制等特性,从而在性能上做到了较好的平衡
基本工作原理
ODB核心步骤就两步
- 映射:定义对象和数据库表之间的映射关系
- 持久化:将对象保存到数据库或者从数据库中加载对象
映射C++对象到数据库表
ODB 提供了一个 database
类(通常是 odb::database
)来操作数据库。开发者可以用它来保存、加载和查询 C++ 对象
db.persist(p)
会将 Person
对象的 name_
和 age_
持久化到数据库中的 Person
表
#include <odb/database.hxx>
#include <odb/transaction.hxx>
#include <odb/sqlite/database.hxx> // 支持 SQLite
int main() {
odb::sqlite::database db("example.db", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
Person p("John", 30);
// 开始事务
{
odb::transaction t(db.begin());
db.persist(p); // 将对象持久化到数据库
t.commit();
}
return 0;
}
从数据库中加载对象
b.load<Person>(1)
会根据数据库表中的 ID 为 1 的记录,自动生成一个 Person
对象
{
odb::transaction t(db.begin());
std::shared_ptr<Person> p = db.load<Person>(1); // 加载 ID 为 1 的 Person 对象
t.commit();
std::cout << "Name: " << p->name() << ", Age: " << p->age() << std::endl;
}
持久化C++对象到数据库
ODB 提供了一个 database
类(通常是 odb::database
)来操作数据库。开发者可以用它来保存、加载和查询 C++ 对象
#include <odb/database.hxx>
#include <odb/transaction.hxx>
#include <odb/sqlite/database.hxx> // 支持 SQLite
int main() {
odb::sqlite::database db("example.db", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
Person p("John", 30);
// 开始事务
{
odb::transaction t(db.begin());
db.persist(p); // 将对象持久化到数据库
t.commit();
}
return 0;
}
ODB常用接口
表创建预处理
#pragma db
- 功能:这是 ODB 的核心预处理指令,用来指定 C++ 类或类成员与数据库字段的映射
- 用于定义类的整体映射规则,比如将类映射为一个数据库表
- 用于定义某个类成员的具体数据库属性,比如列名、类型、索引、默认值等
Object
- 功能:标记一个类是数据库中的一个持久化对象(实体类)
- 用法:#pragma db object 放在类声明的前面
- 作用:表示
Employee
类会映射到数据库中的一个表,果类没有被标记为object
,则 ODB 不会将其映射为数据库表
#pragma db object
class Employee {
public:
int id;
std::string name;
};
table
- 功能:指定类映射到数据库中的表名
- 用法:默认情况下,ODB 将类的名字(小写)作为表名,如果需要自定义表名,那么可以使用table指令
#pragma db object table("employees_table")
class Employee {
int id;
std::string name;
};
数据表属性
id
- 功能:指定某字段为数据库表的主键
- 用法:主键必须唯一,且必须是整形;#pragma db id放在类成员前面,表示该字段是主键
class Employee {
#pragma db id
int id;
std::string name;
};
auto
- 功能:表示主键字段是自动递增的
- 用法:#pragma db id auto用于主键字段,告知ODB让数据库自动生成该字段
class Employee {
#pragma db id auto
int id;
std::string name;
};
column(“xxx”)
- 功能:将类成员变量映射到数据库表的某个特定列名
- 用法:默认情况下,ODB 使用类成员的名字作为数据库列名;如果需要指定一个不同的列名,可以使用colum
class Employee {
#pragma db column("emp_id")
int id;
#pragma db column("full_name")
std::string name;
};
type("xxx")
- 功能:指定字段在数据库表中的具体类型
- 用法:如果需要覆盖默认映射的字段类型,可以用
type
指定
class Employee {
#pragma db type("varchar(255)")
std::string name;
};
unique
- 功能:标记字段为唯一键,保证表中该字段的值不会重复
- 用法:在需要唯一约束的字段上添加
#pragma db unique
class Employee {
#pragma db unique
std::string email;
};
index
- 功能:为字段创建索引以加速查询
- 用法:在需要加速查询的字段上使用
#pragma db index
class Employee {
#pragma db index
std::string name;
};
not_null
- 功能:指定字段不能为
NULL
- 用法:在不允许为空的字段上添加
#pragma db not_null
class Employee {
#pragma db not_null
std::string name;
};
null
- 功能:允许字段为
NULL
- 用法:使用
odb::nullable<T>
表示字段可以存储NULL
值
class Employee {
odb::nullable<std::string> middle_name;
};
default(xxx)
- 功能:为字段指定默认值
- 用法:在需要设置默认值的字段上使用
#pragma db default(xxx)
class Employee {
#pragma db default("Unknown")
std::string department;
};
总结
// 将该类声明为 ODB 持久化对象,并映射到数据库表 "employee_table"
#pragma db object table("employee_table")
class Employee {
public:
// 将字段 `id` 映射为数据库表的主键,并且自动递增
#pragma db id auto
int id;
// 将字段 `name` 映射到数据库表的列名 "full_name",并且设置为不允许为空
#pragma db column("full_name") not_null
std::string name;
// 将字段 `email` 映射到数据库表的列名 "email_address",并且设置为唯一值
#pragma db column("email_address") unique
std::string email;
// 将字段 `role` 映射为数据库表的 varchar(20) 类型,并设置默认值为 "Staff"
#pragma db type("varchar(20)") default("Staff")
std::string role;
// 定义字段 `phone`,允许为空,使用 odb::nullable<T> 表示
odb::nullable<std::string> phone;
// 将字段 `department` 创建一个普通索引,用于加速查询
#pragma db index
std::string department;
};
查询预处理
view
View
是只读的结果类,用于查询数据并将查询结果映射到特定的结构体或类中。View
类本质上是查询的只读视图,不能修改底层数据
事例1
- 通过View查询学生信息,然后通过学生ID和班级表ID进行关联,从而查询到对应班级名称
object(Student)
:指定视图操作的主要表是Student
object(Classes)
:将Student::_classes_id
与Classes::_id
进行关联,关联的表命名为classes
#pragma db view object(Student)\
object(Classes = classes : Student::_classes_id == classes::_id)\
query((?))
struct classes_student {
// 将 Student::_id 映射到视图中的 id 字段
#pragma db column(Student::_id)
unsigned long id; // 学生的唯一ID
#pragma db column(Student::_sn)
unsigned long sn; // 学号
#pragma db column(Student::_name)
std::string name; // 学生姓名
#pragma db column(Student::_age)
odb::nullable<unsigned short> age; // 学生年龄 (可为空)
#pragma db column(classes::_name)
std::string classes_name; // 班级名称
};
通过该视图可以执行SQL查询
SELECT Student._id, Student._sn, Student._name, Student._age, Classes._name
FROM Student
JOIN Classes ON Student._classes_id = Classes._id
WHERE <dynamic_condition>;
query
ODB 提供的查询接口,用于执行动态查询并将结果映射到 C++ 对象中。查询语句可以通过占位符参数绑定动态条件
基本用法
typedef odb::query<T> query;
// 查询所有年龄大于20的学生
typedef odb::query<Student> query;
query q = query::age > 20;
// 等效下面的SQL语句
SELECT * FROM Student WHERE age > 20;
查询所有学生信息和班级名称
void queryClassesStudent(database& db) {
transaction t(db.begin());
// 动态条件
odb::result<classes_student> result(
db.query<classes_student>("WHERE Student._age >= 18")
);
// 遍历结果
for (const auto& record : result) {
std::cout << "Student ID: " << record.id
<< ", Name: " << record.name
<< ", Age: " << (record.age ? *record.age : 0)
<< ", Class: " << record.classes_name << std::endl;
}
t.commit();
}
result
用于存储和处理查询的结果集。它的模板参数是查询的目标类型(T
),可以是数据库表对象或视图结构体
typedef odb::result<T> result;
支持类似于vector中的相关操作,迭代器、范围for等
database类
与MySQL中基本操作类似,类似于对其操作进行二次封装
连接管理接口
open()
用于打开数据库连接。如果数据库尚未连接,则建立连接。该方法可能接受连接配置(如数据库路径、用户名、密码等)作为参数
database.open("database_file");
close()
关闭数据库连接。释放所有资源
database.close();
is_open()(检查数据库是否已经打开)
if (database.is_open()) {
// 执行数据库操作
}
事务管理接口
begin()
database.begin();
commit()
提交事务,确保事务中的所有操作被永久保存到数据库中
database.commit();
rollback()
回滚事务,撤销事务中的所有操作
database.rollback();
is_transaction_active()
检查是否存在活动的事务
if (database.is_transaction_active()) {
// 处理事务
}
对象存取接口
store()
将一个对象持久化到数据库中。如果对象已经存在,它会更新对象;如果对象是新对象,它会插入一条新的记录
database.store(myObject);
erase()
从数据库中删除一个对象
database.erase(myObject);
load()
从数据库中加载一个对象。通常需要一个对象的标识符(如ID)来查找该对象
MyObject* obj = database.load<MyObject>(object_id);
find()
根据条件查询数据库中的对象。这个接口通常支持各种查询条件
std::vector<MyObject> objects = database.find<MyObject>("age > 30");
对象查询接口
query()
执行一个查询,返回符合条件的对象列表
auto results = database.query<MyObject>("SELECT * FROM MyObject WHERE age > 30");
count()
返回符合某个条件的对象数量
int count = database.count<MyObject>("age > 30");
distinct()
查询数据库中某个字段的不同值
auto distinctNames = database.distinct<MyObject>("name");
类型映射与元数据接口
get_type_info()
获取与某个对象类型(类)相关的元数据,包括字段、类型等
TypeInfo info = database.get_type_info<MyObject>();
get_object_count()
获取某个对象类型在数据库中的数量
int objectCount = database.get_object_count<MyObject>();
数据库备份与恢复
backup()
执行数据库的备份操作,将数据库内容复制到一个备份文件
database.backup("backup_file");
restore()
从备份文件恢复数据库
database.restore("backup_file");
基本使用
操作流程总结
- 构建连接池多对象
- 构建数据库操作database对象
- 获取事务对象然后开启事务
- 数据库操作
- 提交事务
创建student.hxx
#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>
#pragma db object
class Student{
public:
Student() {}
Student(unsigned long sn, const std::string &name, unsigned short age, unsigned long cid):
_sn(sn), _name(name), _age(age), _classes_id(cid){}
void sn(unsigned long num) { _sn = num; }
unsigned long sn() { return _sn; }
void name(const std::string &name) { _name = name; }
std::string name() { return _name; }
void age(unsigned short num) { _age = num; }
odb::nullable<unsigned short> age() { return _age; }
void classes_id(unsigned long cid) { _classes_id = cid; }
unsigned long classes_id() { return _classes_id; }
private:
friend class odb::access;
#pragma db id auto
unsigned long _id;
#pragma db unique
unsigned long _sn;
std::string _name;
odb::nullable<unsigned short> _age;
#pragma db index
unsigned long _classes_id;
};
#pragma db object
class Classes {
public:
Classes() {}
Classes(const std::string &name) : _name(name){}
void name(const std::string &name) { _name = name; }
std::string name() { return _name; }
private:
friend class odb::access;
#pragma db id auto
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)
unsigned long sn;
#pragma db column(Student::_name)
std::string name;
#pragma db column(Student::_age)
odb::nullable<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;
};
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time student.hxx
// 执行SQL文件
mysql -u <username> -p TestDB < student.sql
插入操作
void insert_classes(odb::mysql::database &db)
{
try {
//获取事务对象开启事务
odb::transaction trans(db.begin());
Classes c1("一年级一班");
Classes c2("一年级二班");
db.persist(c1);
db.persist(c2);
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "插入数据出错:" << e.what() << std::endl;
}
}
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, "王五", 18, 1);
Student s4(4, "赵六", 15, 2);
Student s5(5, "刘七", 18, 2);
Student s6(6, "孙八", 23, 2);
db.persist(s1);
db.persist(s2);
db.persist(s3);
db.persist(s4);
db.persist(s5);
db.persist(s6);
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "插入学生数据出错:" << e.what() << std::endl;
}
}
查询数据
通过学生表查找某个学生的信息
Student select_student(odb::mysql::database &db)
{
Student res;
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 << "数据量不对!\n";
return Student();
}
res = *r.begin();
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "更新学生数据出错:" << e.what() << std::endl;
}
return res;
}
更新学生记录
通过update()方法更新学生记录,stu是一个已经存在的学生对象,更新数据会覆盖原记录
void update_student(odb::mysql::database &db, Student &stu)
{
try{
odb::transaction trans(db.begin());
db.update(stu);
trans.commit();
}catch(std::exception &e){
std::cout<<"更新学生数据出错:"<<e.what()<<std::endl;
}
}
删除某个ID的学生
// 删除classe_id==2的学生
void remove_student(odb::mysql::database &db)
{
try {
//获取事务对象开启事务
odb::transaction trans(db.begin());
typedef odb::query<Student> query;
db.erase_query<Student>(query::classes_id == 2);
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "更新学生数据出错:" << e.what() << std::endl;
}
}
查找符合条件学生的所有信息
void classes_student(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->id << std::endl;
std::cout << it->sn << std::endl;
std::cout << it->name << std::endl;
std::cout << *it->age << std::endl;
std::cout << it->classes_name << std::endl;
}
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "更新学生数据出错:" << e.what() << std::endl;
}
}
查找student表中id == 1的学生姓名
// 查询 Student 表中 id == 1 的学生姓名
void all_student(odb::mysql::database &db)
{
try {
//获取事务对象开启事务
odb::transaction trans(db.begin());
typedef odb::query<Student> query;
typedef odb::result<struct all_name> result;
result r(db.query<struct all_name>(query::id == 1));
for (auto it = r.begin(); it != r.end(); ++it) {
std::cout << it->name << std::endl;
}
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "查询所有学生姓名数据出错:" << e.what() << std::endl;
}
}
代码综合测试
#include <odb/database.hxx>
#include <odb/mysql/database.hxx>
#include "student.hxx"
#include "student-odb.hxx"
#include <gflags/gflags.h>
DEFINE_string(host, "127.0.0.1", "这是Mysql服务器地址");
DEFINE_int32(port, 0, "这是Mysql服务器端口");
DEFINE_string(db, "TestDB", "数据库默认库名称");
DEFINE_string(user, "root", "这是Mysql用户名");
DEFINE_string(pswd, "123456", "这是Mysql密码");
DEFINE_string(cset, "utf8", "这是Mysql客户端字符集");
DEFINE_int32(max_pool, 3, "这是Mysql连接池最大连接数量");
void insert_classes(odb::mysql::database &db)
{
try {
//获取事务对象开启事务
odb::transaction trans(db.begin());
Classes c1("一年级一班");
Classes c2("一年级二班");
db.persist(c1);
db.persist(c2);
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "插入数据出错:" << e.what() << std::endl;
}
}
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, "王五", 18, 1);
Student s4(4, "赵六", 15, 2);
Student s5(5, "刘七", 18, 2);
Student s6(6, "孙八", 23, 2);
db.persist(s1);
db.persist(s2);
db.persist(s3);
db.persist(s4);
db.persist(s5);
db.persist(s6);
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "插入学生数据出错:" << e.what() << std::endl;
}
}
Student select_student(odb::mysql::database &db)
{
Student res;
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 << "数据量不对!\n";
return Student();
}
res = *r.begin();
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "更新学生数据出错:" << e.what() << std::endl;
}
return res;
}
void update_student(odb::mysql::database &db, Student &stu)
{
try{
odb::transaction trans(db.begin());
db.update(stu);
trans.commit();
}catch(std::exception &e){
std::cout<<"更新学生数据出错:"<<e.what()<<std::endl;
}
}
// 删除classe_id==2的学生
void remove_student(odb::mysql::database &db)
{
try {
//获取事务对象开启事务
odb::transaction trans(db.begin());
typedef odb::query<Student> query;
db.erase_query<Student>(query::classes_id == 2);
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "更新学生数据出错:" << e.what() << std::endl;
}
}
//查询某个班级所有的学生
void classes_student(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->id << std::endl;
std::cout << it->sn << std::endl;
std::cout << it->name << std::endl;
std::cout << *it->age << std::endl;
std::cout << it->classes_name << std::endl;
}
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "更新学生数据出错:" << e.what() << std::endl;
}
}
// 查询 Student 表中 id == 1 的学生姓名
void all_student(odb::mysql::database &db)
{
try {
//获取事务对象开启事务
odb::transaction trans(db.begin());
typedef odb::query<Student> query;
typedef odb::result<struct all_name> result;
result r(db.query<struct all_name>(query::id == 1));
for (auto it = r.begin(); it != r.end(); ++it) {
std::cout << it->name << std::endl;
}
//5. 提交事务
trans.commit();
}catch (std::exception &e) {
std::cout << "查询所有学生姓名数据出错:" << e.what() << std::endl;
}
}
int main(int argc , char *argv[])
{
google::ParseCommandLineFlags(&argc, &argv, true);
//1. 构造连接池工厂配置对象
std::unique_ptr<odb::mysql::connection_pool_factory> cpf(
new odb::mysql::connection_pool_factory(FLAGS_max_pool, 0));
//2. 构建数据库操作对象
odb::mysql::database db(
FLAGS_user, FLAGS_pswd, FLAGS_db,
FLAGS_host, FLAGS_port, "", FLAGS_cset,
0, std::move(cpf));
// 插入数据
insert_classes(db);
insert_student(db);
// 查询数据
auto stu = select_student(db);
std::cout<<stu.sn()<<std::endl;
std::cout<<stu.name()<<std::endl;
if (stu.age()) std::cout << *stu.age() << std::endl;
std::cout << stu.classes_id() << std::endl;
//更新数据
stu.age(15);
update_student(db, stu);
remove_student(db);
classes_student(db);
all_student(db);
return 0;
}