• QxOrm使用教程(ORM for C++ 对象关系映射)


    ORM

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

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

    简单理解,ORM 就是在数据库和对象之间作了一个映射:

    • 数据库的表(table) –> 类(class)

    • 记录(record,行数据)–> 对象(object)

    • 字段(field)–> 对象的属性(attribute)

    ORM 的优缺点

    优点:

    • ORM 提供了一种面向对象的查询语言,这使得开发者可以专注于对象模型,而不必关心数据库结构或 SQL 语义。
    • ORM 提供了大量的服务,开发者只需要关注业务逻辑,而不是重复的 CRUD(Create、Read、Update、Delete)操作,这可以减少大量的代码。
    • ORM 将迫使你使用 MVC 风格,这通常会使代码更加简洁、更容易理解。
    • ORM 有现成的工具,很多功能可以自动完成,比如预处理、事务等。
    • ORM 对数据库进行了抽象,因此从一种数据库切换到另一种(例如:从 MySQL 到 PostgreSQL)会很容易。

    缺点:

    • 无论是什么 ORM 框架,都需要花费相当大的精力去学习和理解。
    • ORM 抽象掉了数据库层,开发者无法了解底层数据库(和 SQL)的相关操作。
    • 对于复杂的查询,ORM 要么难以实现,要么性能不如原生的 SQL。

    主流的 ORM 框架

    目前为止,C++ 中主流的 ORM 框架有以下几个:

    其中,LiteSQL 和 ODB 不依赖于特定的框架,而 QxOrm 依赖于 Qt,Wt::Dbo 依赖于 Wt。

    综合考虑,如果是纯 C++ 开发,可以选择使用 ODB。它拥有大量的用户群体,(相比 LiteSQL)技术支持好,(相比 QxOrm)编译时间短,(相比 Wt::Dbo)提供了更多的特性,更重要的是它易于使用,并且提供了很全面的文档。

    当然,如果是 Qt 开发,也可以选择使用 QxOrm。它几乎支持所有的数据库,并且也有良好的文档。除此之外,它还提供了一个图形编辑器 - QxEntityEditor,可以很方便地以图形方式来管理数据模型。

    关于 QxOrm

    QxOrm 是一个 C++ 库,旨在为 C++/Qt 开发人员提供对象关系映射(ORM)功能(类似于 Java 中的 Hibernate,.Net 中的 NHibernate)。

    其主要特性包括:

    • 持久性:支持最常见的数据库,如 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(默认禁用)。

    编译 QxOrm

    进入 QxOrm 下载页面 ,选择最新版本进行下载,目前最新为 QxOrm 1.4.8 。下载完成之后,将 QxOrm 的 zip 包解压缩。

    对一些重点目录的介绍一下:

    • doc:介绍 QxOrm 的相关文档。
    • include:包含了 QxOrm 的所有头文件(.h)。
    • lib:库目录,用于存放编译后的 .lib 和 .dll 文件。
    • src:包含了 QxOrm 的所有源文件(.cpp)。
    • test:包含了 QxOrm 相关的示例程序。

    有关 QxOrm 的各个历史版本以及各版本的一些特性,都记录在 changes.txt 文件中,感兴趣的话可以大概看一看。

    由于源码包中提供了 CMakeLists.txt、QxOrm.pro 和 QxOrm.sln,所以无论你使用 CMake 还是 Qt Creator,亦或者是 Visual Studio,都能够快速编译 QxOrm。

    以 Qt Creator 为例,打开 QxOrm.pro 并进行编译。成功之后,对应的库会生成到 QxOrm/lib 目录下。

    默认情况下,这会生成共享库(动态链接库);倘若要生成静态链接库,需要启用 QxOrm.pri 中的 _QX_STATIC_BUILD 编译选项。

    QxOrm 使用

    QxOrm 几乎支持所有的主流数据库,比如 SQLite、MySQL、PostgreSQL、Oracle、MS SQL Server、MongoDB 等。为了快速了解它的用法,我们以 SQLite 为例,来介绍一些常见的数据库操作(例如:增删改查)。

    先来创建一个名为 User 的项目,项目所在目录与 QxOrm 解压目录同级,可以根据自己的需要调整。项目完整源代码下载地址

    项目文件

    项目文件由 User.pro 表示,它包含了项目中所有的文件列表(头文件、源文件),以及所有依赖项(QxOrm.pri 文件包含了与 Qt 和 boost 库的所有依赖项):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    # QxOrm 的通用配置,包含了 Qt 和 boost 库依赖
    include($$PWD/../../QxOrm/QxOrm.pri)

    CONFIG += console
    DEFINES += _BUILDING_USER

    # 预编译头文件
    !contains(DEFINES, _QX_NO_PRECOMPILED_HEADER) {
    PRECOMPILED_HEADER = precompiled.h
    }

    # QxOrm 库相关配置
    INCLUDEPATH += $$PWD/../../QxOrm/include
    LIBS += -L$$PWD/../../QxOrm/lib

    # 设置生成的目标名称、添加依赖库
    CONFIG(debug, debug|release) {
    TARGET = Userd
    LIBS += -lQxOrmd
    } else {
    TARGET = User
    LIBS += -lQxOrm
    }

    # 文件列表
    HEADERS += \
    precompiled.h \
    export.h \
    user.h

    SOURCES += \
    main.cpp \
    user.cpp

    这里有一个重要的常量 - _BUILDING_USER,通过它可以知道项目是否正在编译(参见 export.h 和 Windows 下的 DLL 机制 - 导入或导出函数、类…)。

    export.h 文件

    如果使用过 DLL,相信对此文件并不陌生,它可以管理类、函数 … 的导出/导入。

    QxOrm 使用了相同的机制来提供某些功能:因此对于使用 QxOrm 库的所有项目,export.h 文件是必需的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #ifndef EXPORT_H
    #define EXPORT_H

    #ifdef _BUILDING_USER
    #define USER_DLL_EXPORT QX_DLL_EXPORT_HELPER
    #else
    #define USER_DLL_EXPORT QX_DLL_IMPORT_HELPER
    #endif

    #ifdef _BUILDING_USER
    #define QX_REGISTER_HPP_USER QX_REGISTER_HPP_EXPORT_DLL
    #define QX_REGISTER_CPP_USER QX_REGISTER_CPP_EXPORT_DLL
    #else
    #define QX_REGISTER_HPP_USER QX_REGISTER_HPP_IMPORT_DLL
    #define QX_REGISTER_CPP_USER QX_REGISTER_CPP_IMPORT_DLL
    #endif

    #endif // EXPORT_H

    预编译头文件

    预编译头文件的作用在于:提高编译速度。换句话说,使用它能够减少项目的编译时间。

    QxOrm 使用元编程的概念来提供许多功能,元编程在编译时成本很高,因此使用 precompiled.h 文件可以更快地编译项目:

    1
    2
    3
    4
    5
    6
    7
    #ifndef PRECOMPILED_H
    #define PRECOMPILED_H

    #include <QxOrm.h>
    #include "export.h"

    #endif // PRECOMPILED_H

    此外,还有一个优点:文件 QxOrm.h 包含了 Qt 和 boost 库的基本功能,因此,不需要再编写像 #include <QtCore/QString.h> 这样的语句来使用 Qt 的 QString 类;同样地,也不需要编写像 #include <boost/shared_ptr.hpp> 这样的语句来使用 boost 库的智能指针。

    定义 User 类

    在 C++ 代码中,User 类对应的是数据库中的 User 表,而类的属性对应的是表中的一个字段(列)。因此,C++ 源代码中的一个 User 类实例对应 User 表中的一条记录(行),这种机制使得 C++ 源代码更易于开发和维护。

    为 User 类定义三个属性,id、name 和 age:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #ifndef USER_H
    #define USER_H

    class USER_DLL_EXPORT User
    {
    public:
    User() : id(0) { }
    virtual ~User() { }

    long id;
    QString name;
    int age;
    };

    QX_REGISTER_HPP_USER(User, qx::trait::no_base_class_defined, 1)

    #endif // USER_H

    QX_REGISTER_HPP_USER 宏是必须的,用于将 User 类注册到 QxOrm 的上下文中:

    • 参数一:表示要注册的当前类 - User。
    • 参数二:基类,如果没有基类,则使用 qx::trait::no_base_class_defined。
    • 参数三:用于序列化的类版本。

    在 user.cpp 文件中,需要实现 qx::register_class(),它是一个设置函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include "precompiled.h"
    #include "user.h"
    #include <QxOrm_Impl.h>

    QX_REGISTER_CPP_USER(User)

    namespace qx {
    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");
    }
    }

    和 QX_REGISTER_HPP_USER 相同,QX_REGISTER_CPP_USER 宏也是必需的,用于将 User 类注册到 QxOrm 的上下文中。

    基本应用

    现在,是时候一展身手了。通过 User 类,来了解 QxOrm 中的一些基本操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    #include "precompiled.h"
    #include "user.h"
    #include <QxOrm_Impl.h>

    int main(int argc, char *argv[])
    {
    QCoreApplication a(argc, argv);

    // 初始化参数,用于和数据库交互
    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("");

    // 在数据库中创建 User 表
    QSqlError daoError = qx::dao::create_table<User>();

    // 创建 3 个用户
    // 可以使用 std 和 Qt 智能指针:std::shared_ptr、QSharedPointer 等...
    typedef QSharedPointer<User> UserPtr;
    UserPtr u1;
    u1.reset(new User());
    u1->name = "Jack Ma";
    u1->age = 30;

    UserPtr u2;
    u2.reset(new User());
    u2->name = "Pony";
    u2->age = 25;

    UserPtr u3;
    u3.reset(new User());
    u3->name = "Waleon";
    u3->age = 18;

    // 将所有用户插入容器中
    // 可以使用 std、boost、Qt 和 qx::QxCollection<Key,Value> 中的许多容器
    typedef QVector<UserPtr> VectorUser;
    VectorUser users;
    users.push_back(u1);
    users.push_back(u2);
    users.push_back(u3);

    // 将容器中的所有用户插入到数据库中
    // p1、p2、p3 的 id 属性会自动更新
    daoError = qx::dao::insert(users);

    // 修改第二个用户的信息,并更新到数据库中
    u2->name = "Pony modified";
    u2->age = 38;
    daoError = qx::dao::update(u2);

    // 从数据库中删除第一个用户
    daoError = qx::dao::delete_by_id(u1);

    // 计算用户的数量
    long userCount = qx::dao::count<User>();
    qDebug() << "User Count: " << userCount;

    // 将 id 为 3 的用户取出,并传给一个新变量
    UserPtr userTmp;
    userTmp.reset(new User());
    userTmp->id = 3;
    daoError = qx::dao::fetch_by_id(userTmp);
    qDebug() << "User Tmp: " << userTmp->id << userTmp->name << userTmp->age;

    #if _QX_SERIALIZE_XML
    // 将容器中的用户导出到 XML 文件中(序列化)
    qx::serialization::xml::to_file(users, "./export_users.xml");

    // 将 XML 中的用户导入至新容器
    VectorUser usersXmlTmp;
    qx::serialization::xml::from_file(usersXmlTmp, "./export_users.xml");
    #endif

    #ifndef _QX_NO_JSON
    // 将容器中的用户导出到 Json 文件中(序列化)
    qx::serialization::json::to_file(users, "./export_users.json");

    // 将 Json 文件中的用户导入至新容器
    VectorUser usersJsonTmp;
    qx::serialization::json::from_file(usersJsonTmp, "./export_users.json");
    #endif

    // 克隆一个用户
    UserPtr uClone = qx::clone_to_qt_shared_ptr(*u1);
    qDebug() << "Clone from u1: " << uClone->id << uClone->name << uClone->age;

    // 按类名(factory)创建新用户
    qx::any uAny = qx::create("User");

    // 将用户插入到 qx::cache
    qx::cache::set("users", users);

    // 从 qx::cache 中删除所有元素
    qx::cache::clear();

    // 内存泄漏
    User *user = new User();

    return a.exec();
    }

    这里重点介绍一下 QxOrm_Impl.h,它的作用是检测内存泄露。如果使用 QxMemLeak 模块或 boost::serialization 引擎,应该在所有的 *.cpp 中包含它;否则,它便是可选的(非必须)。

    运行程序

    运行程序,除了会打印一系列的输出信息之外,还会生成相应的数据库文件和 JSON 文件。

    QxOrm 不会隐藏 SQL 查询(默认情况下,所有的语句都会显示),所以在控制台中可以看到执行过程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [QxOrm] qx::QxSqlDatabase : create new database connection in thread '0x2554' with key '{652e45d3-7186-4bd6-81d1-9ff32fcff744}'
    [QxOrm] sql query (total: 31.8 ms, db_exec: 0 ms) : CREATE TABLE User (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)
    [QxOrm] sql query (total: 12 ms, db_exec: 1.91 ms) : INSERT INTO User (name, age) VALUES (:name, :age)
    [QxOrm] sql query (total: 9.35 ms, db_exec: 0.511 ms) : UPDATE User SET id = :id, name = :name, age = :age WHERE id = :id_bis
    [QxOrm] sql query (total: 8.49 ms, db_exec: 8.43 ms) : DELETE FROM User WHERE id = :id
    [QxOrm] sql query (total: 0.125 ms, db_exec: 0.0994 ms) : SELECT COUNT(*) FROM User
    User Count: 2
    [QxOrm] sql query (total: 0.153 ms, db_exec: 0.068 ms) : SELECT User.id AS User_id_0, User.name AS User_name_0, User.age AS User_age_0 FROM User WHERE User.id = :id
    User Tmp: 3 "Rose" 18
    Clone from u1: 1 "Jack Ma" 30

    如果要查看数据库 Users.db 的信息,可以使用数据库可视化工具(例如:Navicat Premium)。

    JSON 文件是通过序列化生成的,打开 export_users.json,将会看到相应的数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    [
    {
    "age": 30,
    "id": 1,
    "name": "Jack Ma"
    },
    {
    "age": 38,
    "id": 2,
    "name": "Pony modified"
    },
    {
    "age": 18,
    "id": 3,
    "name": "Rose"
    }
    ]

    文献资料

    QxOrm网站提供了一个用户手册

    本文档的目的是提供用户指南,以学习如何使用QxOrm库功能。本手册适用于正在寻找一种解决方案来管理C ++ / Qt中的持久数据层的开发人员和软件架构师。要理解本文档,需要具备C ++和数据库的技术技能。

    注意:可以使用QxEntityEditor应用程序(用于QxOrm库的图形编辑器,数据模型设计器和源代码生成器)快速轻松地定义本手册/用户指南中描述的所有功能。QxOrm网站上提供了另一个专门针对QxEntityEditor应用程序的文档

    数据模型管理工具

    QxEntityEditor是QxOrm库的图形编辑器:QxEntityEditor提供了一种图形方法来管理数据模型。QxEntityEditor是多平台的(适用于Windows,Linux和Mac OS X),并为所有环境生成本机代码:台式机(Windows,Linux,Mac OS X),嵌入式和移动(Android,iOS,Windows Phone,Raspberry Pi等) )。 QxOrm网站上提供了QxEntityEditor应用程序的用户手册(文档)

    QxEntityEditor基于插件,并提供了多种导入/导出数据模型的方式:

    • 自动生成C ++持久类(在QxOrm上下文中注册);
    • 自动为SQLite,MySQL,PostgreSQL,Oracle和MS SQL Server生成DDL SQL脚本(数据库架构);
    • 管理每个项目版本(ALTER TABLE,ADD COLUMN,DROP INDEX等)的模式演变;
    • 使用 QxService模块通过网络传输数据模型并快速创建客户/服务器应用程序;
    • 导入用于SQLite,MySQL,PostgreSQL,Oracle和MS SQL Server数据库的现有数据库结构(使用ODBC连接或本机驱动程序);
    • 因为每个项目都是不同的,所以QxEntityEditor提供了几种自定义生成文件的方法(尤其是javascript引擎和集成的调试器)。

    结束语

    由于 QxOrm 可以自动对 Entity 对象与数据库中的 Table 进行属性与字段的映射,所以在实际项目中几乎不需要编写数据访问层的代码,这在很大程度上提高了我们的开发效率。

    但 QxOrm 不能解决 SQL 和数据库的所有问题(没有一种工具是万能的),所以有时也需要使用 Qt 的 QtSql 引擎来编写自己的 SQL 查询或存储过程。

  • 相关阅读:
    jQuery
    前端开发之JavaScript篇
    前端开发之css篇
    前端开发之html篇
    mysql续
    MySQL入门
    进程线程协程那些事儿
    Python之socket网络编程
    2016.6.24——vector<vector<int>>【Binary Tree Level Order Traversal】
    2016.6.21——Climbing Stairs
  • 原文地址:https://www.cnblogs.com/chinasoft/p/16065959.html
Copyright © 2020-2023  润新知