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 查詢或存儲過程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM