环境:Windows, Visutal Studio 2019 MySQL 8.0
1.ODB使用简介
ODB的使用依赖于
1.ODB编译器
2.libodb(ODB 公共运行时库)
3.libodb-<database> ODB运行时特定数据库,本例中以MySQL为例
2与3已在上节中编译完成
https://blog.youkuaiyun.com/LutionYoung/article/details/143110367?spm=1001.2014.3001.5501
这三个工具的使用方式:
ODB编译器 ----> 编译xxx.hxx ----> 产生相关文件,包括c++代码 ----> c++编译器将这些代码文件编译进去并链接2与3提到的库 ----> 生成可执行文件
ODB的一个理念就是将c++类对象持久化(Making Objects Persistent)到数据库中,即将这个对象中的成员作为字段插入到数据库中,即使应用程序退出或这个类对象析构,这个类对象的数据仍然存在(即持久化)
2.example hello 工程配置
Visual Studio2019新建工程,这里是创建了一个链接Qt库的工程(并不是是使用Qt那套.pro的工程文件,仍然是.sln,创建其他c++项目也是一样的),QtWidgetsApplication2.h和QtWidgetsApplication2.cpp没有用到,可以不管。
工程文件结构:
工程配置:
附加包含目录:
common文件下放的是odb头文件、odb-mysql头文件和.lib文件,common目录下的结构:
附加库目录:
附加依赖项:
增加了person.hxx文件,该文件由ODB工程编译。
person.hxx文件内容(文件内容可以先跳过不看,先跑通整个编译流程):
// person.hxx
#ifndef PERSON_HXX
#define PERSON_HXX
#include <string>
#include <cstddef> // std::size_t
#include <odb/core.hxx> // (1)头文件 必须包含
#pragma db model version(1, 1) // (2)定义了当前数据库结构的版本,第一个数字1代表最低版本,第二个数字代表当前版本,用于数据库结构改变时使用
/* 该语句指定了std::string类型映射为MySQL的varchar(256)类型,默认映射为TEXT类型
* 因为MySQL5.7以后就有了严格模式,规定了TEXT BOOL等类型默认不允许(也可修改为允许)有默认值
* 这会导致升级model版本时的xxxpre.sql和xxxpost.sql无法在MySQL8.0中执行
*/
#pragma db value(std::string) type("VARCHAR(256)")
#pragma db object // (3)告诉ODB编译器这是一个持久类
class person
{
public:
// (4)构造函数 也可以是私有的
person (const std::string& first,
const std::string& last,
unsigned short age)
: first_ (first), last_ (last), age_ (age)
{
}
const std::string& first () const{return first_;}
const std::string&last () const{return last_;}
unsigned short
age () const
{
return age_;
}
void
age (unsigned short age)
{
age_ = age;
}
unsigned long id() { return id_; }
private:
friend class odb::access; // (5)让odb::access可以自由访问该类的对象
person () {}
// ODB会在数据库中创建id first last age这些字段,注意在类中这些成员名是带有'_'的,即字段名+'_'为成员的名称
// (6)告诉ODB下边的id_是持久类的标识符,即同一个持久类的不同的对象,标识符都不相同
#pragma db id auto // auto表示id是由数据库来安排的
unsigned long id_;
std::string first_;
std::string last_;
unsigned short age_;
// #pragma db default("default") // 设置数据库中middle字段的默认值 为了在数据库结构升级时使用
// std::string middle_ = "11111"; // 在版本(db model version)2中 增加该成员,即在数据库中增加该字段 该行设置了该持久类构造后middle_成员的默认值 持久化后会才写到数据库中 此时需要先执行xxxpre.sql 再执行xxxpost.sql后 新的应用程序就可以正常操作修改过的数据库中的person表了
};
// ODB提出view概念 使用view进行一些更复杂的查询操作
#pragma db view object(person)
struct person_stat
{
#pragma db column("count(" + person::id_ + ")") // 数量
std::size_t count;
#pragma db column("min(" + person::age_ + ")") // age的最小值
unsigned short min_age;
#pragma db column("max(" + person::age_ + ")") // age的最大值
unsigned short max_age;
};
#endif // PERSON_HXX
main.cpp文件内容(文件内容可以先跳过不看,先跑通整个编译流程):
#include "QtWidgetsApplication2.h"
#include <QtWidgets/QApplication>
#include <QFile>
#include <memory> // std::auto_ptr
#include <iostream>
// ODB公共库部分头文件
#include <odb/database.hxx>
#include <odb/transaction.hxx>
// ODB MySQL部分头文件
#include <odb/mysql/database.hxx>
// 持久化对象头文件
#include "person.hxx"
#include "person-odb.hxx"
using namespace std;
using namespace odb::core;
int main(int argc, char *argv[])
{
插入
typedef odb::query<person> query;
typedef odb::result<person> result;
try
{
std::string user = "root";
std::string passwd = "root";
std::string dbName = "database1";
std::string host = "localhost";
unsigned int port = 3306;
std::string socket;
std::string charset = "utf8";
unsigned long client_flags = 0; // CLIENT_SSL; // 客户端使用SSL 参考MYSQL C API
auto_ptr<database> db(new odb::mysql::database(user, passwd, dbName, host, port, socket, charset));
unsigned long john_id, jane_id, joe_id;
// 实例化持久化对象 并给字段赋值
{
person john("John", "Doe", 33);
person jane("Jane", "Doe", 32);
person joe("Joe", "Dirt", 30);
// 开始事务
transaction t(db->begin());
t.tracer(odb::stderr_tracer); // 使用tracer可打印ODB实际的SQL语句 但是值会用?来表示
// 让这些对象在数据库中持久化 并获取他们的id
john_id = db->persist(john);
jane_id = db->persist(jane);
joe_id = db->persist(joe);
t.commit(); // 提交事务 不提交事务则数据库会回滚
// transaction对象t 离开作用域之前也未提交 则数据库同样会回滚
}
}
catch (const odb::exception& e)
{
cerr << e.what() << endl;
}
查询
try {
std::string user = "root";
std::string passwd = "root";
std::string dbName = "database1";
std::string host = "localhost";
unsigned int port = 3306;
std::string socket;
std::string charset = "utf8";
unsigned long client_flags = 0; // CLIENT_SSL; // 客户端使用SSL 参考MYSQL C API
auto_ptr<database> db(new odb::mysql::database(user, passwd, dbName, host, port, socket, charset));
person john("John", "Doe", 33);
person jane("Jane", "Doe", 32);
person joe("Joe", "Dirt", 30);
transaction t(db->begin());
t.tracer(odb::stderr_tracer);
// 查询person表中年龄大于30的
result r(db->query<person>(query::age > 30));
for (result::iterator i(r.begin()); i != r.end(); ++i)
{
// 通过person类中的成员访问具体的结果
cout << "Hello, " << i->first() << " (" << i->id() << ")!" << endl;
}
t.commit();
}
catch (const odb::exception& e) {
cerr << e.what() << endl;
return 1;
}
修改
try {
std::string user = "root";
std::string passwd = "root";
std::string dbName = "database1";
std::string host = "localhost";
unsigned int port = 3306;
std::string socket;
std::string charset = "utf8";
unsigned long client_flags = 0; // CLIENT_SSL; // 客户端使用SSL 参考MYSQL C API
auto_ptr<database> db(new odb::mysql::database(user, passwd, dbName, host, port, socket, charset));
unsigned long john_id, jane_id, joe_id;
{
person john("John", "Doe", 33);
person jane("Jane", "Doe", 32);
person joe("Joe", "Dirt", 30);
transaction t(db->begin());
t.tracer(odb::stderr_tracer);
// 让这些对象在数据库中持久化 并获取他们的id
john_id = db->persist(john);
jane_id = db->persist(jane);
joe_id = db->persist(joe);
t.commit();
}
// 通过id修改Joe Dirt的age
{
transaction t(db->begin());
t.tracer(odb::stderr_tracer);
// 通过持久类对象的标识从数据库中获取该对象
auto_ptr<person> joe(db->load<person>(joe_id));
// 修改age
joe->age(joe->age() + 1);
// 更新该对象
db->update(*joe);
t.commit();
}
// 通过查询后修改
{
transaction t(db->begin());
t.tracer(odb::stderr_tracer);
// 如果数据库中只有一个 则可以使用query_one来查询 从而避免了迭代器遍历
auto_ptr<person> joe(db->query_one<person>(query::first == "Joe"
&& query::last == "Dirt"));
if (joe.get() != 0) {
joe->age(joe->age() + 1);
db->update(*joe);
}
t.commit();
}
}
catch (const odb::exception& e) {
cerr << e.what() << endl;
return 1;
}
使用view进行稍微复杂的操作
try {
std::string user = "root";
std::string passwd = "root";
std::string dbName = "database1";
std::string host = "localhost";
unsigned int port = 3306;
std::string socket;
std::string charset = "utf8";
unsigned long client_flags = 0; // CLIENT_SSL; // 客户端使用SSL 参考MYSQL C API
auto_ptr<database> db(new odb::mysql::database(user, passwd, dbName, host, port, socket, charset));
transaction t(db->begin());
t.tracer(odb::stderr_tracer);
// 查询的结果总是只有一个元素
// 查询出总的数量 最大age 最小age 查询的结果存储在person_stat中
person_stat ps(db->query_value<person_stat>());
cout << "count : " << ps.count << endl
<< "min age: " << ps.min_age << endl
<< "max age: " << ps.max_age << endl;
t.commit();
}
catch (const odb::exception& e) {
cerr << e.what() << endl;
return 1;
}
删除操作
try {
std::string user = "root";
std::string passwd = "root";
std::string dbName = "database1";
std::string host = "localhost";
unsigned int port = 3306;
std::string socket;
std::string charset = "utf8";
unsigned long client_flags = 0; // CLIENT_SSL; // 客户端使用SSL 参考MYSQL C API
auto_ptr<database> db(new odb::mysql::database(user, passwd, dbName, host, port, socket, charset));
// 插入John Doe
unsigned long john_id;
{
person john1("John", "Doe", 33);
person john2("John", "Doe", 33);
transaction t(db->begin());
t.tracer(odb::stderr_tracer);
// 让这些对象在数据库中持久化 并获取他们的id
john_id = db->persist(john1);
db->persist(john2);
t.commit();
}
// 删除John Doe
// 通过id删除
{
transaction t(db->begin());
t.tracer(odb::stderr_tracer);
db->erase<person>(john_id);
t.commit();
}
// 查找后删除
{
transaction t(db->begin());
t.tracer(odb::stderr_tracer);
auto_ptr<person> john(db->query_one<person>(query::first == "John" &&
query::last == "Doe"));
if (john.get() != 0) {
db->erase(*john);
}
t.commit();
}
}
catch (const odb::exception& e) {
cerr << e.what() << endl;
return 1;
}
return 0;
}
person.hxx需要设置自定义生成工具,让Visual Studio2019自动调用ODB编译:
配置命令行和输出:
命令行:
odb.exe --std c++11 --database mysql --generate-query --generate-schema %(Identity)
其中%(Identity)代表的是person.hxx
输出:
%(Filename)-odb.hxx;%(Filename)-odb.cxx;%(Filename)-odb.ixx;%(Filename).sql;%(Outputs)
因为odb编译person.hxx后会生成如下文件(还会由其他文件):
第一次编译会在目录下生成如下文件(编译应该会失败,但是ODB编译执行成功):
**person.sql:**用于创建person表,表的字段与person类的成员相关,要保证应用程序在执行前这张表已经创建好,在mysql中执行该文件中的sql语句:
**person.xml:**用于记录表结构的版本
**person.cxx.与person.hxx:**为ODB编译器生成的c++代码,需要使用Visual Studio2019添加到项目中进行编译,此时工程为:
执行该应用程序前,需要将odb公共库的dll和odbmysql的dll以及mysql库的dll放到exe的同级目录下:
然后执行应用程序,可以查看数据库中person表里已经有数据了。
3.表的结构的升级
因为数据库的结构可能会被修改,ODB支持这样的修改,例如在上边的person.hxx中,如下代码:
#pragma db model version(1, 1) // 定义了当前数据库结构的版本 第一个数字1代表最低版本 第二个数字2代表当前版本
修改为版本2:
#pragma db model version(1, 2) // 定义了当前数据库结构的版本 第一个数字1代表最低版本 第二个数字2代表当前版本
定义了表的结构的版本,并且ODB编译器会生成person.xml来记录表结构的版本,这个文件是ODB编译器自己来维护的。
当要为表增加一个字段时,解注释如下代码:
#pragma db default("default") // 设置数据库中middle字段的默认值 为了在数据库结构升级时使用
std::string middle_ = "11111"; // 该行设置了该持久类构造后middle_成员的默认值 持久化后会才写到数据库中
再次编译,会发现生成了两个文件:
先执行pre文件里的sql语句,再执行post文件里的sql语句,然后新的应用程序就可以正常使用新版本的数据库了。