ORM 全称是 Object Relational Mapping(对象关系映射),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。
面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的;而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。
为了解决以上两者不匹配的现象,对象关系映射技术应运而生。
简单理解,ORM 就是在数据(库)和对象之间作了一个映射。如下图所示:
右边的图我稍微解说一下,首先是类分为“定义”与“实例化”,类的定义只是定义类的结构名称等,而实例化则是依据定义在内存中生成具体的对象;而数据库也差不多,数据库有“表结构”和“表数据”,表数据就是由一行行的数据组成,每一行数据都需要尊从表结构中的字段。
C++中,ORM库比较有名的有下表中的四种。说明:以下数据为个人统计,用于参照,不一定就是对的。
ORM库的选择:
主页:http://www.qxorm.com/qxorm_en/home.html
gitee下载链接:https://gitee.com/jiangtao008/QxOrm
gitee仓库下载(速度快点)如有问题可去github搜索QxOrm一样可下载。
github下载:https://github.com/QxOrm/QxOrm
目前未找到pdf,但是在源码中有html网页手册,如下图路径:
打开index.html文件后,点击如下图即可找到官方手册:
使用QxOrm前一般都需要对源代码进行编译生成动态库,编译过程有时间另起文章。
为什么需要编译动态库,因为QxOrm为GNU/GPLv3开源协议,直接使用源代码则需要公开你的源代码,你的app使用他的源代码编译的动态库也需要公开源代码。。。。。,主要是使用动态库方便很多,所以像这种第三方库,没有特殊需求基本都是编译动态库使用。
其实使用开源软件编译的动态库,LGPL协议明确你的代码不需要开源,而GPL在国内这环境可以不开源!毕竟没人管你
不过开发商业app还是需要注意点,毕竟国内已经有相关案例了。
GPL认知及诉讼案例:https://blog.csdn.net/weixin_42887343/article/details/121157052
GNU/GPL V3快速指南:https://www.gnu.org/licenses/quick-guide-gplv3.html
图片来源github。
说明: 默认情况下,QxOrm 库只依赖 QtCore 和 QtSql 模块,如果启用 QxOrm HTTP web server 特性,那么还将依赖于 QtNetwork 模块,除此之外,有些特性还需要依赖 boost(默认禁用,如xml序列化)。
在使用前,我们需要将下载的源代码编译成动态库的形式,然后给我们的项目使用。
编译方法:
如果使用qt编译的话,我们直接使用qtCreator打开QxOrm.pro文件即可进行编译,如下图,使用项目默认配置即可。
如果需要其他配置,则打开QxOrm.pri文件,依据注释信息进行编辑。如下图,开启QxOrm对qt类的序列化支持。
编译输出:
使用qt编译的话,编译的动态库会在源代码中的lib目录下,如下图所示:
include为头文件路劲,lib为动态库路劲。
注意: 最好debug和relase两个版本都编译好,便于后期开发。
(1)首先将QxOrm源代码(编译后的)放在你的项目根目录下,如下图所示:
(2)我的项目为qt项目,所以使用pro文件管理项目,在项目的pro文件中添加如下代码,具体内容看注释:
#_BUILDING_USER,通过它可以知道项目是否正在编译
DEFINES += _BUILDING_APP
#包含模块的pri文件,一些宏定义使用中需要用到
include($$PWD/QxOrm/QxOrm.pri)
#添加源代码的头文件路劲,后面代码包含头文件的时候就可以使用相对路径了
INCLUDEPATH += $$PWD/QxOrm/include
#添加编译好的动态库,QxOrm源码编译的动态库在QxOrm/lib目录中,最好编译debug、release两个版本
LIBS += -L$$PWD/QxOrm/lib
CONFIG(debug, debug|release) {
LIBS += -lQxOrmd
} else {
LIBS += -lQxOrm
}
qx::QxSqlDatabase::getSingleton()->setDriverName("QSQLITE");
qx::QxSqlDatabase::getSingleton()->setDatabaseName("./Users.db");
qx::QxSqlDatabase::getSingleton()->setHostName("localhost");
qx::QxSqlDatabase::getSingleton()->setUserName("root");
qx::QxSqlDatabase::getSingleton()->setPassword("");
void WinUser::initOrm()
{
// 数据库一,该方式为创建默认数据库
qx::QxSqlDatabase::getSingleton()->setDriverName("QSQLITE");
qx::QxSqlDatabase::getSingleton()->setDatabaseName("./Users.db");
qx::QxSqlDatabase::getSingleton()->setHostName("localhost");
qx::QxSqlDatabase::getSingleton()->setUserName("root");
qx::QxSqlDatabase::getSingleton()->setPassword("12345678");
// 数据库二
QSqlDatabase db2 = QSqlDatabase::addDatabase("QSQLITE"); //定义全局的,此处只是举例
db2.setDatabaseName("./Users2.db");
...
// 数据库三
QSqlDatabase db3 = QSqlDatabase::addDatabase("QSQLITE"); //定义全局的,此处只是举例
db3.setDatabaseName("./Users3.db");
...
// 数据库四
QSqlDatabase db4 = QSqlDatabase::addDatabase("QSQLITE"); //定义全局的,此处只是举例
db4.setDatabaseName("./Users4.db");
...
qx::dao::create_table<User>(); //数据库一创建表(默认)
qx::dao::create_table<User>(&db2); //数据库二创建表
qx::dao::create_table<User>(&db3); //数据库三创建表
qx::dao::create_table<User>(&db4); //数据库四创建表
QSqlError daoError = qx::dao::delete_all<User>();(默认)
daoError = qx::dao::delete_all<User>(&db2);
daoError = qx::dao::delete_all<User>(&db3);
daoError = qx::dao::delete_all<User>(&db4);
}
当连接多个数据库的时候,还是使用qt的原始方式连接,如下:
QSqlDatabase db2 = QSqlDatabase::addDatabase("QSQLITE");
只是在数据库操作的时候,需要将对应的QSqlDatabase指针传入,如果默认为空则操作默认连接的数据库,如下所示:
qx::dao::create_table<User>(); //为默认数据库创建表格
daoError = qx::dao::delete_all<User>(&db2); //为db2数据库创建表格
后面的其他所有表格类似。
qx::dao::create_table<User>();
其中传入的模板-User类是要求的,需要被qx注册,注册对象(数据库中对应的表结构)User示例如下:
tableUser.h文件
#ifndef USER_H
#define USER_H
#include "precompiled.h"
/******************************************************************
* User 类对应的是数据库中的 User 表,而类的属性对应的是表中的一个字段(列)
* 所以,User 类实例对应 User 数据库表中的一条记录(record)
* 以上都需要的Qx注册
* ***************************************************************/
class APP_DLL_EXPORT User
{
public:
User():id(0)
{ }
~User()
{ }
//user的属性
long id;
QString name;
int age;
};
/************************************************************
* QX_REGISTER_HPP_APP 宏是必须的,用于将 User 类注册到 QxOrm 的上下文中
* 参数一:表示要注册的当前类 - User
* 参数二:基类,如果没有基类,则使用 qx::trait::no_base_class_defined
* 参数三:用于序列化的类版本
* ***********************************************************/
QX_REGISTER_HPP_APP(User, qx::trait::no_base_class_defined, 1)
#endif // USER_H
tableUser.cpp文件
#include "tableUser.h"
#include
//与QX_REGISTER_HPP_APP宏一样
QX_REGISTER_CPP_APP(User)
namespace qx
{
/**************************************************************
* qx::register_class() 是一个设置函数
* 用于将 User 类对应的属性 注册到 QxOrm 的上下文中
* ***********************************************************/
template <> void register_class(QxClass<User> & t)
{
// 注册 User::id <=> 数据库中的主键
t.id(&User::id, "id");
// 注册 User::name 属性,使用的 key 是 name,version 是 1。
t.data(&User::name, "name", 1);
// 注册 User::age 属性,使用的 key 是 age。
t.data(&User::age, "age");
}
}
其中#include "precompiled.h"
为预编译头文件,如下:
#ifndef PRECOMPILED_H
#define PRECOMPILED_H
//预编译头文件,此文件写好后几乎不会变动,可以减少后期的编译时间
#include
#include "export.h"
#endif // PRECOMPILED_H
#include "export.h"
为动态库调用常用的宏定义,不具体说明了,代码如下:
#ifndef EXPORT_H
#define EXPORT_H
#ifdef _BUILDING_APP
#define APP_DLL_EXPORT QX_DLL_EXPORT_HELPER
#else
#define APP_DLL_EXPORT QX_DLL_IMPORT_HELPER
#endif
#ifdef _BUILDING_APP
#define QX_REGISTER_HPP_APP QX_REGISTER_HPP_EXPORT_DLL
#define QX_REGISTER_CPP_APP QX_REGISTER_CPP_EXPORT_DLL
#else
#define QX_REGISTER_HPP_APP QX_REGISTER_HPP_IMPORT_DLL
#define QX_REGISTER_CPP_APP QX_REGISTER_CPP_IMPORT_DLL
#endif
#endif // EXPORT_H
插入数据给出了一系列接口,如下图:
其中最基本的就是第一个接口了,因为使用的模板,所以插入的数据可以是当个User类实体,也可以是链表、容器、哈希等等。
示例:
QList<User> aList;
for(int i = 0;i < 500;i ++)
{
User user;
user.age = qrand() % 90 + 10;
user.name = QString ("jiangtao %1").arg(i);
aList.append(user);
//qx::dao::insert(user); //插入数据1
}
qx::dao::insert(aList); //插入数据2
QVector<User> aVector;
qx::dao::insert(aVector); //插入数据3
性能:
删除条数 | 花费时间 |
---|---|
1 | 67 ms |
200 | 81 ms |
2000 | 88 ms |
后续又测试了插入十万条数据,发现耗时也才1s左右(包含程序中可是有运行随机数函数的时间),这就很神奇!查看源代码,貌似使用了线程池,默认多线程插入(还没有看到这部分代码,还不确定)。
更新接口如下:
最常用的接口为update,其更新依据为User中的id值,使用如下:
//将id为1对应的name改为"test"
User user_update;
user_update.id = 1;
user_update.name = "test";
qx::dao::update(user_update);
在数据不存在的情况下编辑,使用update将会失败并返回错误信息。如果无法判断数据是否存在,则使用下图接口:
save接口在此数据库id对应的数据不存在时将会新增,如果存在则是编辑。
接口如下,有两种接口,下面的destroy类型的为软删除。
示例:
User user_delete;
user_delete.id = 3;
qx::dao::delete_by_id(user_delete); //1、以id为依据
QSqlError daoError = qx::dao::delete_all<User>(); //2、删除表中所有数据
qx::QxSqlQuery delete_query("WHERE User.name = 'test 8'");
qx::dao::delete_by_query<User>(delete_query); //3、使用过滤语句删除
性能:
删除条数 | 花费时间 |
---|---|
1 | 66 ms |
5 | 408 ms |
10 | 860 ms |
40 | 3000 ms |
400 | 32.7 s |
我觉得这里就很神奇,插入速度很快,删除速度却比较慢,查看了源代码也没有搞清楚,不过猜测删除是不能使用多线程的,需要考虑到线程安全问题吧,毕竟插入不存在线程安全问题。
官方手册中说明获取数据有三种方法,1是通过id获取,2是获取所有数据,3是通过过滤条件获取,如下图所示:
使用id为依据查询的,代码如下:
User user_fetch;
user_fetch.id = 225;
qx::dao::fetch_by_id<User>(user_fetch);
qDebug() << "Fetch user id:"<<user_fetch.id
<< " user age: " << user_fetch.age
<< " use name:"<<user_fetch.name;
判断表中数据是否存在
User user_fetch;
user_fetch.id = 225; //以id为依据
qx_bool bDaoExist = qx::dao::exist(user_fetch);
qDebug()<<"exist flag:"<<bDaoExist.toString();
个人感觉QxOrm的查询接口比较鸡肋。。。。。。。。。。。,如下图:
而且他们官方都说了,如果他们的接口无法满足你的需求,就需要使用Qt的QtQuery了。。。。。。,最关键的是,QxOrm查询的结果只能放在链表或容器等数据结构中,而不能转为QSqlQueryModel类型供QtableView直接显示!!!(不知道是不是我没有找到地方,目前无法实现)
接下来提供一些查询示例代码:
(1)sql语句搜索
使用QxSqlQuery 类,说是sql语句吧,又不完全…
qx::QxSqlQuery query("WHERE User.name = 'test 8'");
QList<User> list_of_female;
qx::dao::fetch_by_query(query, list_of_female);
for (int i = 0; i < list_of_female.count(); i++)
;//your code
(2)单条件搜索
qx_query query;
query.where("author.sex").isEqualTo(author::female);
qx::dao::fetch_by_query(query, list_of_female);
for (int i = 0; i < list_of_female.count(); i++)
;//your code
(3)组合搜索(官方示例):
qx_query query; //搜索3 - 组合搜索
query.where("sex").isEqualTo(author::female)
.and_("age").isGreaterThan(38)
.or_("last_name").isNotEqualTo("Dupont")
.or_("first_name").like("Alfred")
.and_OpenParenthesis("id").isLessThanOrEqualTo(999)
.and_("birth_date").isBetween(date1, date2)
.closeParenthesis()
.or_("id").in(50, 999, 11, 23, 78945)
.and_("is_deleted").isNotNull()
.orderAsc("last_name", "first_name", "sex")
.limit(50, 150);
QList<User> list_of_female;
qx::dao::fetch_by_query(query, list_of_female);
for (int i = 0; i < list_of_female.count(); i++)
;//your code
查询篇,如果写的qt代码,还是建议使用Qt的QtQuery进行查询,毕竟查询结果可以直接转为QSqlQueryModel、QSqlTableModel等数据模型,然后QtableView、QtableWidget就可以直接调用setModel显示数据。
这个部分就是将程序中的数据转为XML、Json等文件存储,或者反过来的过程,其中程序中的数据可以从数据库中读取,从Json中读取的数据也可以写入数据库中。
序列化代码全部定义在qx::serialization命名空间中,使用如下:
(1)XML文件的读写
注意QxOrm官方原话:
XML boost::serialization engine默认是关闭的:要开启这个特性,需要在QxOrm.pri(或者QxOrm.cmake)配置文件中定义。定义(去掉前面的注释)
_QX_ENABLE_BOOST_SERIALIZATION
和_QX_ENABLE_BOOST_SERIALIZATION_XML
编译选项。还需要构建boost::serialization二进制文件(因为这个模块不仅仅是头文件),并将这个boost 序列化二进制文件的路径设置为QxOrm.pri(或QxOrm.cmake)配置文件的QX_BOOST_LIB_PATH
、QX_BOOST_LIB_SERIALIZATION_DEBUG
和QX_BOOST_LIB_SERIALIZATION_RELEASE
变量。
也就是说,qt下无法使用它的xml序列化…
代码先贴上,官网也有:
//=== 将容器中的用户导出到 XML 文件中(序列化) ===
QVector<User> users;
qx::serialization::xml::to_file(users, "./export_users.xml");
//=== 将 XML 中的用户导入至新容器 ===
QVector<User> usersXmlTmp;
qx::serialization::xml::from_file(usersXmlTmp, "./export_users.xml");
(2)Json文件的读写
//=== 将容器中的用户导出到 Json 文件中(序列化) ===
qx::serialization::json::to_file(users, "./export_users.json");
//=== 将 Json 文件中的用户导入至新容器 ===
QVector<User> usersJsonTmp;
qx::serialization::json::from_file(usersJsonTmp, "./export_users.json");
QxOrm官方原话:
数据库事务是作为单个逻辑工作单元执行的一系列操作。如果事务执行过程中没有发生错误,则系统提交事务;如果在事务过程中发生错误,或者如果用户指定了回滚操作,那么事务中的数据操作不会持久化(保存)到数据库中。
QxOrm 库的qx::QxSession类目的就是自动管理数据库事务,使用非常方便,类中实现操作符重载,直接可以加上各个操作即可,示例代码如下:
// 创建一个会话:一个有效的线程数据库连接被自动分配给该会话并打开一个事务
qx::QxSession session;
session += qx::dao::delete_all<User>(); //删除User表中得所有数据
session += qx::dao::insert(aList); //重新加载一些数据(aList自定义的User表格数据)
updatatableView(); //对数据库做了操作后,更新表格显示
if(session.isValid())
QMessageBox::information(nullptr,"提示","事务操作成功!");
else
QMessageBox::information(nullptr,"提示","事务操作失败!\n" + session.firstError().text());
以下接口中,除了第一个,其他接口很少使用。
(1)计算表中数据量
可知道表中有多少行数据,代码如下:
long rowCount = qx::dao::count<User>();
qDebug() << "rowCount: " << rowCount;
(2)克隆用户
UserPtr uClone = qx::clone_to_qt_shared_ptr(*u1);
qDebug() << "Clone from u1: " << uClone->id << uClone->name << uClone->age;
(3)按类名(factory)创建新用户
qx::any uAny = qx::create("User");
(4)将用户插入到 qx::cache
qx::cache::set("users", users);
(5)从 qx::cache 中删除所有元素
qx::cache::clear();
QxOrm还有非常多的功能的,本文只是最最最基本使用,可以说是分享给大家的入门经验。
其中感觉QxOrm除了接口简明外,他的数据库表关联比较强大,官方重点介绍了Relationships篇章(3.8章)。
有时间的话还是可以看看QxOrm的源代码,比如很多接口使用模板,支持类、链表、等各种数据结构,我们可以研究研究实现方式。
文章可能存在很多错误,有了解的望指正,最后希望大家共同学习,共同进步!