Dans ce chapitre, nous allons traiter plusieurs fonctionnalités de la librarie QxOrm avec la création
d'un projet de gestion d'un blog en C++.
Le tutoriel qxBlog est constitué des étapes suivantes :
1- Projet qxBlog -
gestion d'un blog en C++ avec les tables suivantes :
- blog : 1 blog est écrit par 1 author, peut avoir plusieurs comment et peut être associé à plusieurs category
- author : 1 author peut rédiger plusieurs blog
- comment : 1 comment est associé à 1 blog
- category : 1 category référence plusieurs blog
2- Le projet qxBlog du tutoriel a
l'arborescence suivante :
./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 de ce tutoriel est disponible dans le dossier
./test/qxBlog/ de la distribution de QxOrm.
Le fichier qxBlog.sqlite correspond à la base de données du tutoriel au format sqlite.
3- Contenu du fichier qxBlog.pro
:
Ce fichier permet la compilation du projet par l'outil qmake
fourni par la librairie Qt.
L'outil qmake est multi-plateforme, 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 librarie QxOrm qui est
elle-même dépendante des excellentes librairies 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, la librairie
QxOrm a besoin de cette information pour exporter ou importer certaines
classes. Pour notre projet, cette variable se nomme
_BUILDING_QX_BLOG.

4- 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 dépendants 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
- lorsque 1 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_REGISTER_HPP_QX_BLOG QX_REGISTER_HPP_EXPORT_DLL
#define QX_REGISTER_CPP_QX_BLOG QX_REGISTER_CPP_EXPORT_DLL
#else
#define QX_REGISTER_HPP_QX_BLOG QX_REGISTER_HPP_IMPORT_DLL
#define QX_REGISTER_CPP_QX_BLOG QX_REGISTER_CPP_IMPORT_DLL
#endif
#ifdef Q_OS_WIN
#define QX_BLOG_DLL_EXPORT __declspec(dllexport)
#define QX_BLOG_DLL_IMPORT __declspec(dllimport)
#else
#define QX_BLOG_DLL_EXPORT
#define QX_BLOG_DLL_IMPORT
#endif
#ifdef Q_OS_WIN
#ifndef _BUILDING_QX_BLOG
#undef QX_BLOG_DLL_EXPORT
#define QX_BLOG_DLL_EXPORT QX_BLOG_DLL_IMPORT
#endif
#endif
#endif
|
5- 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éta-programmation pour
fournir l'ensemble de ses fonctionnalités.
La méta-programmation étant couteuse en temps de compilation, votre
projet se compilera beaucoup plus vite avec ce fichier
precompiled.h.
Un autre avantage (et non des moindres) est que le fichier
QxOrm.h regroupe les fonctionnalités de base des librairies
boost et Qt.
Il n'est donc plus nécessaire d'écrire #include
<QtCore/QString.h> pour utiliser la classe QString de Qt par
exemple.
De même, pour utiliser les pointeurs intelligents de boost, il n'est
plus nécessaire d'écrire #include
<boost/shared_ptr.hpp>.
#ifndef _QX_BLOG_PRECOMPILED_HEADER_H_
#define _QX_BLOG_PRECOMPILED_HEADER_H_
#include <QxOrm.h>
#include "export.h"
#endif
|
6- Contenu des fichiers author.h et
author.cpp :
1 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 2 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 1 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 1 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 définissons également 2 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 boost::shared_ptr<blog> blog_ptr;
typedef std::vector<blog_ptr> list_blog; enum enum_sex { male, female, unknown }; QString m_id;
QString m_name;
QDate m_birthdate;
enum_sex m_sex;
list_blog m_blogX; author() : m_id(0), m_sex(unknown) { ; }
virtual ~author() { ; } 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
|
#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());
}
|
7- Contenu des fichiers comment.h et
comment.cpp :
1 comment est associé à 1 blog et 1 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 2 tables qui correspondent (nous
reprenons la table blog vue précédemment) :

De même que pour la classe author, nous définissons 2 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 boost::shared_ptr<blog> blog_ptr; long m_id;
QString m_text;
QDateTime m_dt_create;
blog_ptr m_blog; 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
|
#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");
}}
|
8- Contenu des fichiers category.h et
category.cpp :
1 category référence plusieurs blog et 1 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 3 tables qui correspondent (nous
reprenons la table blog vue précédemment) :

De même que pour les classes author et comment, nous définissons 2 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 boost::shared_ptr<blog> blog_ptr;
typedef qx::QxCollection<long, blog_ptr> list_blog; long m_id;
QString m_name;
QString m_desc;
list_blog m_blogX; 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
|
#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");
}}
|
9- Contenu des fichiers blog.h et
blog.cpp :
1 blog est écrit par 1 author, peut avoir plusieurs
comment et peut être associé à plusieurs category. Cette
classe contient donc 3 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 type de relation one-to-one est disponible en
annexe de ce tutoriel avec la classe/table person : 1
person correspond à 1 author.
De même que pour les autres classes, nous définissons 2 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: long m_id;
QString m_text;
QDateTime m_dt_creation;
author_ptr m_author;
list_comment m_commentX;
list_category m_categoryX; 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
|
#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");
}}
|
10- 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 sur le
site de Qt).
Outre la persistance, QxOrm possède également d'autres
fonctionnalités intéressantes liées à la gestion des données :
-
la serialization automatique des données ou collections de données au
format binaire et xml
- la reflection 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[])
{ QApplication app(argc, argv); 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(""); 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>(); 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); daoError = qx::dao::insert(authorX);
qAssert(qx::dao::count<author>() == 3); author_ptr author_clone = qx::clone(* author_2);
qAssert(author_clone->m_id == "author_id_2");
qAssert(author_clone->m_sex == author::female); 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); qx::dump(list_of_female_author); 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";
{ QSqlDatabase db = qx::QxSqlDatabase::getDatabase();
bool bCommit = db.transaction(); 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); if (bCommit) { db.commit(); }
else { db.rollback(); }
} 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; daoError = qx::dao::save(blog_1); blog_1->m_text = "update blog_text_1";
blog_1->m_author = author_2;
daoError = qx::dao::save(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); 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); 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"); qx::dump(blog_tmp); qx_bool bInvokeOk = qx::QxClassX::invoke("author", "age", author_1);
qAssert(bInvokeOk);
return 0;
}
|
Cliquez ici pour afficher les traces après exécution du programme qxBlog...
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, 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 écrouler les performances
d'une application. Il est important de maîtriser la notion de
relation entre les classes (voir le syndrôme n+1 que l'on peut
rencontrer avec Hibernate ou tout autre ORM).
Autre remarque : Il est important de signaler que les méthodes void
register_class(...) liées à vos classes sont appelées automatiquement
et lorsque c'est nécessaire par la librairie QxOrm.
Par rapport à d'autres librairies 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.
11- Annexe - exemple de relation one-to-one
avec la classe person :
Ajoutons à notre projet la classe person (fichiers person.h et
person.cpp) : 1 person correspond à 1 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 2 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 à 1 person, ce
qui correspond à une relation many-to-one sur la même table person.
De plus, si 1 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.
|