一文学会QxOrm

目录

    • 一 ORM简介
      • 1.1 百度介绍
      • 1.2 诞生缘由
      • 1.3 在代码中的作用
      • 1.4 C++各ORM库及其对比
    • 二 QxOrm基本内容
      • 2.1 下载QxOrm源代码
      • 2.2 QxOrm数据手册
      • 2.3 开源协议
      • 2.4 QxORM主要特性包括
    • 三 QxOrm编译
    • 四 项目搭建
    • 五 QxOrm使用
      • 5.1 连接数据库
        • 默认连接一个数据库
        • 链接多个数据库
        • 多数据库连接说明
      • 5.2 创建表格
      • 5.3 插入数据
      • 5.4 更新/编辑数据
      • 5.5 删除数据
      • 5.6 获取(查询)数据
      • 5.7 查询方法
      • 5.8 数据序列化
      • 5.9 事务
      • 5.10 其他接口
    • 六 总结
    • 七、源代码解读

一 ORM简介

1.1 百度介绍

ORM 全称是 Object Relational Mapping(对象关系映射),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。

1.2 诞生缘由

面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的;而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。
为了解决以上两者不匹配的现象,对象关系映射技术应运而生。

简单理解,ORM 就是在数据(库)和对象之间作了一个映射。如下图所示:
一文学会QxOrm_第1张图片
右边的图我稍微解说一下,首先是类分为“定义”与“实例化”,类的定义只是定义类的结构名称等,而实例化则是依据定义在内存中生成具体的对象;而数据库也差不多,数据库有“表结构”和“表数据”,表数据就是由一行行的数据组成,每一行数据都需要尊从表结构中的字段。

1.3 在代码中的作用

  1. 统一访问接口。
  2. 简化访问方式。
  3. 实现分层模式。
  4. 提高安全特性。

1.4 C++各ORM库及其对比

C++中,ORM库比较有名的有下表中的四种。说明:以下数据为个人统计,用于参照,不一定就是对的。
一文学会QxOrm_第2张图片
ORM库的选择:

  • 如果是纯 C++ 开发,可以选择使用 ODB。它拥有大量的用户群体,(相比 LiteSQL)技术支持好,(相比 QxOrm)编译时间短,(相比 Wt::Dbo)提供了更多的特性,更重要的是它易于使用,并且提供了很全面的文档。
  • 如果是 Qt 开发,也可以选择使用 QxOrm。它几乎支持所有的数据库,并且也有良好的文档。除此之外,它还提供了一个图形编辑器 - QxEntityEditor,可以很方便地以图形方式来管理数据模型。

二 QxOrm基本内容

主页:http://www.qxorm.com/qxorm_en/home.html

2.1 下载QxOrm源代码

gitee下载链接:https://gitee.com/jiangtao008/QxOrm
gitee仓库下载(速度快点)如有问题可去github搜索QxOrm一样可下载。
github下载:https://github.com/QxOrm/QxOrm

2.2 QxOrm数据手册

目前未找到pdf,但是在源码中有html网页手册,如下图路径:
一文学会QxOrm_第3张图片
打开index.html文件后,点击如下图即可找到官方手册:
一文学会QxOrm_第4张图片

2.3 开源协议

使用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
一文学会QxOrm_第5张图片
图片来源github。

2.4 QxORM主要特性包括

  • 持久性:支持最常见的数据库,如 SQLite、MySQL、PostgreSQL、Oracle、MS SQL Server、MongoDB(具有 1-1、1-n、n-1 和 n-n 关系)。
  • 序列化:JSON、二进制和 XML 格式。
  • 反射(或内省):动态访问类定义、检索属性和调用类方法。
  • HTTP Web Server:独立的多线程 HTTP 1.1 web 服务器(支持 SSL/TLS、持久连接、cookie、会话、分块响应、URL 分发器/路由)。
  • JSON API:与 C++/Qt 以外的其他技术的互操作性(REST web 服务、QML 应用程序、脚本语言)。

说明: 默认情况下,QxOrm 库只依赖 QtCore 和 QtSql 模块,如果启用 QxOrm HTTP web server 特性,那么还将依赖于 QtNetwork 模块,除此之外,有些特性还需要依赖 boost(默认禁用,如xml序列化)。

三 QxOrm编译

在使用前,我们需要将下载的源代码编译成动态库的形式,然后给我们的项目使用。
编译方法:
如果使用qt编译的话,我们直接使用qtCreator打开QxOrm.pro文件即可进行编译,如下图,使用项目默认配置即可。
一文学会QxOrm_第6张图片
如果需要其他配置,则打开QxOrm.pri文件,依据注释信息进行编辑。如下图,开启QxOrm对qt类的序列化支持。
一文学会QxOrm_第7张图片

编译输出:
使用qt编译的话,编译的动态库会在源代码中的lib目录下,如下图所示:
一文学会QxOrm_第8张图片
include为头文件路劲,lib为动态库路劲。
注意: 最好debug和relase两个版本都编译好,便于后期开发。

四 项目搭建

(1)首先将QxOrm源代码(编译后的)放在你的项目根目录下,如下图所示:
一文学会QxOrm_第9张图片
(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
}

五 QxOrm使用

5.1 连接数据库

默认连接一个数据库
    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数据库创建表格

后面的其他所有表格类似。

5.2 创建表格

    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

5.3 插入数据

插入数据给出了一系列接口,如下图:
在这里插入图片描述
其中最基本的就是第一个接口了,因为使用的模板,所以插入的数据可以是当个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左右(包含程序中可是有运行随机数函数的时间),这就很神奇!查看源代码,貌似使用了线程池,默认多线程插入(还没有看到这部分代码,还不确定)。

5.4 更新/编辑数据

更新接口如下:
一文学会QxOrm_第10张图片
最常用的接口为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对应的数据不存在时将会新增,如果存在则是编辑。

5.5 删除数据

接口如下,有两种接口,下面的destroy类型的为软删除。
一文学会QxOrm_第11张图片
示例:

    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

我觉得这里就很神奇,插入速度很快,删除速度却比较慢,查看了源代码也没有搞清楚,不过猜测删除是不能使用多线程的,需要考虑到线程安全问题吧,毕竟插入不存在线程安全问题。

5.6 获取(查询)数据

官方手册中说明获取数据有三种方法,1是通过id获取,2是获取所有数据,3是通过过滤条件获取,如下图所示:
一文学会QxOrm_第12张图片
使用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();

5.7 查询方法

个人感觉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显示数据。

5.8 数据序列化

这个部分就是将程序中的数据转为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_PATHQX_BOOST_LIB_SERIALIZATION_DEBUGQX_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");

5.9 事务

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());

5.10 其他接口

以下接口中,除了第一个,其他接口很少使用。

(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的源代码,比如很多接口使用模板,支持类、链表、等各种数据结构,我们可以研究研究实现方式。

文章可能存在很多错误,有了解的望指正,最后希望大家共同学习,共同进步!

七、源代码解读

你可能感兴趣的:(源码解读,开源项目,sqlite,ORM,qt,QxOrm,qt,orm)