I. Présentation du projet qxBlog▲
QxOrm fournit les fonctionnalités suivantes à partir d'une simple fonction de paramétrage (similaire à un fichier de mapping XML pour Hibernate en Java) : - persistance : basé sur le module QtSql de Qt, communication avec de nombreuses bases de données (avec support des relations 1-1, 1-n, n-1 et n-n) ; - sérialisation : basé sur le module boost::serialization de boost, flux de données binaire et XML ; - réflexion (ou introspection) : accès aux classes, propriétés et méthodes par chaînes de caractères (gestion dynamique des objets). |
Le tutoriel qxBlog correspond à la création d'un projet C++ de gestion de blogs. Ce projet est lié à une base de données constituée des tables suivantes :
- blog : un blog est écrit par un author, peut avoir plusieurs comment et peut être associé à plusieurs category ;
- author : un author peut rédiger plusieurs blog ;
- comment : un comment est associé à un blog ;
- category : une category référence plusieurs blog.
II. Arborescence du projet qxBlog▲
./qxBlog/ ./qxBlog/qxBlog.pro ./qxBlog/qxBlog.sqlite |
./qxBlog/include/precompiled.h ./qxBlog/include/export.h ./qxBlog/include/author.h ./qxBlog/include/blog.h ./qxBlog/include/category.h ./qxBlog/include/comment.h |
./qxBlog/src/author.cpp ./qxBlog/src/blog.cpp ./qxBlog/src/category.cpp ./qxBlog/src/comment.cpp ./qxBlog/src/main.cpp |
Remarque : le code source de ce tutoriel est disponible dans le dossier './test/qxBlog/' de la distribution de QxOrm.
Il existe un projet similaire dans le dossier './test/qxBlogCompositeKey/' pour montrer comment définir des identifiants sur plusieurs colonnes (composite key).
Pour plus d'informations sur la notion de 'multi-columns primary key' : suivre ce lien de la FAQ de QxOrm.
Le fichier qxBlog.sqlite correspond à la base de données du tutoriel au format SQLite.
III. Contenu du fichier qxBlog.pro▲
Ce fichier permet la compilation du projet par l'outil qmake fourni par le framework Qt. L'outil qmake est multiplateforme, ce qui signifie que le projet qxBlog peut être compilé sous Windows, Linux, Mac, etc.
Le fichier qxBlog.pro décrit la liste de tous les fichiers du projet (header + source) ainsi que les dépendances du projet. Le projet qxBlog est dépendant de la bibliothèque QxOrm qui est elle-même dépendante des excellents frameworks boost et Qt. Pour simplifier la gestion des dépendances du projet, il est possible d'inclure le fichier QxOrm.pri.
Une information importante à renseigner dans le fichier qxBlog.pro est la définition d'une variable indiquant que l'on est en train de compiler le projet (voir le fichier export.h). En effet, tout comme le mécanisme des DLL sous Windows, QxOrm a besoin de cette information pour exporter ou importer certaines classes. Pour notre projet qxBlog, cette variable se nomme _BUILDING_QX_BLOG.
IV. Contenu du fichier export.h▲
L'écriture d'une DLL sous Windows impose d'avoir un fichier de ce type pour la gestion des 'export/import' des classes, fonctions, variables, etc. QxOrm utilise ce même mécanisme pour fournir certaines fonctionnalités : le fichier export.h est donc indispensable pour tous les projets qui dépendent de QxOrm.
Remarque : pour simplifier, il est possible de schématiser le mécanisme des DLL de la façon suivante :
- lorsque la DLL est compilée, l'outil de compilation exporte chaque classe ;
- lorsqu'une autre DLL dépendante de la précédente est compilée, l'outil de compilation importe les classes nécessaires de la DLL précédente.
#ifndef _QX_BLOG_EXPORT_H_
#define _QX_BLOG_EXPORT_H_
#ifdef _BUILDING_QX_BLOG
#define QX_BLOG_DLL_EXPORT QX_DLL_EXPORT_HELPER
#else
// _BUILDING_QX_BLOG
#define QX_BLOG_DLL_EXPORT QX_DLL_IMPORT_HELPER
#endif
// _BUILDING_QX_BLOG
#ifdef _BUILDING_QX_BLOG
#define QX_REGISTER_HPP_QX_BLOG QX_REGISTER_HPP_EXPORT_DLL
#define QX_REGISTER_CPP_QX_BLOG QX_REGISTER_CPP_EXPORT_DLL
#else
// _BUILDING_QX_BLOG
#define QX_REGISTER_HPP_QX_BLOG QX_REGISTER_HPP_IMPORT_DLL
#define QX_REGISTER_CPP_QX_BLOG QX_REGISTER_CPP_IMPORT_DLL
#endif
// _BUILDING_QX_BLOG
#endif
// _QX_BLOG_EXPORT_H_
V. Contenu du fichier precompiled.h▲
Il s'agit d'un en-tête précompilé (precompiled header) permettant d'optimiser les temps de compilation du projet. En effet, QxOrm utilise les techniques de métaprogrammation pour fournir l'ensemble de ses fonctionnalités. La métaprogrammation étant couteuse en temps de compilation, un projet C++ se compilera beaucoup plus vite avec un fichier precompiled.h.
Un seul fichier d'en-tête est nécessaire pour disposer de l'ensemble des fonctionnalités de QxOrm : le fichier QxOrm.h.
#ifndef _QX_BLOG_PRECOMPILED_HEADER_H_
#define _QX_BLOG_PRECOMPILED_HEADER_H_
#include
<QxOrm.h>
#include
"export.h"
#endif
// _QX_BLOG_PRECOMPILED_HEADER_H_
VI. Contenu des fichiers author.h et author.cpp (relation one-to-many)▲
Un author est une personne qui peut rédiger plusieurs blog : nous allons ainsi montrer comment utiliser la relation de type one-to-many. Au niveau base de données, voici les deux tables qui correspondent :
Dans le code C++, les propriétés de la classe author sont le symétrique des champs de la table author dans la base de données.
Donc une instance de la classe author dans le code C++ correspond à une ligne de la table author dans la base de données.
Ce principe permet d'écrire du code C++ simple de compréhension et facile à maintenir.
Nous ajoutons une méthode à notre classe author : int age() qui calculera l'âge en fonction de la date de naissance envoyée par la base de données. Nous écrivons également deux typedef pour représenter un pointeur (smart-pointer) vers un objet author ainsi qu'une collection de author. La classe author a un identifiant de type QString (par défaut, un identifiant dans le contexte QxOrm est de type long) : nous utilisons la macro QX_REGISTER_PRIMARY_KEY(author, QString) pour spécialiser le template.
La classe author s'écrit de la façon suivante :
#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_
class
blog;
class
QX_BLOG_DLL_EXPORT author
{
public
:
// -- typedef
typedef
boost::
shared_ptr<
blog>
blog_ptr;
typedef
std::
vector<
blog_ptr>
list_blog;
// -- enum
enum
enum_sex {
male, female, unknown }
;
// -- propriétés
QString m_id;
QString m_name;
QDate m_birthdate;
enum_sex m_sex;
list_blog m_blogX;
// -- constructeur, destructeur virtuel
author() : m_id(0
), m_sex(unknown) {
; }
virtual
~
author() {
; }
// -- méthodes
int
age() const
;
}
;
QX_REGISTER_PRIMARY_KEY(author, QString)
QX_REGISTER_HPP_QX_BLOG(author, qx::trait::
no_base_class_defined, 0
)
typedef
boost::
shared_ptr<
author>
author_ptr;
typedef
qx::
QxCollection<
QString, author_ptr>
list_author;
#endif
// _QX_BLOG_AUTHOR_H_
#include
"../include/precompiled.h"
#include
"../include/author.h"
#include
"../include/blog.h"
#include
<QxMemLeak.h>
QX_REGISTER_CPP_QX_BLOG(author)
namespace
qx {
template
<>
void
register_class(QxClass<
author>
&
t)
{
t.id(&
author::
m_id, "author_id"
);
t.data(&
author::
m_name, "name"
);
t.data(&
author::
m_birthdate, "birthdate"
);
t.data(&
author::
m_sex, "sex"
);
t.relationOneToMany(&
author::
m_blogX, "list_blog"
, "author_id"
);
t.fct_0<
int
>
(&
author::
age, "age"
);
}}
int
author::
age() const
{
if
(!
m_birthdate.isValid()) {
return
-
1
; }
return
(QDate::
currentDate().year() -
m_birthdate.year());
}
VII. Contenu des fichiers comment.h et comment.cpp (relation many-to-one)▲
Un comment est associé à un blog et un blog peut contenir plusieurs comment : nous allons ainsi montrer comment utiliser la relation de type many-to-one. Au niveau base de données, voici les deux tables qui correspondent (nous reprenons la table blog vue précédemment) :
De même que pour la classe author, nous écrivons deux typedef pour représenter un pointeur vers un objet comment ainsi qu'une collection de comment.
La classe comment s'écrit de la façon suivante :
#ifndef _QX_BLOG_COMMENT_H_
#define _QX_BLOG_COMMENT_H_
class
blog;
class
QX_BLOG_DLL_EXPORT comment
{
public
:
// -- typedef
typedef
boost::
shared_ptr<
blog>
blog_ptr;
// -- propriétés
long
m_id;
QString m_text;
QDateTime m_dt_create;
blog_ptr m_blog;
// -- constructeur, destructeur virtuel
comment() : m_id(0
) {
; }
virtual
~
comment() {
; }
}
;
QX_REGISTER_HPP_QX_BLOG(comment, qx::trait::
no_base_class_defined, 0
)
typedef
boost::
shared_ptr<
comment>
comment_ptr;
typedef
QList<
comment_ptr>
list_comment;
#endif
// _QX_BLOG_COMMENT_H_
#include
"../include/precompiled.h"
#include
"../include/comment.h"
#include
"../include/blog.h"
#include
<QxMemLeak.h>
QX_REGISTER_CPP_QX_BLOG(comment)
namespace
qx {
template
<>
void
register_class(QxClass<
comment>
&
t)
{
t.id(&
comment::
m_id, "comment_id"
);
t.data(&
comment::
m_text, "comment_text"
);
t.data(&
comment::
m_dt_create, "date_creation"
);
t.relationManyToOne(&
comment::
m_blog, "blog_id"
);
}}
VIII. Contenu des fichiers category.h et category.cpp (relation many-to-many)▲
Une category référence plusieurs blog et un blog peut appartenir à plusieurs category : nous allons ainsi montrer comment utiliser la relation de type many-to-many. Ce type de relation implique une table supplémentaire dans la base de données pour stocker la liste des id de chaque côté des relations. Au niveau base de données, voici les trois tables qui correspondent (nous reprenons la table blog vue précédemment) :
De même que pour les classes author et comment, nous écrivons deux typedef pour représenter un pointeur vers un objet category ainsi qu'une collection de category.
La classe category s'écrit de la façon suivante :
#ifndef _QX_BLOG_CATEGORY_H_
#define _QX_BLOG_CATEGORY_H_
class
blog;
class
QX_BLOG_DLL_EXPORT category
{
public
:
// -- typedef
typedef
boost::
shared_ptr<
blog>
blog_ptr;
typedef
qx::
QxCollection<
long
, blog_ptr>
list_blog;
// -- propriétés
long
m_id;
QString m_name;
QString m_desc;
list_blog m_blogX;
// -- constructeur, destructeur virtuel
category() : m_id(0
) {
; }
virtual
~
category() {
; }
}
;
QX_REGISTER_HPP_QX_BLOG(category, qx::trait::
no_base_class_defined, 0
)
typedef
QSharedPointer<
category>
category_ptr;
typedef
qx::
QxCollection<
long
, category_ptr>
list_category;
#endif
// _QX_BLOG_CATEGORY_H_
#include
"../include/precompiled.h"
#include
"../include/category.h"
#include
"../include/blog.h"
#include
<QxMemLeak.h>
QX_REGISTER_CPP_QX_BLOG(category)
namespace
qx {
template
<>
void
register_class(QxClass<
category>
&
t)
{
t.id(&
category::
m_id, "category_id"
);
t.data(&
category::
m_name, "name"
);
t.data(&
category::
m_desc, "description"
);
t.relationManyToMany(&
category::
m_blogX, "list_blog"
, "category_blog"
, "category_id"
, "blog_id"
);
}}
IX. Contenu des fichiers blog.h et blog.cpp (relations one-to-many, many-to-one et many-to-many)▲
Un blog est écrit par un author, peut avoir plusieurs comment et peut être associé à plusieurs category. Cette classe contient donc trois relations : one-to-many, many-to-one et many-to-many.
Remarque : QxOrm gère également le type de relation one-to-one qui est cependant beaucoup moins utilisée que les autres relations. Un exemple de relation one-to-one est disponible en annexe de ce tutoriel avec la classe/table person : une person correspond à un author.
De même que pour les autres classes, nous écrivons deux typedef pour représenter un pointeur vers un objet blog ainsi qu'une collection de blog.
La classe blog s'écrit de la façon suivante :
#ifndef _QX_BLOG_BLOG_H_
#define _QX_BLOG_BLOG_H_
#include
"author.h"
#include
"comment.h"
#include
"category.h"
class
QX_BLOG_DLL_EXPORT blog
{
public
:
// -- propriétés
long
m_id;
QString m_text;
QDateTime m_dt_creation;
author_ptr m_author;
list_comment m_commentX;
list_category m_categoryX;
// -- constructeur, destructeur virtuel
blog() : m_id(0
) {
; }
virtual
~
blog() {
; }
}
;
QX_REGISTER_HPP_QX_BLOG(blog, qx::trait::
no_base_class_defined, 0
)
typedef
boost::
shared_ptr<
blog>
blog_ptr;
typedef
std::
vector<
blog_ptr>
list_blog;
#endif
// _QX_BLOG_BLOG_H_
#include
"../include/precompiled.h"
#include
"../include/blog.h"
#include
<QxMemLeak.h>
QX_REGISTER_CPP_QX_BLOG(blog)
namespace
qx {
template
<>
void
register_class(QxClass<
blog>
&
t)
{
t.id(&
blog::
m_id, "blog_id"
);
t.data(&
blog::
m_text, "blog_text"
);
t.data(&
blog::
m_dt_creation, "date_creation"
);
t.relationManyToOne(&
blog::
m_author, "author_id"
);
t.relationOneToMany(&
blog::
m_commentX, "list_comment"
, "blog_id"
);
t.relationManyToMany(&
blog::
m_categoryX, "list_category"
, "category_blog"
, "blog_id"
, "category_id"
);
}}
X. Contenu du fichier main.cpp▲
QxOrm permet de communiquer de manière simple et performante avec de nombreuses bases de données (voir la liste des bases de données supportées sur le site de Qt). Il est recommandé d'utiliser un pilote spécifique à la base de données afin d'optimiser les performances : le pilote QODBC, bien que générique et permettant de s'interfacer avec la plupart des bases de données du marché, sera moins performant qu'un pilote natif (par exemple, le pilote QMYSQL pour une base de données MySQL).
Outre la persistance, QxOrm possède également d'autres fonctionnalités intéressantes liées à la gestion des données :
- la sérialisation automatique des données ou collections de données au format binaire et XML ;
- la réflexion (ou introspection) permettant d'accéder à la définition des classes paramétrées dans le contexte QxOrm ainsi que l'appel automatique des méthodes de classes.
#include
"../include/precompiled.h"
#include
<QtGui/qapplication.h>
#include
"../include/blog.h"
#include
"../include/author.h"
#include
"../include/comment.h"
#include
"../include/category.h"
#include
<QxMemLeak.h>
int
main(int
argc, char
*
argv[])
{
// Initialisation de l'objet application du framework Qt
QApplication app(argc, argv);
// Paramètres de connexion à la base de données
qx::QxSqlDatabase::
getSingleton()->
setDriverName("QSQLITE"
);
qx::QxSqlDatabase::
getSingleton()->
setDatabaseName("./qxBlog.sqlite"
);
qx::QxSqlDatabase::
getSingleton()->
setHostName("localhost"
);
qx::QxSqlDatabase::
getSingleton()->
setUserName("root"
);
qx::QxSqlDatabase::
getSingleton()->
setPassword(""
);
// Vide toutes les tables de la base de données
QSqlError daoError =
qx::dao::
delete_all<
author>
();
daoError =
qx::dao::
delete_all<
comment>
();
daoError =
qx::dao::
delete_all<
category>
();
daoError =
qx::dao::
delete_all<
blog>
();
// Création d'une liste de trois 'author'
author_ptr author_1; author_1.reset(new
author());
author_ptr author_2; author_2.reset(new
author());
author_ptr author_3; author_3.reset(new
author());
author_1->
m_id =
"author_id_1"
; author_1->
m_name =
"author_1"
;
author_1->
m_sex =
author::
male; author_1->
m_birthdate =
QDate::
currentDate();
author_2->
m_id =
"author_id_2"
; author_2->
m_name =
"author_2"
;
author_2->
m_sex =
author::
female; author_2->
m_birthdate =
QDate::
currentDate();
author_3->
m_id =
"author_id_3"
; author_3->
m_name =
"author_3"
;
author_3->
m_sex =
author::
female; author_3->
m_birthdate =
QDate::
currentDate();
list_author authorX;
authorX.insert(author_1->
m_id, author_1);
authorX.insert(author_2->
m_id, author_2);
authorX.insert(author_3->
m_id, author_3);
// Insertion de la liste des trois 'author' dans la base de données
daoError =
qx::dao::
insert(authorX);
qAssert(qx::dao::
count<
author>
() ==
3
);
// Création d'un clone de l'instance 'author_id_2'
author_ptr author_clone =
qx::
clone(*
author_2);
qAssert(author_clone->
m_id ==
"author_id_2"
);
qAssert(author_clone->
m_sex ==
author::
female);
// Construit une requête pour récupérer uniquement les 'author' de type 'female', soit 'author_id_2' et 'author_id_3'
qx::
QxSqlQuery query("WHERE author.sex = :sex"
);
query.bind(":sex"
, author::
female);
list_author list_of_female_author;
daoError =
qx::dao::
fetch_by_query(query, list_of_female_author);
qAssert(list_of_female_author.count() ==
2
);
// Affiche la liste des 'author' de type 'female' dans un flux XML
qx::
dump(list_of_female_author);
// Création de trois 'category' pour classer les 'blog'
category_ptr category_1 =
category_ptr(new
category());
category_ptr category_2 =
category_ptr(new
category());
category_ptr category_3 =
category_ptr(new
category());
category_1->
m_name =
"category_1"
; category_1->
m_desc =
"desc_1"
;
category_2->
m_name =
"category_2"
; category_2->
m_desc =
"desc_2"
;
category_3->
m_name =
"category_3"
; category_3->
m_desc =
"desc_3"
;
{
// Ouverture d'un 'scope' pour gérer la destruction d'une connexion temporaire à la base de données
// Création d'une connexion temporaire à la base de données et ouverture d'une transaction
QSqlDatabase db =
qx::QxSqlDatabase::
getDatabase();
bool
bCommit =
db.transaction();
// Insertion de trois 'category' dans la base de données en utilisant la connexion temporaire 'db'
daoError =
qx::dao::
insert(category_1, (&
db)); bCommit =
(bCommit &&
!
daoError.isValid());
daoError =
qx::dao::
insert(category_2, (&
db)); bCommit =
(bCommit &&
!
daoError.isValid());
daoError =
qx::dao::
insert(category_3, (&
db)); bCommit =
(bCommit &&
!
daoError.isValid());
qAssert(bCommit);
qAssert(category_1->
m_id !=
0
);
qAssert(category_2->
m_id !=
0
);
qAssert(category_3->
m_id !=
0
);
// Fin de la transaction => 'commit' ou 'rollback' en cas d'erreur
if
(bCommit) {
db.commit(); }
else
{
db.rollback(); }
}
// Fermeture du 'scope' : l'instance 'db' est détruite
// Création d'un 'blog' à partir du nom de la classe (design pattern 'factory')
boost::
any blog_any =
qx::
create("blog"
);
blog_ptr blog_1 =
boost::
any_cast<
blog_ptr>
(blog_any);
blog_1->
m_text =
"blog_text_1"
;
blog_1->
m_dt_creation =
QDateTime::
currentDateTime();
blog_1->
m_author =
author_1;
// Insertion de 'blog_1' dans la base de données avec la méthode 'save()'
daoError =
qx::dao::
save(blog_1);
// Modification des propriétés de 'blog_1' et mise à jour dans la base de données
blog_1->
m_text =
"update blog_text_1"
;
blog_1->
m_author =
author_2;
daoError =
qx::dao::
save(blog_1);
// Ajoute deux commentaires à 'blog_1'
comment_ptr comment_1; comment_1.reset(new
comment());
comment_ptr comment_2; comment_2.reset(new
comment());
comment_1->
m_text =
"comment_1 text"
;
comment_1->
m_dt_create =
QDateTime::
currentDateTime();
comment_1->
m_blog =
blog_1;
comment_2->
m_text =
"comment_2 text"
;
comment_2->
m_dt_create =
QDateTime::
currentDateTime();
comment_2->
m_blog =
blog_1;
daoError =
qx::dao::
insert(comment_1);
daoError =
qx::dao::
insert(comment_2);
qAssert(qx::dao::
count<
comment>
() ==
2
);
// Associe deux 'category' à 'blog_1' => nécessite une 3ème table pour la relation 'many-to-many'
blog_1->
m_categoryX.insert(category_1->
m_id, category_1);
blog_1->
m_categoryX.insert(category_3->
m_id, category_3);
daoError =
qx::dao::
save_with_relation("list_category"
, blog_1);
// Récupère avec une seule méthode 'blog_1' et toutes ses relations dans la base de données
blog_ptr blog_tmp; blog_tmp.reset(new
blog());
blog_tmp->
m_id =
blog_1->
m_id;
daoError =
qx::dao::
fetch_by_id_with_all_relation(blog_tmp);
qAssert(blog_tmp->
m_commentX.count() ==
2
);
qAssert(blog_tmp->
m_categoryX.count() ==
2
);
qAssert(blog_tmp->
m_text ==
"update blog_text_1"
);
qAssert(blog_tmp->
m_author &&
blog_tmp->
m_author->
m_id ==
"author_id_2"
);
// Affiche 'blog_tmp' provenant de la base de données dans un flux XML
qx::
dump(blog_tmp);
// Invoque la méthode 'age()' de la classe 'author' par le mécanisme de réflexion (ou introspection)
qx_bool bInvokeOk =
qx::QxClassX::
invoke("author"
, "age"
, author_1);
qAssert(bInvokeOk);
return
0
;
}
Voici les traces générées après exécution du programme qxBlog :
[QxOrm] qx::QxSqlDatabase : create new database connection in thread '4456' with key '{e986c95d-1cb0-4368-ad9c-3dd4ccd20b84}'
[QxOrm] sql query (93 ms) : DELETE FROM author
[QxOrm] sql query (63 ms) : DELETE FROM comment
[QxOrm] sql query (94 ms) : DELETE FROM category
[QxOrm] sql query (78 ms) : DELETE FROM blog
[QxOrm] sql query (62 ms) : INSERT INTO author (author_id, name, birthdate, sex) VALUES (:author_id, :name, :birthdate, :sex)
[QxOrm] sql query (0 ms) : SELECT COUNT(*) FROM author
[QxOrm] sql query (0 ms) : SELECT author.author_id AS author_author_id_0, author.name AS author_name_0, author.birthdate AS
author_birthdate_0, author.sex AS author_sex_0 FROM author WHERE author.sex = :sex
[QxOrm] start dump 'qx::QxCollection<QString, boost::shared_ptr<author>>'
<qx.QxCollection-QString_boost.shared_ptr-author-- class_id="0" tracking_level="0" version="0">
<count>2</count>
<item class_id="1" tracking_level="0" version="0">
<first class_id="2" tracking_level="0" version="0">author_id_2</first>
<second class_id="3" tracking_level="0" version="1">
<px class_id="4" tracking_level="1" version="0" object_id="_0">
<author_id>author_id_2</author_id>
<name>author_2</name>
<birthdate class_id="5" tracking_level="0" version="0">20100409</birthdate>
<sex>1</sex>
<list_blog class_id="6" tracking_level="0" version="0">
<count>0</count>
<item_version>1</item_version>
</list_blog>
</px>
</second>
</item>
<item>
<first>author_id_3</first>
<second>
<px class_id_reference="4" object_id="_1">
<author_id>author_id_3</author_id>
<name>author_3</name>
<birthdate>20100409</birthdate>
<sex>1</sex>
<list_blog>
<count>0</count>
<item_version>1</item_version>
</list_blog>
</px>
</second>
</item>
</qx.QxCollection-QString_boost.shared_ptr-author-->
[QxOrm] end dump 'qx::QxCollection<QString, boost::shared_ptr<author>>'
[QxOrm] sql query (0 ms) : INSERT INTO category (name, description) VALUES (:name, :description)
[QxOrm] sql query (0 ms) : INSERT INTO category (name, description) VALUES (:name, :description)
[QxOrm] sql query (0 ms) : INSERT INTO category (name, description) VALUES (:name, :description)
[QxOrm] sql query (0 ms) : INSERT INTO blog (blog_text, date_creation, author_id) VALUES (:blog_text, :date_creation, :author_id)
[QxOrm] sql query (0 ms) : SELECT blog.blog_id AS blog_blog_id_0 FROM blog WHERE blog_blog_id_0 = :blog_id
[QxOrm] sql query (0 ms) : UPDATE blog SET blog_id = :blog_id, blog_text = :blog_text, date_creation = :date_creation, author_id
= :author_id WHERE blog_id = :blog_id_bis
[QxOrm] sql query (78 ms) : INSERT INTO comment (comment_text, date_creation, blog_id) VALUES (:comment_text, :date_creation,
:blog_id)
[QxOrm] sql query (78 ms) : INSERT INTO comment (comment_text, date_creation, blog_id) VALUES (:comment_text, :date_creation,
:blog_id)
[QxOrm] sql query (0 ms) : SELECT COUNT(*) FROM comment
[QxOrm] sql query (0 ms) : SELECT blog.blog_id AS blog_blog_id_0 FROM blog WHERE blog_blog_id_0 = :blog_id
[QxOrm] sql query (0 ms) : UPDATE blog SET blog_id = :blog_id, blog_text = :blog_text, date_creation = :date_creation, author_id
= :author_id WHERE blog_id = :blog_id_bis
[QxOrm] sql query (0 ms) : SELECT category.category_id AS category_category_id_0 FROM category WHERE category_category_id_0
= :category_id
[QxOrm] sql query (0 ms) : UPDATE category SET category_id = :category_id, name = :name, description = :description WHERE
category_id = :category_id_bis
[QxOrm] sql query (0 ms) : SELECT category.category_id AS category_category_id_0 FROM category WHERE category_category_id_0
= :category_id
[QxOrm] sql query (0 ms) : UPDATE category SET category_id = :category_id, name = :name, description = :description WHERE
category_id = :category_id_bis
[QxOrm] sql query (extra-table) : DELETE FROM category_blog WHERE category_blog.blog_id = :blog_id
[QxOrm] sql query (extra-table) : INSERT INTO category_blog (blog_id, category_id) VALUES (:blog_id, :category_id)
[QxOrm] sql query (0 ms) : SELECT blog.blog_id AS blog_blog_id_0, blog.blog_text AS blog_blog_text_0, blog.date_creation AS
blog_date_creation_0, blog.author_id AS blog_author_id_0, author_1.author_id AS author_1_author_id_0, author_1.name AS
author_1_name_0, author_1.birthdate AS author_1_birthdate_0, author_1.sex AS author_1_sex_0, comment_2.comment_id AS
comment_2_comment_id_0, comment_2.blog_id AS comment_2_blog_id_0, comment_2.comment_text AS comment_2_comment_text_0,
comment_2.date_creation AS comment_2_date_creation_0, category_3.category_id AS category_3_category_id_0, category_3.name
AS category_3_name_0, category_3.description AS category_3_description_0 FROM blog LEFT OUTER JOIN author author_1 ON
author_1_author_id_0 = blog_author_id_0 LEFT OUTER JOIN comment comment_2 ON comment_2_blog_id_0 = blog_blog_id_0
LEFT OUTER JOIN category_blog ON blog_blog_id_0 = category_blog.blog_id LEFT OUTER JOIN category category_3 ON
category_blog.category_id = category_3_category_id_0 WHERE blog_blog_id_0 = :blog_id
[QxOrm] start dump 'boost::shared_ptr<blog>'
<boost.shared_ptr-blog- class_id="0" tracking_level="0" version="1">
<px class_id="1" tracking_level="1" version="0" object_id="_0">
<blog_id>113</blog_id>
<blog_text class_id="2" tracking_level="0" version="0">update blog_text_1</blog_text>
<date_creation class_id="3" tracking_level="0" version="0">20100409162612000</date_creation>
<author_id class_id="4" tracking_level="0" version="1">
<px class_id="5" tracking_level="1" version="0" object_id="_1">
<author_id>author_id_2</author_id>
<name>author_2</name>
<birthdate class_id="6" tracking_level="0" version="0">20100409</birthdate>
<sex>1</sex>
<list_blog class_id="7" tracking_level="0" version="0">
<count>0</count>
<item_version>1</item_version>
</list_blog>
</px>
</author_id>
<list_comment class_id="8" tracking_level="0" version="0">
<count>2</count>
<item class_id="9" tracking_level="0" version="1">
<px class_id="10" tracking_level="1" version="0" object_id="_2">
<comment_id>209</comment_id>
<comment_text>comment_1 text</comment_text>
<date_creation>20100409162612000</date_creation>
<blog_id>
<px class_id_reference="1" object_id="_3">
<blog_id>113</blog_id>
<blog_text></blog_text>
<date_creation></date_creation>
<author_id>
<px class_id="-1"></px>
</author_id>
<list_comment>
<count>0</count>
</list_comment>
<list_category class_id="11" tracking_level="0" version="0">
<count>0</count>
</list_category>
</px>
</blog_id>
</px>
</item>
<item>
<px class_id_reference="10" object_id="_4">
<comment_id>210</comment_id>
<comment_text>comment_2 text</comment_text>
<date_creation>20100409162612000</date_creation>
<blog_id>
<px class_id_reference="1" object_id="_5">
<blog_id>113</blog_id>
<blog_text></blog_text>
<date_creation></date_creation>
<author_id>
<px class_id="-1"></px>
</author_id>
<list_comment>
<count>0</count>
</list_comment>
<list_category>
<count>0</count>
</list_category>
</px>
</blog_id>
</px>
</item>
</list_comment>
<list_category>
<count>2</count>
<item class_id="12" tracking_level="0" version="0">
<first>355</first>
<second class_id="13" tracking_level="0" version="0">
<qt_shared_ptr class_id="14" tracking_level="1" version="0" object_id="_6">
<category_id>355</category_id>
<name>category_1</name>
<description>desc_1</description>
<list_blog class_id="15" tracking_level="0" version="0">
<count>0</count>
</list_blog>
</qt_shared_ptr>
</second>
</item>
<item>
<first>357</first>
<second>
<qt_shared_ptr class_id_reference="14" object_id="_7">
<category_id>357</category_id>
<name>category_3</name>
<description>desc_3</description>
<list_blog>
<count>0</count>
</list_blog>
</qt_shared_ptr>
</second>
</item>
</list_category>
</px>
</boost.shared_ptr-blog->
[QxOrm] end dump 'boost::shared_ptr<blog>'
Remarque importante : QxOrm n'a pas pour objectif de 'cacher' le code SQL (par défaut toutes les requêtes sont tracées). Même si QxOrm simplifie énormément le code C++ et optimise ainsi les temps de développement et de maintenance d'un produit, il est très important d'avoir de bonnes connaissances SQL.
QxOrm ne résoudra pas toutes les problématiques liées aux bases de données (par exemple, jointure limitée à deux tables dans une même requête), il sera alors nécessaire d'utiliser directement le module QtSql de Qt en écrivant soi-même les requêtes SQL ou les procédures stockées. Enfin, il faut être extrêmement vigilant au nombre de requêtes effectuées entre le programme C++ et la base de données : un trop grand nombre d'appels à la base de données peut détériorer les performances d'une application. Il est important de maîtriser la notion de relations entre les classes ou bien de jointures pour les tables d'une base de données (voir le syndrome n+1 SELECT que l'on peut rencontrer avec Hibernate ou tout autre ORM).
Pour plus d'informations sur le langage SQL, regardez l'excellent site de Frédéric Brouart.
Autre remarque : il est important de signaler que les méthodes void register_class(...) sont appelées automatiquement et lorsque c'est nécessaire par QxOrm. Par rapport à d'autres bibliothèques qui nécessitent un appel pour enregistrer les types de chaque classe, QxOrm permet un enregistrement automatique des classes, ce qui simplifie encore un peu plus le développement d'applications.
XI. Annexe - exemple de relation one-to-one avec la classe person▲
Ajoutons à notre projet la classe person (fichiers person.h et person.cpp) : une person correspond à un author dans la base de données. Ils partagent donc le même identifiant, c'est ce qu'on appelle une relation de type one-to-one. Voici les deux tables de notre base de données (nous reprenons la table author vue précédemment) :
Remarque : nous ajoutons à la table person le champ mother_id.
Nous pouvons ainsi connaître la mère (de type person) associée à une person, ce qui correspond à une relation many-to-one sur la même table person.
De plus, si une person est une mère, nous pouvons connaître la liste de ses enfants (de type person), ce qui correspond à une relation one-to-many sur la même table person.
XII. Remerciements▲
Je remercie tout particulièrement Thibaut Cuvelier pour ses conseils pour l'amélioration de ce tutoriel.
Je remercie également Claude Leloup pour ses relectures et corrections orthographiques.