提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、创建学生类和班级类
- 1.学生类
- 2.班级类
- 3.生成数据库支持代码
- 二、创建数据库对象,对数据库进行操作
- 1.构建连接池工厂配置对象
- 2.构造数据库操作对象
- 3.数据库操作
- 1.新增数据
- 2.查询数据
- 3.更新数据
- 4.删除操作
- 5.多表查询
- 5.部分字段查询
前言
odb可以简化数据库的操作,通过将c++对象直接映射到数据库表,通过对 对象的操作,完成数据库表的操作。我们这次简单使用下odb来实现一个学生表和班级表的增删改查操作。
一、创建学生类和班级类
由于odb是通过是通过对象映射到表的,所以创建表,我们就需要先创建两个类,一个学生类,一个班级类。
1.学生类
首先学生表中需要id(编号),sn(学号),name(姓名),age(年龄),classid(班级编号)这几个字段,于是我们的类中也要有这几个编号。
另外,我们需要提供默认构造和有参构造,并且提供get和set方法,来进行成员变量的设置和获取。这些都是和我们定义普通类是一样的。
可以看到代码中有很多#pragma db指令。
#pragma db object就代表我们需要将这个类映射成数据库表,这也是最重要的。
#pragma db id 代表该成员变量需要作为表的主键,同时后面跟上了一个auto 代表需要设置自增约束。
#pragma db unique 代表设置字段唯一性约束。
#pragma index代表设置普通索引。
其中所有的字段默认都会添加not null约束的,如果需要指定该字段可以为空,就需要使用odb::nullable类型来定义变量,这样映射到数据库表中的变量就是null约束了。
但是有一点需要注意,就是在获取该字段时,由于该字段可能为空,所以需要对该字段进行判断。怎么获取我们后面介绍。
在类中我们声明了一个友元类odb::access,因为我们的成员变量都是私有,后续odb需要操作我们的成员变量,因此需要申明友元。
#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 autounsigned long _id;#pragma db uniqueunsigned long _sn;std::string _name;odb::nullable<unsigned short> _age;#pragma db indexunsigned long _classes_id;
};
2.班级类
班级类和学生类都是一样的操作。
我们总结以下odb需要在普通的类中新增哪些操作:
- #pragma db object
- 定义默认构造和有参构造,和get set方法
- 申明友元类odb::access;
- 对成员变量设置主键和约束等。
#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 autounsigned long _id;std::string _name;
};
3.生成数据库支持代码
上面的两份代码写完后,我们需要基于上面的代码来生成mysql数据库交互所需要的代码。
这里和protubuf的.proto文件生成有点类似。
- odb:这是 ODB 工具的命令行可执行文件,负责代码生成和数据库模式创建。
- -d mysql:指定目标数据库为 MySQL。这意味着生成的代码和模式将针对 MySQL 数据库进行优化和配置。
- -std c++11:指定使用 C++11 标准进行代码生成。这通常是为了支持 C++11 的特性(如智能指针、范围for循环等)。
- -profile boost/date-time:在数据库表中有一些时间字段datatime等,我们c++类中是使用的boost中的类来定义的,因此加上这个选项。
- 最后的student.hxx就是我们上面编写的学生类的源文件。
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time student.hxx
上面的指令执行完毕后,我们就会得到五个源文件,分别是
student-odb.cxx ------student-odb.hxx-----------studnet-odb.ixx----------------studnet.sql--------student.hxx
其中-odb.cxx是我们需要进行编译的,-odb.hxx是我们需要进行include包含的,ixx是一个中间文件我们不需要关心。.sql就是odb帮我们生成的sql语句用于创建数据库表.
我们需要手动到导入.sql文件创建数据库表。
mysql -uroot -p < student.sql
二、创建数据库对象,对数据库进行操作
1.构建连接池工厂配置对象
odb::mysql::connection_pool_factory数据库连接池工厂对象,这里需要指定两个参数一个是最大连接数量和最小连接数量.FLAGS_max_pool和0.
std::unique_ptr<odb::mysql::connection_pool_factory> cpf(new odb::mysql::connection_pool_factory(FLAGS_max_pool, 0));
2.构造数据库操作对象
构造数据库操作对象,后续我们进行的增删改查操作都是通过这个对象的函数来完成。这里需要指定几个参数,分别是用户名,密码,数据库名称,主机地址,端口号,socket,字符集,client_falgs,和连接池工程对象。这里的连接池工厂对象需要使用unique_ptr创建,且必须传入右值,不然会报错。
odb::mysql::database db(FLAGS_user, FLAGS_pswd, FLAGS_db,FLAGS_host, FLAGS_port, "", FLAGS_cset,0, std::move(cpf));
3.数据库操作
接下来就可以通过数据库对象来进行数据库操作了。我们封装一个插入学生数据的函数,形参传入我们的数据库对象。
需要注意的是,我们在进行数据库操作的时候需要开启事务,在操作完毕之后选哟手动提交事务。然后就是进行数据库操作可能会失败,odb会抛出异常,因此为了防止程序崩溃,我们需要捕获异常。
1.新增数据
接下来的操作都是通过数据库对象提供的函数实现的。
通过begin()来获取一个事务对象,
然后定义几个student对象,这里的student就是我们前面.hxx定义的。这里的一个student对象就代表数据库表中的一行记录。
接着使用persist进行插入操作。
最后手动提交事务。
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;}
}
对班级表新增数据,都是一样的操作。
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;}
}
2.查询数据
查询数据就一定会返回一个结果并且查询数据一定需要指定where条件。
在odb中通过 odb::result< Student >来获取结果,这个< student >需要我们显示指定,你要查询那一张表,就填入你要查询表的映射的类。
where条件是通过odb::query< student >来指定的。
需要使用数据库对象提供的query函数来进行查询操作,这里的query不是我们上面的odb::query。这俩个不是一回事,数据库对象的query是提供查询操作,而odb::query是指定where条件的。
db.query需要传入一个参数,这个参数就是where条件,query::name == “张三”,这里的query::name就是我们上面定义的odb::query< student >,这里我们显示指定的< student >,代表我们的where条件就是student中的字段,
这里的db.query< student >也指定了一个studnet,这里指定的studnet代表我们进行查询的表是studnet表。
这里可能有点绕,odb的只支持全量查询,默认显示表中所有字段,如果想只显示莫几个字段,则需要根据视图,来指定一个新的类,这个类中只有你需要显示的字段,进而对这个类的操作,显示出你要查询的字段。
typedef odb::query<Student> query;typedef odb::result<Student> result;result r(db.query<Student>(query::name == "张三"));
查询操作的全部代码,可以result可以当作一个vector,里面存放这你查询到的所有记录,每个记录都是一个student,它是支持迭代器的,r.begin()这既是获取迭代器,然后对他解引用获取第一个studnet,由于我们直插入了一个张三的学生,所以这里只有一个值。
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;
}
3.更新数据
更新数据这块有些不同,你要更新数据,你要先查询到你要更新的记录,然后将该记录也就是一个Student对象更改后,通过update写入数据库中。可以看到,这里的形参就是查询到的student对象,我们已经对他进行了更改,将age改为了20,接着通过update传入student进行数据库更新操作。
void update_student(odb::mysql::database &db, Student &stu)
{try {//获取事务对象开启事务odb::transaction trans(db.begin());db.update(stu);//5. 提交事务trans.commit();}catch (std::exception &e) {std::cout << "更新学生数据出错:" << e.what() << std::endl;}
}
4.删除操作
删除操作,和更新不同,可以通过erase_query来进行删除数据。需要提供where条件,那么就需要创建odb::query对象。同样的erase_query< student >这里显示指定的student代表你要操作的表,而这里odb::query< syudent >这里指定的studnet是你where条件需要用到的表的映射类。
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;}
}
5.多表查询
我们需要通过视图,创建一个视图,这个视图中包含了我们需要查询的多张表中的字段。例如这里我们需要所有学生的信息,包括学生的班级,那么我们就需要创建一个视图,把学生表的所有字段和班级表的班级姓名字段都包含进来。这里我们创建一个新的类classes_student代表我们多表查询的一个临时表。后续我们操作这个类对象就可以完成多表查询的结果了。
通过#pragma db view创建视图,需要指定你创建的视图是由哪几个表组成的,所以使用object(),我们这里使用学生表和班级表,Classes后面有一个等号,代表取别名, 后面接着一个冒号,由于两个表联立会产生笛卡尔积,所以我们需要指定where条件。
这里的成员变量我们需要使用#pragma db column来指定这个变量是属于哪一个类的。
//查询所有的学生信息,并显示班级名称
#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;
};
以下是多表查询的全部代码。我们通过视图的方式创建了一个classes_student类,通过操作这个类就可以,对这个视图(临时表)进行操作,这样就把多表查询简化成了普通的查询。
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;}
}
5.部分字段查询
odb默认只支持全字段查询,所以我们如果只想查询某个表的某些字段,就需要通过视图,创建一个临时表,然后对这个临时表操作,完成部分字段查询的操作。
这里我们只想查询学生表中的学生姓名字段,于是我们的视图中只有一个成员变量。在
#pragma db query
// 只查询学生姓名 , (?) 外部调用时传入的过滤条件
#pragma db view query("select name from Student")
struct all_name {std::string name;
};
以下是所有的部分字段查询的全部代码。可以看到这里的odb::query< student >这个是指定where条件的,我们要查询id等于1的学生的姓名。
db.query< struct all_name >是要针对all_name这个表进行操作,这里要填all_name而不是studnet.
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;}
}