Qu'est-ce que QxOrm ?
|
QxOrm est une biblioth�que C++ open source de gestion de donn�es (Object Relational Mapping,
ORM).
QxOrm est d�velopp� XDL Teamarty, Ing�nieur en d�veloppement logiciel depuis 2003.
� partir d'une simple fonction de param�trage (que l'on peut comparer avec un fichier de
mapping XML Hibernate), vous aurez acc�s aux fonctionnalit�s suivantes :
-
persistance : communication avec de nombreuses
bases de donn�es (avec support des relations 1-1, 1-n, n-1 et n-n) ;
-
s�rialisation des donn�es (flux binaire, XML
et JSON) ;
- moteur de r�flexion (ou introspection) pour acc�der aux classes, attributs
et invoquer des m�thodes.
QxOrm est d�pendant des excellentes biblioth�ques boost (compatible � partir de la version 1.38) et Qt (compatible � partir de la version
4.5.0).
La biblioth�que QxOrm a �t� retenue pour faire partie du programme Qt
Ambassador.
|
Qu'est-ce que QxEntityEditor ?
|
QxEntityEditor est un �diteur graphique pour la biblioth�que QxOrm :
QxEntityEditor permet de g�rer graphiquement le mod�le d'entit�s.
QxEntityEditor est multi-plateforme (disponible pour Windows, Linux et Mac OS X) et g�n�re du
code natif pour tous les environnements : bureau (Windows, Linux, Mac OS X), embarqu� et mobile
(Android, iOS, Windows Phone, Raspberry Pi, etc.).
Une vid�o de pr�sentation de l'application QxEntityEditor est
disponible.
QxEntityEditor est bas� sur un syst�me de plugins et propose diverses fonctionnalit�s pour
importer/exporter le mod�le de donn�es :
- g�n�ration automatique du code C++ (classes persistantes enregistr�es dans le contexte QxOrm) ;
- g�n�ration automatique des scripts SQL DDL (sch�ma de base de donn�es) pour les bases SQLite,
MySQL, PostgreSQL, Oracle et MS SQL Server ;
- supporte l'�volution du sch�ma de base de donn�es pour chaque version d'un projet (ALTER
TABLE, ADD COLUMN, DROP INDEX, etc.) ;
- g�n�ration automatique des classes C++ de services pour transf�rer le mod�le de donn�es sur le
r�seau, en utilisant le module QxService, pour cr�er rapidement des applications client/serveur ;
- importation automatique des structures de bases de donn�es existantes (par connexion ODBC) pour
les bases SQLite, MySQL, PostgreSQL, Oracle et MS SQL Server ;
- parce que chaque projet est diff�rent, QxEntityEditor propose plusieurs outils pour
personnaliser les fichiers g�n�r�s (notamment un moteur javascript et un d�bogueur int�gr�).
QxEntityEditor est d�velopp� XDL Teamarty, Ing�nieur en d�veloppement logiciel depuis 2003.
|
Comment contacter QxOrm pour indiquer un bug ou poser une question
?
|
Si vous trouvez un bug ou si vous avez une question concernant le fonctionnement de la biblioth�que
QxOrm,
vous pouvez envoyer un mail � : support@qxorm.com.
Un forum (en anglais) d�di� � QxOrm est disponible en cliquant ici.
Vous pouvez �galement retrouver la communaut� fran�aise de QxOrm sur le forum de Developpez.com.
|
Comment installer et compiler QxOrm ?
|
Un tutoriel pour installer un environnement de d�veloppement avec QxOrm
sous Windows est disponible en cliquant ici.
QxOrm utilise le processus qmake de la biblioth�que Qt pour g�n�rer les
makefile et compiler le projet.
qmake est multiplateforme et fonctionne parfaitement sous Windows, Linux (Unix) et Mac.
Pour compiler QxOrm, il suffit d'ex�cuter les commandes suivantes :
qmake
make debug
make release
Sous Windows, des fichiers *.vcproj et *.sln sont disponibles pour les �diteurs
Visual C++ 2008, Visual C++ 2010 et Visual C++ 2012.
Les fichiers *.pro sont lisibles par l'�diteur Qt Creator, et des plugins existent
permettant de s'interfacer avec de nombreux �diteurs C++.
Les fichiers mingw_build_all_debug.bat et mingw_build_all_release.bat pr�sents dans le
dossier ./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests avec le
compilateur MinGW sous Windows.
Les fichiers gcc_build_all_debug.sh et gcc_build_all_release.sh pr�sents dans le dossier
./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests avec GCC sous
Linux.
Enfin, les fichiers osx_build_all_debug.sh et osx_build_all_release.sh pr�sents dans le
dossier ./tools/ permettent de compiler rapidement QxOrm ainsi que tous les tests sous
Mac (merci � Dominique Billet pour l'�criture des scripts).
Remarque : suivant l'environnement de d�veloppement, il peut �tre n�cessaire de modifier le
fichier QxOrm.pri pour param�trer la configuration de la biblioth�que boost :
QX_BOOST_INCLUDE_PATH = $$quote(D:/Dvlp/_Libs/Boost/1_42/include)
QX_BOOST_LIB_PATH = $$quote(D:/Dvlp/_Libs/Boost/1_42/lib_shared)
QX_BOOST_LIB_SERIALIZATION_DEBUG = "boost_serialization-vc90-mt-gd-1_42"
QX_BOOST_LIB_SERIALIZATION_RELEASE = "boost_serialization-vc90-mt-1_42"
|
Quelles sont les bases de donn�es prises en compte par QxOrm ?
|
QxOrm utilise le moteur QtSql de Qt bas� sur un syst�me de plugin.
Une liste d�taill�e des bases de donn�es support�es est disponible sur le site de Qt en cliquant ici.
Le plugin ODBC (QODBC) assure une compatibilit� avec de nombreuses bases de donn�es.
Pour des performances optimales, il est conseill� d'utiliser un plugin sp�cifique � une base de
donn�es :
- QMYSQL : MySQL ;
- QPSQL : PostgreSQL (versions 7.3 and above) ;
- QOCI : Oracle Call Interface Driver ;
- QSQLITE : SQLite version 3 ;
- QDB2 : IBM DB2 (version 7.1 and above) ;
- QIBASE : Borland InterBase ;
- QTDS : Sybase Adaptive Server.
|
Pourquoi QxOrm est d�pendant de deux biblioth�ques : boost et Qt ?
|
QxOrm utilise de nombreuses fonctionnalit�s disponibles dans les excellentes biblioth�ques
boost et Qt.
De plus, ces deux biblioth�ques sont utilis�es dans de nombreux projets � la fois professionnels et
open-source.
Il existe un grand nombre de forums, de tutoriaux, et toute une communaut� pour r�pondre � toutes les
probl�matiques que vous pourriez rencontrer.
L'objectif de QxOrm n'est pas de red�velopper des fonctionnalit�s qui existent d�j� mais de
fournir un outil performant d'acc�s aux bases de donn�es
comme il en existe dans d'autres langages (Java avec Hibernate, .Net avec
NHibernate, Ruby, Python, etc.).
 |
Qt : biblioth�que compl�te : IHM
(QtGui), r�seau (QtNetwork), XML (QtXml), base de donn�es
(QtSql), etc.
La documentation est excellente et le code C++ �crit � partir
de cette biblioth�que est � la fois performant et simple de
compr�hension.
Depuis le rachat par Nokia puis Digia et sa nouvelle licence LGPL, Qt est
sans contexte la biblioth�que phare du moment.
QxOrm est compatible avec les principaux objets d�finis par Qt
: QObject, QString, QDate, QTime, QDateTime, QList, QHash,
QSharedPointer, QScopedPointer, etc.
Il est conseill� d'installer et d'utiliser la derni�re version
de Qt disponible � l'adresse suivante : http://www.qt.io/ |
 |
boost : de nombreux modules de la
biblioth�que boost font partie de la nouvelle norme C++.
C'est une biblioth�que reconnue pour sa qualit�, son code 'C++
moderne', sa documentation, sa portabilit�, etc.
QxOrm utilise les fonctionnalit�s suivantes de boost :
smart_pointer, serialization, type_traits,
multi_index_container, unordered_container, any, tuple,
foreach, function.
Il est conseill� d'installer et d'utiliser la derni�re version
de boost disponible � l'adresse suivante : http://www.boost.org/
|
|
Pourquoi QxOrm n�cessite un en-t�te pr�compil� (precompiled header) pour pouvoir �tre
utilis� ?
|
QxOrm utilise les techniques de m�taprogrammation C++ pour fournir une grande partie de
ses fonctionnalit�s.
Vous n'avez pas besoin de savoir utiliser la m�taprogrammation pour travailler avec la biblioth�que
QxOrm.
En effet, QxOrm se veut simple d'utilisation et un code C++ �crit avec Qt et QxOrm est facile � lire,
donc facile � d�velopper et � maintenir.
Cependant, la m�taprogrammation est couteuse en temps de compilation.
En utilisant un fichier precompiled.h, un projet C++ se compilera beaucoup plus vite.
Un seul fichier d'en-t�te est n�cessaire pour disposer de l'ensemble des fonctionnalit�s de QxOrm : le
fichier QxOrm.h.
|
Est-il possible d'acc�l�rer les temps de compilation d'un projet ?
|
Oui, si la serialization des donn�es au format XML n'est pas utilis�e dans le projet, vous
pouvez d�sactiver cette fonctionnalit�.
Les temps de compilation seront alors r�duits mais vous n'aurez plus acc�s au namespace
qx::serialization:xml.
Pour d�sactiver la serialization XML, il faut ouvrir le fichier de configuration
QxOrm.pri et supprimer (ou mettre en commentaire) l'option de compilation
_QX_ENABLE_BOOST_SERIALIZATION_XML.
Une recompilation de la biblioth�que QxOrm est n�cessaire pour prendre en compte cette
modification.
Une autre possibilit� est d'utiliser les classes polymorphiques de la biblioth�que boost::serialization (� la place des classes template).
Cette fonctionnalit� r�duit les temps de compilation ainsi que la taille de l'�xecutable g�n�r�.
En contre-partie, la vitesse d'ex�cution du programme sera r�duite puisqu'une partie du travail
effectu� lors de la compilation devra �tre r�alis� � l'ex�cution de l'application.
Pour utiliser cette fonctionnalit� dans QxOrm, vous devez activer l'option de compilation
_QX_ENABLE_BOOST_SERIALIZATION_POLYMORPHIC dans le fichier de configuration
QxOrm.pri.
Attention : les fonctions de serialization seront alors accessibles depuis les
namespace suivants : qx::serialization::polymorphic_binary,
qx::serialization::polymorphic_text et qx::serialization::polymorphic_xml.
Une recompilation de la biblioth�que QxOrm est n�cessaire pour prendre en compte cette
modification.
Enfin, il est �galement possible d'utiliser la macro Q_PROPERTY pour d�clarer les propri�t�s si
la classe h�rite du type QObject.
Dans ce cas, il existe deux mani�res diff�rentes pour enregistrer les propri�t�s dans le contexte
QxOrm, dont une qui r�duit sensiblement les temps de compilation du projet.
Pour plus d'informations sur cette fonctionnalit�, rendez-vous sur cette
Question-R�ponse de la FAQ.
Remarque : il est �galement n�cessaire de s'assurer que toutes les optimisations propos�es par
le compilateur sont activ�es, notamment au niveau de la compilation parall�le sur plusieurs
processeurs :
- MSVC++ : utiliser la variable d'environnement SET CL=/MP
- GCC et Clang : pr�ciser le nombre de processeurs utilis�s en param�tre du process
make, par exemple pour 8 coeurs : SET MAKE_COMMAND=make -j8
|
Quels sont les diff�rents types de serialization disponibles
?
|
QxOrm utilise le framework de serialization propos� par la biblioth�que boost.
Il existe plusieurs types de serialization disponibles : binaire, XML, JSON, texte, etc.
Le fichier de configuration QxOrm.pri permet d'activer et/ou d�sactiver les diff�rents types de
serialization.
Chaque type de serialization poss�de ses propres caract�ristiques :
- binary : smallest, fastest, non-portable ;
- text : larger, slower, portable ;
- XML : largest, slowest, portable.
Remarque : le type binary n'est pas portable, ce qui signifie que des donn�es ne peuvent
pas s'�changer entre un syt�me Windows et un syst�me Unix par exemple.
Si vous devez faire transiter des donn�es sur un r�seau � travers plusieurs syst�mes d'exploitation,
il faut utiliser le type de serialization text ou XML.
QxOrm propose �galement une autre solution : le type de serialization portable_binary.
Le type portable_binary poss�de les m�mes caract�ristiques que le type binary et permet
d'�changer des donn�es de mani�re portable.
Cependant, ce type de serialization n'est pas propos� officiellement par la biblioth�que boost,
il est donc n�cessaire de tester avant de l'utiliser dans un logiciel en production.
|
Pourquoi QxOrm fournit un nouveau type de container qx::QxCollection<Key,
Value> ?
|
Il existe de nombreux container dans les biblioth�ques stl, boost et
Qt.
Il est donc l�gitime de se poser cette question : � quoi sert qx::QxCollection<Key,
Value> ?
qx::QxCollection<Key,
Value> est un nouveau container (bas� sur l'excellente biblioth�que boost::multi_index_container) qui poss�de les fonctionnalit�s suivantes :
- conserve l'ordre d'insertion des �l�ments dans la liste ;
- acc�s rapide � un �l�ment par son index : �quivaut � std::vector<T> ou
QList<T> par exemple ;
- acc�s rapide � un �l�ment par une cl� (hash-map) : �quivaut � QHash<Key,
Value> ou boost::unordered_map<Key, Value> par exemple ;
- fonctions de tri sur le type Key et sur le type Value.
Remarque :
qx::QxCollection<Key, Value> est compatible avec la macro foreach fournie par la
biblioth�que Qt ainsi que par la macro BOOST_FOREACH fournie par la biblioth�que boost.
Cependant, chaque �l�ment renvoy� par ces deux macros correspond � un objet de type
std::pair<Key, Value>.
Pour obtenir un r�sultat 'plus naturel' et plus lisible, il est conseill� d'utiliser la macro
_foreach : cette macro utilise BOOST_FOREACH pour tous les container sauf pour
qx::QxCollection<Key, Value>.
Dans ce cas, l'�l�ment renvoy� correspond au type Value (voir par la suite l'exemple
d'utilisation).
La macro _foreach est donc compatible avec tous les container (stl, Qt,
boost, etc.) puisqu'elle utilise la macro BOOST_FOREACH.
Autre Remarque :
qx::QxCollection<Key, Value> est particuli�rement adapt� pour recevoir des donn�es issues
d'une base de donn�es.
En effet, ces donn�es peuvent �tre tri�es (en utilisant ORDER BY dans une requ�te SQL par
exemple), il est donc important de conserver l'ordre d'insertion des �l�ments dans la liste.
De plus, chaque donn�e issue d'une base de donn�es poss�de un identifiant unique. Il est donc
int�ressant de pouvoir acc�der � un �l�ment en fonction de cet identifiant unique de mani�re
extr�mement rapide (hash-map).
Exemple d'utilisation de la collection qx::QxCollection<Key, Value> :
class drug { public: QString code; QString name; QString desc; };
typedef boost::shared_ptr<drug> drug_ptr;
qx::QxCollection<QString, drug_ptr> lstDrugs;
drug_ptr d1; d1.reset(new drug()); d1->code = "code1"; d1->name = "name1"; d1->desc = "desc1";
drug_ptr d2; d2.reset(new drug()); d2->code = "code2"; d2->name = "name2"; d2->desc = "desc2";
drug_ptr d3; d3.reset(new drug()); d3->code = "code3"; d3->name = "name3"; d3->desc = "desc3";
lstDrugs.insert(d1->code, d1);
lstDrugs.insert(d2->code, d2);
lstDrugs.insert(d3->code, d3);
_foreach(drug_ptr p, lstDrugs)
{ qDebug() << qPrintable(p->name) << " " << qPrintable(p->desc); }
for (long l = 0; l < lstDrugs.count(); ++l)
{
drug_ptr p = lstDrugs.getByIndex(l);
QString code = lstDrugs.getKeyByIndex(l);
qDebug() << qPrintable(p->name) << " " << qPrintable(p->desc);
}
qx::QxCollectionIterator<QString, drug_ptr> itr(lstDrugs);
while (itr.next())
{
QString code = itr.key();
qDebug() << qPrintable(itr.value()->name) << " " << qPrintable(itr.value()->desc);
}
lstDrugs.sortByKey(true);
lstDrugs.sortByValue(false);
drug_ptr p = lstDrugs.getByKey("code2");
drug_ptr p = lstDrugs.getByIndex(2);
bool bExist = lstDrugs.exist("code3");
bool bEmpty = lstDrugs.empty();
lstDrugs.removeByIndex(2);
lstDrugs.removeByKey("code3");
lstDrugs.clear();
|
|
Pourquoi QxOrm fournit un nouveau type de pointeur intelligent
qx::dao::ptr<T> ?
|
QxOrm est compatible avec les pointeurs intelligents des biblioth�ques boost et
Qt.
Le pointeur intelligent d�velopp� par QxOrm est bas� sur QSharedPointer et apporte de
nouvelles fonctionnalit�s s'il est utilis� avec les fonctions 'qx::dao::...'.
qx::dao::ptr<T>
conserve automatiquement les valeurs issues de la base de donn�es.
Il est ainsi possible de v�rifier � tout moment si une instance d'objet a subi des modifications gr�ce
� la m�thode 'isDirty()' : cette m�thode peut renvoyer la liste de toutes les propri�t�s ayant
�t� modifi�es.
qx::dao::ptr<T> peut �galement �tre utilis� par la fonction
'qx::dao::update_optimized()' pour mettre � jour en base de donn�es uniquement les champs
modifi�s.
qx::dao::ptr<T> peut �tre utilis� avec un objet simple ou bien avec la plupart des
containers : stl, boost, Qt et qx::QxCollection<Key, Value>.
Exemple d'utilisation du pointeur intelligent qx::dao::ptr<T> :
qx::dao::ptr<blog> blog_isdirty = qx::dao::ptr<blog>(new blog());
blog_isdirty->m_id = blog_1->m_id;
daoError = qx::dao::fetch_by_id(blog_isdirty);
qAssert(! daoError.isValid() && ! blog_isdirty.isDirty());
blog_isdirty->m_text = "blog property 'text' modified => blog is dirty !!!";
QStringList lstDiff; bool bDirty = blog_isdirty.isDirty(lstDiff);
qAssert(bDirty && (lstDiff.count() == 1) && (lstDiff.at(0) == "blog_text"));
if (bDirty) { qDebug("[QxOrm] test dirty 1 : blog is dirty => '%s'", qPrintable(lstDiff.join("|"))); }daoError = qx::dao::update_optimized(blog_isdirty);
qAssert(! daoError.isValid() && ! blog_isdirty.isDirty());
qx::dump(blog_isdirty);typedef qx::dao::ptr< QList<author_ptr> > type_lst_author_test_is_dirty;
type_lst_author_test_is_dirty container_isdirty = type_lst_author_test_is_dirty(new QList<author_ptr>());
daoError = qx::dao::fetch_all(container_isdirty);
qAssert(! daoError.isValid() && ! container_isdirty.isDirty() && (container_isdirty->count() == 3));
author_ptr author_ptr_dirty = container_isdirty->at(1);
author_ptr_dirty->m_name = "author name modified at index 1 => container is dirty !!!";
bDirty = container_isdirty.isDirty(lstDiff);
qAssert(bDirty && (lstDiff.count() == 1));
if (bDirty) { qDebug("[QxOrm] test dirty 2 : container is dirty => '%s'", qPrintable(lstDiff.join("|"))); }
author_ptr_dirty = container_isdirty->at(2);
author_ptr_dirty->m_birthdate = QDate(1998, 03, 06);
bDirty = container_isdirty.isDirty(lstDiff);
qAssert(bDirty && (lstDiff.count() == 2));
if (bDirty) { qDebug("[QxOrm] test dirty 3 : container is dirty => '%s'", qPrintable(lstDiff.join("|"))); }daoError = qx::dao::update_optimized(container_isdirty);
qAssert(! daoError.isValid() && ! container_isdirty.isDirty());
qx::dump(container_isdirty);QStringList lstColumns = QStringList() << "date_creation";
list_blog lst_blog_with_only_date_creation;
daoError = qx::dao::fetch_all(lst_blog_with_only_date_creation, NULL, lstColumns);
qAssert(! daoError.isValid() && (lst_blog_with_only_date_creation.size() > 0));
if ((lst_blog_with_only_date_creation.size() > 0) && (lst_blog_with_only_date_creation[0] != NULL))
{ qAssert(lst_blog_with_only_date_creation[0]->m_text.isEmpty()); }
qx::dump(lst_blog_with_only_date_creation);
|
|
Faut-il utiliser QString ou std::string ?
|
QxOrm conseille d'utiliser la classe QString pour la gestion des cha�nes de caract�res.
M�me si boost fournit de nombreuses fonctionnalit�s avec son module boost::string_algo,
la classe QString est plus simple � utiliser et surtout prend en charge automatiquement les
diff�rents formats : Ascii, Utf8, Utf16, etc.
Cependant, QxOrm est compatible avec std::string et std::wstring si vous pr�f�rez
utiliser ce type de cha�ne de caract�res.
|
Faut-il utiliser les pointeurs intelligents smart-pointer ?
|
QxOrm conseille fortement d'utiliser les pointeurs intelligents de boost ou
Qt.
Le langage C++ ne poss�de pas de Garbage Collector comme Java ou C# par
exemple.
L'utilisation des smart-pointer simplifie �norm�ment la gestion de la m�moire en C++.
L'id�al dans un programme C++ est de n'avoir aucun appel � delete ou delete[].
De plus, les smart-pointer font partie de la nouvelle norme C++ : C++1x.
Il est donc essentiel aujourd'hui de conna�tre les classes suivantes :
|
La cl� primaire est de type long par d�faut. Est-il possible d'utiliser une cl�
de type QString ou autre ?
|
Par d�faut, lorsqu'un mapping d'une classe C++ est �crit avec la m�thode void
qx::register_class<T>, l'identifiant associ� � la classe est de type long (cl�
primaire avec auto-incr�mentation dans la base de donn�es).
Il est possible de d�finir un identifiant d'un autre type en utilisant la macro
QX_REGISTER_PRIMARY_KEY.
Cette macro sp�cialise le template qx::trait::get_primary_key pour associer un type
d'identifiant � une classe C++.
Par exemple, pour d�finir un identifiant unique de type QString pour la classe C++
myClass (mapp�e vers une table de la BDD avec une colonne de type VARCHAR pour cl�
primaire), il suffit d'�crire :
QX_REGISTER_PRIMARY_KEY(myClass, QString)
Voici un exemple d'utilisation de la macro QX_REGISTER_PRIMARY_KEY avec une classe
author poss�dant un identifiant de type QString :
#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_
class author
{
public: QString m_id;
QString m_name; author() { ; }
virtual ~author() { ; }
};
QX_REGISTER_PRIMARY_KEY(author, QString)
QX_REGISTER_HPP_QX_BLOG(author, qx::trait::no_base_class_defined, 0)
#endif // _QX_BLOG_AUTHOR_H_
|
|
Comment d�finir une cl� primaire sur plusieurs colonnes (composite key)
?
|
QxOrm supporte la notion de 'multi-columns primary key'.
L'identifiant de la classe doit �tre du type suivant :
- QPair ou std::pair pour d�finir deux colonnes ;
- boost::tuple pour d�finir de deux � neuf colonnes.
Il est n�cessaire d'utiliser la macro QX_REGISTER_PRIMARY_KEY() pour sp�cialiser le
template et ainsi d�finir le type d'identifiant sur plusieurs colonnes.
La liste des noms des colonnes doit �tre de la forme suivante :
'column1|column2|column3|etc.'.
Exemple d'utilisation avec la classe 'author' du projet 'qxBlogCompositeKey',
cette classe poss�de un identifiant sur trois colonnes :
#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_
class blog;
class QX_BLOG_DLL_EXPORT author
{
QX_REGISTER_FRIEND_CLASS(author)
public: typedef boost::tuple<QString, long, QString> type_composite_key;
static QString str_composite_key() { return "author_id_0|author_id_1|author_id_2"; } typedef boost::shared_ptr<blog> blog_ptr;
typedef std::vector<blog_ptr> list_blog; enum enum_sex { male, female, unknown }; type_composite_key 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; type_composite_key getId() const { return m_id; }
QString getId_0() const { return boost::tuples::get<0>(m_id); }
long getId_1() const { return boost::tuples::get<1>(m_id); }
QString getId_2() const { return boost::tuples::get<2>(m_id); } void setId_0(const QString & s) { boost::tuples::get<0>(m_id) = s; }
void setId_1(long l) { boost::tuples::get<1>(m_id) = l; }
void setId_2(const QString & s) { boost::tuples::get<2>(m_id) = s; }
};
QX_REGISTER_PRIMARY_KEY(author, author::type_composite_key)
QX_REGISTER_HPP_QX_BLOG(author, qx::trait::no_base_class_defined, 0)
typedef boost::shared_ptr<author> author_ptr;
typedef qx::QxCollection<author::type_composite_key, author_ptr> list_author;
#endif // _QX_BLOG_AUTHOR_H_
|
#include "../include/precompiled.h"
#include "../include/author.h"
#include "../include/blog.h"
#include <QxOrm_Impl.h>
QX_REGISTER_CPP_QX_BLOG(author)
namespace qx {
template <> void register_class(QxClass<author> & t)
{
t.id(& author::m_id, author::str_composite_key());
t.data(& author::m_name, "name");
t.data(& author::m_birthdate, "birthdate");
t.data(& author::m_sex, "sex");
t.relationOneToMany(& author::m_blogX, blog::str_composite_key(), author::str_composite_key());
t.fct_0<int>(& author::age, "age");
}}
int author::age() const
{
if (! m_birthdate.isValid()) { return -1; }
return (QDate::currentDate().year() - m_birthdate.year());
}
|
|
Comment enregistrer des membres private ou protected dans le contexte
QxOrm ?
|
Pour enregistrer des membres private ou protected dans le contexte QxOrm (fonction
qx::register_class<T>), il faut d�clarer les friend class n�cessaires.
Pour simplifier l'�criture avec les template C++, la biblioth�que QxOrm fournit la macro
suivante : QX_REGISTER_FRIEND_CLASS(myClass).
Un exemple d'utilisation se trouve dans le dossier ./test/qxDllSample/dll1/ du package QxOrm
avec la classe CPerson :
namespace qx {
namespace test {
class QX_DLL1_EXPORT CPerson : public QObject
{
Q_OBJECT
QX_REGISTER_FRIEND_CLASS(qx::test::CPerson)
};
}}
|
|
Comment activer/d�sactiver le module QxMemLeak pour la d�tection automatique
des fuites m�moires ?
|
Le module QxMemLeak permet une d�tection rapide des fuites
m�moire en mode Debug une fois l'ex�cution du programme termin�e
(avec indication du fichier et de la ligne => style MFC de
Microsoft).
Ce module a �t� d�velopp� par Wu Yongwei
et a subi
quelques modifications pour �tre int�gr� dans QxOrm.
Si un autre outil est d�j� utilis� (Valgrind par exemple),
cette fonctionnalit� ne doit pas �tre activ�e.
Pour activer/d�sactiver le module QxMemLeak, il suffit de modifier la constante
_QX_USE_MEM_LEAK_DETECTION d�finie dans le fichier QxConfig.h.
Une recompilation de la biblioth�que QxOrm est n�cessaire pour prendre en compte cette modification.
|
Comment g�rer la notion d'h�ritage avec la base de donn�es ?
|
On retrouve g�n�ralement dans les diff�rents outils de type ORM trois diff�rentes strat�gies
pour g�rer la notion d'h�ritage avec la base de donn�es :
QxOrm utilise par d�faut la strat�gie Concrete Table Inheritance (les autres strat�gies
ne sont pas fonctionnelles � l'heure actuelle).
De nombreux tutoriaux et forums sont disponibles sur internet pour plus de d�tails sur cette notion
d'h�ritage.
Un exemple d'utilisation avec une classe de base se trouve dans le dossier
./test/qxDllSample/dll2/ avec la classe BaseClassTrigger.
|
Comment utiliser les Trigger ?
|
Les Trigger de QxOrm permettent d'effectuer divers traitements avant et/ou apr�s une
insertion, une mise � jour ou bien une suppression dans la base de donn�es.
Un exemple d'utilisation se trouve dans le dossier ./test/qxDllSample/dll2/ avec la classe
BaseClassTrigger.
Cette classe contient cinq propri�t�s : m_id, m_dateCreation, m_dateModification,
m_userCreation et m_userModification.
Ces propri�t�s se mettront � jour automatiquement pour chaque classe h�ritant de
BaseClassTrigger (cf. les classes Foo et Bar du m�me projet).
Il est n�cessaire de sp�cialiser le template 'QxDao_Trigger' pour profiter de cette
fonctionnalit�.
#ifndef _QX_BASE_CLASS_TRIGGER_H_
#define _QX_BASE_CLASS_TRIGGER_H_
class QX_DLL2_EXPORT BaseClassTrigger
{
QX_REGISTER_FRIEND_CLASS(BaseClassTrigger)
protected:
long m_id;
QDateTime m_dateCreation;
QDateTime m_dateModification;
QString m_userCreation;
QString m_userModification;
public:
BaseClassTrigger() : m_id(0) { ; }
virtual ~BaseClassTrigger() { ; }
long getId() const { return m_id; }
QDateTime getDateCreation() const { return m_dateCreation; }
QDateTime getDateModification() const { return m_dateModification; }
QString getUserCreation() const { return m_userCreation; }
QString getUserModification() const { return m_userModification; }
void setId(long l) { m_id = l; }
void setDateCreation(const QDateTime & dt) { m_dateCreation = dt; }
void setDateModification(const QDateTime & dt) { m_dateModification = dt; }
void setUserCreation(const QString & s) { m_userCreation = s; }
void setUserModification(const QString & s) { m_userModification = s; }
void onBeforeInsert(qx::dao::detail::IxDao_Helper * dao);
void onBeforeUpdate(qx::dao::detail::IxDao_Helper * dao);
};
QX_REGISTER_HPP_QX_DLL2(BaseClassTrigger, qx::trait::no_base_class_defined, 0)
namespace qx {
namespace dao {
namespace detail {
template <>
struct QxDao_Trigger<BaseClassTrigger>
{
static inline void onBeforeInsert(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
{ if (t) { t->onBeforeInsert(dao); } }
static inline void onBeforeUpdate(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
{ if (t) { t->onBeforeUpdate(dao); } }
static inline void onBeforeDelete(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onBeforeFetch(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onAfterInsert(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onAfterUpdate(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onAfterDelete(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
static inline void onAfterFetch(BaseClassTrigger * t, qx::dao::detail::IxDao_Helper * dao)
{ Q_UNUSED(t); Q_UNUSED(dao); }
};
}}}
#endif // _QX_BASE_CLASS_TRIGGER_H_
|
#include "../include/precompiled.h"
#include "../include/BaseClassTrigger.h"
#include <QxOrm_Impl.h>
QX_REGISTER_CPP_QX_DLL2(BaseClassTrigger)
namespace qx {
template <> void register_class(QxClass<BaseClassTrigger> & t)
{
IxDataMember * pData = NULL;
pData = t.id(& BaseClassTrigger::m_id, "id");
pData = t.data(& BaseClassTrigger::m_dateCreation, "date_creation");
pData = t.data(& BaseClassTrigger::m_dateModification, "date_modification");
pData = t.data(& BaseClassTrigger::m_userCreation, "user_creation");
pData = t.data(& BaseClassTrigger::m_userModification, "user_modification");
}}
void BaseClassTrigger::onBeforeInsert(qx::dao::detail::IxDao_Helper * dao)
{
Q_UNUSED(dao);
m_dateCreation = QDateTime::currentDateTime();
m_dateModification = QDateTime::currentDateTime();
m_userCreation = "current_user_1";
m_userModification = "current_user_1";
}
void BaseClassTrigger::onBeforeUpdate(qx::dao::detail::IxDao_Helper * dao)
{
Q_UNUSED(dao);
m_dateModification = QDateTime::currentDateTime();
m_userModification = "current_user_2";
}
|
|
Comment d�clarer une classe abstraite dans le contexte QxOrm ?
|
Une classe abstraite C++ (contenant au moins une m�thode virtuelle pure) ne peut pas �tre mapp�e avec
une table d'une base de donn�es (puisqu'elle ne peut pas �tre instanci�e).
Cependant, il peut �tre int�ressant de d�finir une classe abstraite contenant une liste de propri�t�s
utilis�es par plusieurs objets persistants.
Un exemple de classe abstraite se trouve dans le dossier ./test/qxDllSample/dll2/ de la
distribution de QxOrm avec la classe BaseClassTrigger.
QxOrm propose le m�canisme suivant pour d�finir une classe abstraite dans le contexte QxOrm :
- d�clarer la classe avec la m�thode 'void register_class' comme n'importe qu'elle autre
classe ;
- utiliser la macro QX_REGISTER_ABSTRACT_CLASS(className) juste apr�s la d�finition de la
classe.
|
Comment d�clarer une classe d�finie dans un espace de nom (namespace) dans le
contexte QxOrm ?
|
Si une classe est d�finie dans un espace de nom (namespace), alors une erreur de compilation se
produit avec l'utilisation des macros : QX_REGISTER_HPP et QX_REGISTER_CPP.
Pour �viter ces erreurs de compilation, il est n�cessaire d'utiliser les macros suivantes :
QX_REGISTER_COMPLEX_CLASS_NAME_HPP et QX_REGISTER_COMPLEX_CLASS_NAME_CPP.
Vous trouverez un exemple d'utilisation dans le dossier ./test/qxDllSample/dll1/ de la
distribution de QxOrm avec la classe CPerson d�finie dans l'espace de nom qx::test :
* QX_REGISTER_COMPLEX_CLASS_NAME_HPP_QX_DLL1(qx::test::CPerson, QObject, 0,
qx_test_CPerson)
Les macros QX_REGISTER_COMPLEX_CLASS_NAME... n�cessitent un param�tre suppl�mentaire (dans
l'exemple ci-dessus il s'agit du param�tre qx_test_CPerson) afin de cr�er une variable
globale.
Celle-ci est appel�e d�s le lancement de l'application.
La construction de cette instance globale d�clare la classe dans le module QxFactory (mod�le de
conception fabrique ou design pattern factory).
Un objet C++ ne pouvant pas se nommer avec des caract�res "::", le param�tre suppl�mentaire de
la macro permet de remplacer tous les "::" par des "_".
|
Comment utiliser le m�canisme de suppression logique (soft delete)
?
|
Une suppression logique permet de ne pas effacer de ligne dans une table d'une base de donn�es
(contrairement � une suppression physique) : une colonne suppl�mentaire est ajout�e � la d�finition de
la table pour indiquer que la ligne est supprim�e ou non.
Cette colonne peut contenir soit un bool�en (1 signifie ligne supprim�e, 0 ou vide signifie ligne non
supprim�e), soit la date-heure de suppression de la ligne (si vide, la ligne est consid�r�e comme non
supprim�e).
Il est donc � tout moment possible de r�activer une ligne supprim�e en r�initialisant la valeur � vide
dans la table de la base de donn�es.
Pour activer le m�canisme de suppression logique avec la biblioth�que QxOrm, il faut utiliser la
classe qx::QxSoftDelete dans la fonction de mapping qx::register_class<T>.
Voici un exemple d'utilisation avec une classe Bar contenant deux propri�t�s m_id et
m_desc :
namespace qx {
template <> void register_class(QxClass<Bar> & t)
{
t.setSoftDelete(qx::QxSoftDelete("deleted_at"));
t.id(& Bar::m_id, "id");
t.data(& Bar::m_desc, "desc");
}}
|
Les requ�tes SQL g�n�r�es automatiquement par la biblioth�que QxOrm vont prendre en compte ce
param�tre de suppression logique pour ajouter les conditions n�cessaires (ne pas r�cup�rer les
�l�ments supprim�s, ne pas supprimer physiquement une ligne, etc.).
Par exemple, si vous ex�cutez les lignes suivantes avec la classe Bar :
Bar_ptr pBar; pBar.reset(new Bar());
pBar->setId(5);
QSqlError daoError = qx::dao::delete_by_id(pBar); qAssert(! daoError.isValid());
qx_bool bDaoExist = qx::dao::exist(pBar); qAssert(! bDaoExist);
daoError = qx::dao::delete_all<Bar>(); qAssert(! daoError.isValid());
long lBarCount = qx::dao::count<Bar>(); qAssert(lBarCount == 0);
daoError = qx::dao::destroy_all<Bar>(); qAssert(! daoError.isValid());
|
Vous obtiendrez les traces suivantes :
[QxOrm] sql query (93 ms) : UPDATE Bar SET deleted_at = '20110617115148615' WHERE id = :id
[QxOrm] sql query (0 ms) : SELECT Bar.id AS Bar_id_0, Bar.deleted_at FROM Bar WHERE Bar.id = :id AND (Bar.deleted_at IS NULL OR Bar.deleted_at = '')
[QxOrm] sql query (78 ms) : UPDATE Bar SET deleted_at = '20110617115148724'
[QxOrm] sql query (0 ms) : SELECT COUNT(*) FROM Bar WHERE (Bar.deleted_at IS NULL OR Bar.deleted_at = '')
[QxOrm] sql query (110 ms) : DELETE FROM Bar
|
Remarque : pour supprimer physiquement une ligne de la base de donn�es, il faut utiliser les
fonctions : qx::dao::destroy_by_id() et qx::dao::destroy_all().
Autre remarque : il peut �tre int�ressant de d�finir au niveau du SGBD un index sur la colonne
deleted_at (ou peu importe le nom que vous donnez) afin d'acc�l�rer l'ex�cution des requ�tes
SQL.
|
Comment utiliser les sessions (classe qx::QxSession) pour simplifier la gestion
des transactions des bases de donn�es (C++ RAII) ?
|
Une transaction est une suite d'op�rations effectu�es comme une seule unit�
logique de travail.
Une fois termin�e, la transaction est :
* soit valid�e (commit), alors toutes les modifications sont faites dans la base de donn�es
;
* soit annul�e (rollback), alors toutes les modifications ne sont pas enregistr�e.
La classe qx::QxSession de la biblioth�que QxOrm permet de g�rer automatiquement les
transactions (validation, annulation) en utilisant le m�canisme C++ RAII :
{qx::QxSession session;session += qx::dao::insert(my_object, session.database());
session += qx::dao::update(my_object, session.database());
session += qx::dao::fetch_by_id(my_object, session.database());
session += qx::dao::delete_by_id(my_object, session.database());if (! session.isValid()) { qDebug("[QxOrm] session error : '%s'", qPrintable(session.firstError().text())); }
}
|
Remarque : une session peut d�clencher une exception de type qx::dao::sql_error
lorsqu'une erreur se produit (par d�faut, aucune exception n'est d�clench�e). Il est possible de
param�trer ce comportement en utilisant :
* soit le constructeur de la classe qx::QxSession (pour une session en particulier) ;
* soit le param�tre du singleton qx::QxSqlDatabase::getSingleton()->setSessionThrowable(bool b)
(pour toutes les sessions).
Autre remarque : il est important de ne pas oublier de passer la connection � la base de
donn�es de la session � chaque fonction qx::dao::xxx (en utilisant la m�thode
session.database()).
De plus, il est possible d'initialiser une session avec sa propre connection (provenant d'un pool de
connections par exemple) en utilisant le constructeur de la classe qx::QxSession.
La classe qx::QxSession propose �galement des m�thodes de persistance (CRUD), ce qui peut
simplifier l'�criture du code C++ suivant les habitudes de programmation.
Voici le m�me exemple en utilisant les m�thodes de la classe qx::QxSession � la place des
fonctions du namespace qx::dao :
{ qx::QxSession session; session.insert(my_object);
session.update(my_object);
session.fetchById(my_object);
session.deleteById(my_object); if (! session.isValid()) { qDebug("[QxOrm] session error : '%s'", qPrintable(session.firstError().text())); }
}
|
|
Comment persister un type dont on ne poss�de pas le code source (classe provenant
d'une biblioth�que tierce par exemple) ?
|
La biblioth�que QxOrm permet de persister n'importe quel type, m�me si ce dernier n'est pas enregistr�
dans le contexte QxOrm par la m�thode qx::register_class<T>().
Il est n�cessaire d'�crire les fonctions de s�rialisation de la biblioth�que boost, en utilisant la
m�thode non intrusive (puisque le code source n'est pas disponible ou ne peut pas �tre
modifi�).
Pour plus d'informations sur la s�rialisation des donn�es avec la biblioth�que boost, rendez-vous sur
le tutoriel
de developpez.com.
Par exemple, imaginons une classe 'ExtObject3D' provenant d'une biblioth�que tierce et dont le
code source n'est pas disponible ou ne peut pas �tre modifi�.
Voici le code n�cessaire pour pouvoir persister une instance de type 'ExtObject3D' en base de
donn�es :
#ifndef _PERSIST_EXTOBJECT3D_H_
#define _PERSIST_EXTOBJECT3D_H_
#include "ExtObject3D.h"
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/nvp.hpp>
namespace boost {
namespace serialization {
template <class Archive>
void save(Archive & ar, const ExtObject3D & t, unsigned int version)
{
Q_UNUSED(version);
double x(t.getX()), y(t.getY()), z(t.getZ()), angle(t.getAngle());
ar << boost::serialization::make_nvp("x", x);
ar << boost::serialization::make_nvp("y", y);
ar << boost::serialization::make_nvp("z", z);
ar << boost::serialization::make_nvp("angle", angle);
}
template <class Archive>
void load(Archive & ar, ExtObject3D & t, unsigned int version)
{
Q_UNUSED(version);
double x(0.0), y(0.0), z(0.0), angle(0.0);
ar >> boost::serialization::make_nvp("x", x);
ar >> boost::serialization::make_nvp("y", y);
ar >> boost::serialization::make_nvp("z", z);
ar >> boost::serialization::make_nvp("angle", angle);
t.setX(x);
t.setY(y);
t.setZ(z);
t.setAngle(angle);
}
}}
BOOST_SERIALIZATION_SPLIT_FREE(ExtObject3D)
#endif // _PERSIST_EXTOBJECT3D_H_
|
Le code ci-dessus est suffisant pour persister une instance de type 'ExtObject3D' en base de
donn�es : il est ainsi possible d'utiliser une propri�t� de type 'ExtObject3D' dans une classe
persistante enregistr�e dans le contexte QxOrm.
Cette propri�t� peut �tre mapp�e sur une colonne de type TEXT ou VARCHAR en base de
donn�es.
Le comportement par d�faut de la biblioth�que QxOrm est le suivant : l'instance est s�rialis�e au
format XML avant d'�tre ins�r�e ou mise � jour en base de donn�es.
Ce comportement par d�faut peut �tre utile par exemple si l'on souhaite enregistrer une collection
d'objets sans vouloir faire de relation (et donc g�rer une autre table dans la base de donn�es).
Par exemple, si l'on utilise une propri�t� de type std::vector<mon_objet> dans une classe
persistante sans relation associ�e, la liste d'�l�ments sera automatiquement enregistr�e au format XML
en base de donn�es.
Remarque : ce comportement par d�faut peut �tre facilement modifi� pour un type donn�.
Le moteur QtSql utilise le type QVariant pour faire le lien entre le code C++ et la base
de donn�es.
Le type QVariant peut contenir du texte, des valeurs num�riques, du binaire, etc.
Il peut donc �tre int�ressant de sp�cialiser le comportement par d�faut (s�rialisation XML) si l'on
souhaite stocker des donn�es au format binaire ou bien optimiser les performances (la s�rialisation
XML peut �tre couteuse en temps d'ex�cution).
Il suffit de proposer (en plus des fonctions de s�rialisation boost) les conversions n�cessaires en
QVariant, par exemple avec la classe 'ExtObject3D' :
namespace qx {
namespace cvt {
namespace detail {
template <> struct QxConvert_ToVariant< ExtObject3D > {
static inline QVariant toVariant(const ExtObject3D & t, const QString & format, int index)
{ } };
template <> struct QxConvert_FromVariant< ExtObject3D > {
static inline qx_bool fromVariant(const QVariant & v, ExtObject3D & t, const QString & format, int index)
{; return qx_bool(true); } };
}}}
|
|
Comment utiliser le moteur d'introspection (ou r�flexion) de la biblioth�que QxOrm
?
|
Toute classe enregistr�e dans le contexte QxOrm par la m�thode qx::register_class<T>()
peut �tre utilis�e par le moteur d'introspection (ou r�flexion) de la biblioth�que QxOrm.
Le moteur d'introspection permet d'obtenir de fa�on dynamique (donc pendant l'ex�cution du programme)
des informations propres � un type.
Ces informations correspondent � des m�ta-donn�es et d�crivent de fa�on exhaustive les
caract�ristiques d'une classe (propri�t�s, m�thodes, etc.).
De nombreux langages de programmation (par exemple Java ou C#) int�grent nativement ce m�canisme, ce
n'est pas le cas du C++, c'est pourquoi la biblioth�que QxOrm �mule un moteur d'introspection.
Voici la liste des classes disponibles pour acc�der aux m�ta-donn�es :
- qx::QxClassX :
singleton permettant de parcourir l'ensemble des classes enregistr�es dans le contexte QxOrm par
la m�thode qx::register_class<T>() ;
- qx::IxClass : interface
pour une classe enregistr�e dans le contexte QxOrm ;
- qx::IxDataMemberX : liste des propri�t�s associ�es � une classe ;
- qx::IxDataMember :
interface pour une propri�t� d'une classe ;
- qx::IxFunctionX :
liste des m�thodes associ�es � une classe ;
- qx::IxFunction :
interface pour une m�thode d'une classe.
Une instance de type qx::IxClass poss�de la liste des propri�t�s d'une classe
(qx::IxDataMemberX) ainsi que la liste des m�thodes d'une classe (qx::IxFunctionX).
Le moteur d'introspection de la biblioth�que QxOrm permet par exemple de :
- cr�er dynamiquement une instance en fonction du nom d'une classe sous forme de cha�ne de
caract�res (qx::create()) ;
- acc�der/modifier le contenu d'un champ d'un objet de fa�on dynamique en prenant pour param�tres
un objet et le nom du champ qu'on souhaite acc�der/modifier (qx::IxDataMember::getValue()
et qx::IxDataMember::setValue()) ;
- invoquer une m�thode de classe de fa�on dynamique, en g�rant bien entendu le passage des
param�tres souhait�s � la m�thode (qx::IxFunction::invoke()) ;
- acc�der � la hi�rarchie d'une classe (qx::IxClass::getBaseClass()).
Remarque : le module QxService de la biblioth�que QxOrm (cliquez ici pour acc�der au tutoriel) permettant de cr�er un serveur
d'applications C++ est bas� sur le moteur d'introspection pour appeler dynamiquement les m�thodes de
type service (demande du client) sur le serveur.
Voici un exemple d'utilisation du moteur d'introspection de la biblioth�que QxOrm : comment lister
toutes les classes, propri�t�s et m�thodes enregistr�es dans le contexte QxOrm ?
QString QxClassX::dumpAllClasses()
{
QxClassX::registerAllClasses();
QxCollection<QString, IxClass *> * pAllClasses = QxClassX::getAllClasses();
if (! pAllClasses) { qAssert(false); return ""; }
QString sDump;
long lCount = pAllClasses->count();
qDebug("[QxOrm] start dump all registered classes (%ld)", lCount);
_foreach(IxClass * pClass, (* pAllClasses))
{ if (pClass) { sDump += pClass->dumpClass(); } }
qDebug("[QxOrm] %s", "end dump all registered classes");
return sDump;
}
QString IxClass::dumpClass() const
{
QString sDump;
sDump += "-- class '" + m_sKey + "' (name '" + m_sName + "', ";
sDump += "description '" + m_sDescription + "', version '" + QString::number(m_lVersion) + "', ";
sDump += "base class '" + (getBaseClass() ? getBaseClass()->getKey() : "") + "')\n";
long lCount = (m_pDataMemberX ? m_pDataMemberX->count() : 0);
sDump += "\t* list of registered properties (" + QString::number(lCount) + ")\n";
if (m_pDataMemberX)
{
IxDataMember * pId = this->getId();
for (long l = 0; l < lCount; l++)
{
IxDataMember * p = m_pDataMemberX->get(l); if (! p) { continue; }
IxSqlRelation * pRelation = p->getSqlRelation();
QString sInfos = p->getKey() + ((p == pId) ? QString(" (id)") : QString());
sInfos += (pRelation ? (QString(" (") + pRelation->getDescription() + QString(")")) : QString());
sDump += "\t\t" + sInfos + "\n";
}
}
lCount = (m_pFctMemberX ? m_pFctMemberX->count() : 0);
sDump += "\t* list of registered functions (" + QString::number(lCount) + ")\n";
if (m_pFctMemberX)
{
_foreach_if(IxFunction_ptr p, (* m_pFctMemberX), (p))
{ QString sKey = p->getKey(); sDump += "\t\t" + sKey + "\n"; }
}
qDebug("%s", qPrintable(sDump));
return sDump;
}
|
Si on utilise la m�thode qx::QxClassX::dumpAllClasses() avec le tutoriel qxBlog, voici le r�sultat obtenu :
[QxOrm] start dump all registered classes (4)
-- class 'author' (name 'author', description '', version '0', base class '')
* list of registered properties (5)
author_id (id)
name
birthdate
sex
list_blog (relation one-to-many)
* list of registered functions (1)
age
-- class 'blog' (name 'blog', description '', version '0', base class '')
* list of registered properties (6)
blog_id (id)
blog_text
date_creation
author_id (relation many-to-one)
list_comment (relation one-to-many)
list_category (relation many-to-many)
* list of registered functions (0)
-- class 'comment' (name 'comment', description '', version '0', base class '')
* list of registered properties (4)
comment_id (id)
comment_text
date_creation
blog_id (relation many-to-one)
* list of registered functions (0)
-- class 'category' (name 'category', description '', version '0', base class '')
* list of registered properties (4)
category_id (id)
name
description
list_blog (relation many-to-many)
* list of registered functions (0)
[QxOrm] end dump all registered classes
|
Remarque : il est possible d'ajouter de nouvelles informations au moteur d'introspection en
utilisant la notion de property bag.
En effet, les classes qx::IxClass, qx::IxDataMember et qx::IxFunction poss�dent
chacune une liste d'�l�ments de type QVariant accessibles par cl� de type QString (voir
la classe qx::QxPropertyBag pour plus de d�tails sur cette notion).
|
Comment d�clarer automatiquement les m�ta-propri�t�s de Qt (d�finies par la macro
Q_PROPERTY) dans le contexte QxOrm ?
|
Toute classe h�ritant du type QObject peut d�clarer ses propri�t�s avec la macro Q_PROPERTY : les
propri�t�s deviennent alors des m�ta-propri�t�s.
Ce m�canisme permet au framework Qt de proposer un moteur d'introspection gr�ce au pr�-compilateur
moc.
Les m�ta-propri�t�s peuvent alors �tre utilis�es par exemple par le moteur QML,
QtScript, etc.
La biblioth�que QxOrm n�cessite une d�claration de chacune des propri�t�s d'une classe dans la
fonction de mapping void qx::register_class<T>() afin de proposer l'ensemble de ses
fonctionnalit�s (persistance des donn�es, s�rialisation XML, JSON et binaire, etc.).
Il est possible de d�clarer automatiquement dans le contexte QxOrm l'ensemble des m�ta-propri�t�s sans
maintenir une fonction de mapping void qx::register_class<T>() : la macro
QX_REGISTER_ALL_QT_PROPERTIES() utilise le moteur d'introspection de Qt pour parcourir la liste
des m�ta-propri�t�s.
Voici un exemple d'utilisation avec la classe TestQtProperty se trouvant dans le dossier
./test/qxDllSample/dll1/include/ de la distribution QxOrm :
#ifndef _QX_TEST_QT_META_PROPERTY_H_
#define _QX_TEST_QT_META_PROPERTY_H_
class QX_DLL1_EXPORT TestQtProperty : public QObject
{
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId)
Q_PROPERTY(long number READ number WRITE setNumber)
Q_PROPERTY(QString desc READ desc WRITE setDesc)
Q_PROPERTY(QDateTime birthDate READ birthDate WRITE setBirthDate)
Q_PROPERTY(QVariant photo READ photo WRITE setPhoto)
protected:
int m_id;
long m_number;
QString m_desc;
QDateTime m_birthDate;
QVariant m_photo;
public:
TestQtProperty() : QObject(), m_id(0), m_number(0) { ; }
virtual ~TestQtProperty() { ; }
int id() const { return m_id; }
long number() const { return m_number; }
QString desc() const { return m_desc; }
QDateTime birthDate() const { return m_birthDate; }
QVariant photo() const { return m_photo; }
void setId(int i) { m_id = i; }
void setNumber(long l) { m_number = l; }
void setDesc(const QString & s) { m_desc = s; }
void setBirthDate(const QDateTime & dt) { m_birthDate = dt; }
void setPhoto(const QVariant & v) { m_photo = v; }
};
QX_REGISTER_HPP_QX_DLL1(TestQtProperty, QObject, 0)
#endif // _QX_TEST_QT_META_PROPERTY_H_
|
#include "../include/precompiled.h"
#include "../include/TestQtProperty.h"
#include <QxOrm_Impl.h>
QX_REGISTER_CPP_QX_DLL1(TestQtProperty)
QX_REGISTER_ALL_QT_PROPERTIES(TestQtProperty, "id")
|
Pour ceux qui ne souhaitent pas utiliser la macro QX_REGISTER_ALL_QT_PROPERTIES, il est
possible d'�crire � la place les quatre lignes de code suivantes :
namespace qx {
template <> void register_class(QxClass<TestQtProperty> & t)
{ qx::register_all_qt_properties<TestQtProperty>(t, "id"); }
}
|
Remarque : le deuxi�me param�tre de la macro QX_REGISTER_ALL_QT_PROPERTIES permet
d'indiquer la propri�t� qui servira de cl� primaire dans la base de donn�es.
Si ce param�tre est vide, cela signifie que la classe ne poss�de pas de cl� primaire ou bien que
celle-ci est d�finie dans une classe de base.
Toute propri�t� d�finie avec la macro Q_PROPERTY peut s'enregistrer dans le contexte QxOrm de
deux mani�res diff�rentes :
1- par la m�thode classique : t.data(& MyQObject::my_property, "my_property", 0);
2- ou bien sans mentionner le pointeur vers la donn�e membre de la classe :
t.data("my_property", 0);
Peu importe la m�thode d'enregistrement des propri�t�s dans le contexte QxOrm, elles seront
accessibles par la m�me interface qx::IxDataMember et proposent donc les m�mes fonctionnalit�s.
Il est possible d'utiliser les deux m�thodes dans une m�me fonction de mapping void
qx::register_class<T>().
Chaque m�thode d'enregistrement pr�sente des avantages et inconv�nients.
Voici la liste des avantages de la deuxi�me m�thode d'enregistrement des propri�t�s dans le contexte
QxOrm :
- temps de compilation du projet beaucoup plus rapide ;
- taille de l'ex�cutable g�n�r� plus petite ;
- forte int�gration avec le moteur d'introspection du framework Qt ;
- pas besoin de maintenir la fonction de mapping en utilisant la macro
QX_REGISTER_ALL_QT_PROPERTIES.
Voici les inconv�nients par rapport � la m�thode classique d'enregistrement des propri�t�s :
- n�cessite un h�ritage de la classe QObject pour pouvoir utiliser la macro
Q_PROPERTY ;
- ex�cution du programme plus lente (utilisation du type QVariant � la place des
template C++) ;
- ne supporte pas la notion de relation entre tables de la base de donn�es (one-to-one,
one-to-many, many-to-one et many-to-many) ;
- pas d'acc�s au pointeur sur la donn�e membre de la classe (conversion n�cessaire au type
QVariant pour acc�der et modifier une valeur).
|
Comment construire une requ�te pour interroger la base de donn�es sans �crire de SQL
avec la classe qx::QxSqlQuery ?
|
La classe qx::QxSqlQuery (ou bien son alias qx_query) permet d'interroger la
base de donn�es (trier, filtrer, etc.) de deux mani�res diff�rentes :
Le principal avantage de la premi�re m�thode (�criture manuelle des requ�tes SQL) est de pouvoir
utiliser certaines optimisations sp�cifiques � chaque base de donn�es.
La deuxi�me m�thode (utilisation du code C++ pour g�n�rer la requ�te SQL) permet de mapper
automatiquement les param�tres SQL sans utiliser la fonction qx::QxSqlQuery::bind().
Voici un exemple d'utilisation de la classe qx::QxSqlQuery avec �criture manuelle d'une requ�te
SQL :
qx::QxSqlQuery query("WHERE author.sex = :sex");
query.bind(":sex", author::female);
QList<author> list_of_female;
QSqlError daoError = qx::dao::fetch_by_query(query, list_of_female);
for (long l = 0; l < list_of_female.count(); l++)
{ }
|
La biblioth�que QxOrm supporte trois syntaxes pour l'�criture des param�tres SQL.
Le type de syntaxe peut �tre modifi� de fa�on globale � un projet en utilisant la m�thode suivante :
qx::QxSqlDatabase::getSingleton()->setSqlPlaceHolderStyle().
Les trois param�tres possibles pour cette m�thode sont :
- ph_style_2_point_name : "WHERE author.sex = :sex" (syntaxe par d�faut) ;
- ph_style_at_name : "WHERE author.sex = @sex" ;
- ph_style_question_mark : "WHERE author.sex = ?".
Voici le m�me exemple en utilisant les m�thodes C++ de la classe qx::QxSqlQuery (ou bien son
alias qx_query) pour g�n�rer la requ�te automatiquement :
qx_query query;
query.where("author.sex").isEqualTo(author::female);
QList<author> list_of_female;
QSqlError daoError = qx::dao::fetch_by_query(query, list_of_female);
for (long l = 0; l < list_of_female.count(); l++)
{ }
|
Cette utilisation de la classe qx::QxSqlQuery pr�sente l'avantage de ne pas avoir � mapper les
param�tres de la requ�te, tout en restant tr�s proche de l'�criture manuelle d'une requ�te SQL.
Les param�tres seront automatiquement inject�s en utilisant la syntaxe d�finie de mani�re globale par
la m�thode : qx::QxSqlDatabase::getSingleton()->getSqlPlaceHolderStyle().
Voici un exemple pr�sentant diff�rentes m�thodes disponibles avec la classe qx::QxSqlQuery (ou
bien son alias qx_query) :
qx_query query;
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);
|
Ce qui produira le code SQL suivant pour les bases de donn�es MySQL, PostgreSQL et
SQLite (pour Oracle et SQLServer, le traitement de la m�thode limit() est
diff�rent) :
WHERE sex = :sex_1_0
AND age > :age_3_0
OR last_name <> :last_name_5_0
OR first_name LIKE :first_name_7_0
AND ( id <= :id_10_0 AND birth_date BETWEEN :birth_date_12_0_1 AND :birth_date_12_0_2 )
OR id IN (:id_15_0_0, :id_15_0_1, :id_15_0_2, :id_15_0_3, :id_15_0_4)
AND is_deleted IS NOT NULL
ORDER BY last_name ASC, first_name ASC, sex ASC
LIMIT :limit_rows_count_19_0 OFFSET :offset_start_row_19_0
|
Voici la liste des fonctions et m�thodes disponibles pour utiliser la classe qx::QxSqlQuery (ou
bien son alias qx_query) :
qx::dao::count<T>()
qx::dao::fetch_by_query<T>()
qx::dao::update_by_query<T>()
qx::dao::delete_by_query<T>()
qx::dao::destroy_by_query<T>()
qx::dao::fetch_by_query_with_relation<T>()
qx::dao::fetch_by_query_with_all_relation<T>()
qx::dao::update_by_query_with_relation<T>()
qx::dao::update_by_query_with_all_relation<T>()
qx::dao::update_optimized_by_query<T>()qx::QxSession::count<T>()
qx::QxSession::fetchByQuery<T>()
qx::QxSession::update<T>()
qx::QxSession::deleteByQuery<T>()
qx::QxSession::destroyByQuery<T>()qx::QxRepository<T>::count()
qx::QxRepository<T>::fetchByQuery()
qx::QxRepository<T>::update()
qx::QxRepository<T>::deleteByQuery()
qx::QxRepository<T>::destroyByQuery()
|
Remarque : certaines de ces fonctions ont �galement deux autres param�tres optionnels :
- const QStringList & columns : pour indiquer la liste des colonnes � r�cup�rer (par
d�faut, toutes les colonnes sont r�cup�r�es) ;
- const QStringList & relation : pour indiquer les jointures (one-to-one,
one-to-many, many-to-one et many-to-many d�finies dans la fonction de mapping
void qx::register_class<T>()) entre les tables de la base de donn�es (par d�faut,
aucune relation).
|
Comment utiliser le cache (fonctions du namespace qx::cache) pour stocker tous
types de donn�es ?
|
Le cache propos� par la biblioth�que QxOrm (module QxCache) est thread-safe et permet de stocker facilement
n'importe quel type de donn�es.
Les fonctions pour acc�der au cache se trouvent dans le namespace
qx::cache.
Le cache permet une optimisation du programme : il est possible par exemple de stocker des �l�ments
issus d'une requ�te effectu�e en base de donn�es.
Chaque �l�ment stock� dans le cache est associ� � une cl� de type QString : cette cl� permet de
retrouver rapidement un �l�ment du cache.
Si un nouvel �l�ment est stock� dans le cache avec une cl� qui existe d�j�, alors l'ancien �l�ment
associ� � cette cl� est effac� automatiquement du cache.
Le cache de la biblioth�que QxOrm ne g�re pas la dur�e de vie des objets : il n'y a aucun
delete effectu� par le cache.
C'est pourquoi il est fortement recommand� (mais ce n'est pas une obligation) de privil�gier le
stockage de pointeurs intelligents : par exemple, boost::shared_ptr<T> pour la biblioth�que boost ou bien QSharedPointer<T> pour la biblioth�que Qt.
Le cache peut avoir un co�t relatif maximum pour �viter une utilisation de la m�moire trop importante
: chaque �l�ment ins�r� dans le cache peut indiquer un co�t repr�sentant une estimation de sa taille
m�moire (par exemple, le nombre d'�l�ments d'une collection).
Lorsque le co�t maximum du cache est atteint, les premiers �l�ments ins�r�s dans le cache sont
supprim�s (en respectant l'ordre d'insertion dans le cache) jusqu'� ce que la limite du cache ne soit
plus d�pass�e.
Il est possible d'associer � chaque �l�ment du cache une date-heure d'insertion.
Si aucune date-heure n'est renseign�e, alors la date-heure courante est prise en compte.
Ce m�canisme permet de v�rifier si un �l�ment stock� dans le cache n�cessite une mise � jour ou
non.
Voici un exemple d'utilisation du cache de la biblioth�que QxOrm (fonctions du namespace
qx::cache) :
qx::cache::max_cost(500);boost::shared_ptr< QList<author> > list_author;
QSqlError daoError = qx::dao::fetch_all(list_author);qx::cache::set("list_author", list_author);QSharedPointer< std::vector<blog> > list_blog;
daoError = qx::dao::fetch_all(list_blog);qx::cache::set("list_blog", list_blog, list_blog.count());comment_ptr my_comment;
my_comment.reset(new comment(50));
daoError = qx::dao::fetch_by_id(my_comment);qx::cache::set("comment", my_comment, 1, my_comment->dateModif());list_blog = qx::cache::get< QSharedPointer< std::vector<blog> > >("list_blog");qx_bool bGetOk = qx::cache::get("list_blog", list_blog);bool bRemoveOk = qx::cache::remove("list_author");long lCount = qx::cache::count();long lCurrentCost = qx::cache::current_cost();bool bExist = qx::cache::exist("comment");QDateTime dt;
bGetOk = qx::cache::get("comment", my_comment, dt);qx::cache::clear();
|
|
Comment g�n�rer le sch�ma SQL (cr�ation et mise � jour des tables) en fonction des
classes persistantes C++ d�finies dans le contexte QxOrm ?
|
Il est recommand� d'utiliser l'application QxEntityEditor pour g�rer cette
probl�matique.
La biblioth�que QxOrm ne fournit pas de m�canisme pour g�rer automatiquement la cr�ation et mise �
jour des tables dans la base de donn�es.
En effet, la fonction qx::dao::create_table<T> doit �tre utilis�e uniquement pour cr�er
des prototypes.
Il est fortement recommand� d'utiliser un outil sp�cifique � chaque SGBD pour cr�er et maintenir les
tables de la base de donn�es (par exemple Navicat pour MySql, pgAdmin pour
PostgreSQL, SQLite Manager pour SQLite, etc.).
De plus, un outil sp�cifique � chaque SGBD permet d'appliquer certaines optimisations (ajout d'index
par exemple).
Cependant, il peut �tre int�ressant pour certaines applications de ne pas avoir � g�rer manuellement
les tables de la base de donn�es.
Dans ce cas, il est possible de cr�er une fonction C++ pour parcourir la liste des classes
persistantes enregistr�es dans le contexte QxOrm (en utilisant le moteur d'introspection de la
biblioth�que) et ainsi cr�er un script SQL de g�n�ration et mise � jour des tables de la base de
donn�es.
La biblioth�que QxOrm fournit une fonction C++ cr��e uniquement � titre d'exemple : elle peut donc
servir de base de travail pour cr�er sa propre fonction de g�n�ration de script SQL.
Cette fonction se trouve dans le fichier ./src/QxRegister/QxClassX.cpp et se nomme QString
qx::QxClassX::dumpSqlSchema().
Elle g�n�re un script SQL et le renvoie sous forme de QString : il est possible d'adapter cette
fonction pour g�n�rer un fichier contenant le script SQL ou bien appliquer chaque instruction SQL
directement au SGBD.
Voici un exemple d'impl�mentation propos� par dodobibi pour g�rer
une base PostgreSQL : cet exemple g�re les �volutions futures de son application (ajout de
colonnes dans une table existante, ajout d'index sur une colonne existante, etc.).
Au lancement de l'application, le num�ro de version est indiqu� de la fa�on suivante :
QApplication app(argc, argv);
app.setProperty("DomainVersion", 1);
|
Une table de la base de donn�es permet de stocker le num�ro de version courant.
Une classe persistante C++ est mapp�e sur cette table de la fa�on suivante :
#ifndef _DATABASE_VERSION_H_
#define _DATABASE_VERSION_H_
class MY_DLL_EXPORT DatabaseVersion
{
public:
QString name;
long version;
};
QX_REGISTER_HPP_MY_APP(DatabaseVersion, qx::trait::no_base_class_defined, 0)
#endif // _DATABASE_VERSION_H_
|
#include "../include/precompiled.h"
#include "../include/DatabaseVersion.h"
#include <QxOrm_Impl.h>
QX_REGISTER_CPP_MY_APP(DatabaseVersion)
namespace qx {
template <> void register_class(QxClass<DatabaseVersion> & t)
{
t.id(& DatabaseVersion::name, "name");
t.data(& DatabaseVersion::version, "version");
}}
|
Avec la classe DatabaseVersion, il est possible de v�rifier si la version de la base de donn�es
est � jour.
C'est le r�le de la fonction isDatabaseVersionOld() :
bool isDatabaseVersionOld()
{
DatabaseVersion dbVersion;
dbVersion.name = "MyAppName";
QSqlError err = qx::dao::fetch_by_id(dbVersion);
if (err.isValid()) { qAssert(false); return false; }
return (dbVersion.version < qApp->property("DomainVersion").toInt());
}
|
Si au lancement de l'application, la fonction isDatabaseVersionOld() renvoie true, alors
la mise � jour de la base de donn�es est effectu�e de la fa�on suivante :
void updateDatabaseVersion()
{
try
{
int domainVersion = qApp->property("DomainVersion").toInt(); QSqlDatabase db = qx::QxSqlDatabase::getSingleton()->getDatabaseCloned();
db.setUserName("MyAdminLogin");
db.setPassword("MyAdminPassword"); qx::QxSession session(db, true, true); DatabaseVersion dbVersion;
session.fetchByQuery(qx_query("WHERE name='MyAppName' FOR UPDATE"), dbVersion); if (dbVersion.version >= domainVersion) { return; } QSqlQuery query(db); qx::QxCollection<QString, qx::IxClass *> * pAllClasses = qx::QxClassX::getAllClasses();
if (! pAllClasses) { qAssert(false); return; } QStringList tables = db.tables();
for (long k = 0; k < pAllClasses->count(); k++)
{
qx::IxClass * pClass = pAllClasses->getByIndex(k);
if (! pClass) { continue; } if (pClass->isKindOf("qx::service::IxParameter") || pClass->isKindOf("qx::service::IxService")) { continue; } if (pClass->getVersion() <= dbVersion.version) { continue; } if (! tables.contains(pClass->getName()))
{
query.exec("CREATE TABLE " + pClass->getName() + " ( ) WITH (OIDS = FALSE);"
"ALTER TABLE " + pClass->getName() + " OWNER TO \"MyAdminLogin\";");
session += query.lastError();
} qx::IxDataMemberX * pDataMemberX = pClass->getDataMemberX();
for (long l = 0; (pDataMemberX && (l < pDataMemberX->count_WithDaoStrategy())); l++)
{
qx::IxDataMember * p = pDataMemberX->get_WithDaoStrategy(l);
if (! p || (p->getVersion() <= dbVersion.version)) { continue; }
query.exec("ALTER TABLE " + pClass->getName() + " ADD COLUMN " + p->getName() + " " + p->getSqlType() + ";");
session += query.lastError();
if (p->getIsPrimaryKey()) {
query.exec("ALTER TABLE " + pClass->getName() + " ADD PRIMARY KEY (" + p->getName() + ");");
session += query.lastError();
}
if (p->getAllPropertyBagKeys().contains("INDEX")) {
query.exec("CREATE INDEX " + pClass->getName() + "_" + p->getName() + "_idx" +
" ON " + pClass->getName() + " USING " + p->getPropertyBag("INDEX").toString() + " (" + p->getName() + ");");
session += query.lastError();
}
if (p->getNotNull()) {
query.exec("ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " SET NOT NULL;");
session += query.lastError();
}
if (p->getAutoIncrement()) {
query.exec("CREATE SEQUENCE " + pClass->getName() + "_" + p->getName() + "_seq" + "; "
"ALTER TABLE " + pClass->getName() + "_" + p->getName() + "_seq" + " OWNER TO \"MyAdminLogin\"; "
"ALTER TABLE " + pClass->getName() + " ALTER COLUMN " + p->getName() + " " +
"SET DEFAULT nextval('" + pClass->getName() + "_" + p->getName() + "_seq" + "'::regclass);");
session += query.lastError();
}
if (p->getDescription() != "") { query.exec("COMMENT ON COLUMN " + pClass->getName() + "." + p->getName() + " IS $$" + p->getDescription() + "$$ ;");
session += query.lastError();
}
}
} dbVersion.version = domainVersion;
session.save(dbVersion); }
catch (const qx::dao::sql_error & err)
{
QSqlError sqlError = err.get();
qDebug() << sqlError.databaseText();
qDebug() << sqlError.driverText();
qDebug() << sqlError.number();
qDebug() << sqlError.type();
}
}
|
Remarque : le code pr�c�dent (tout comme la fonction qx::QxClassX::dumpSqlSchema()) peut �tre modifi� pour s'adapter aux
besoins sp�cifiques d'une application.
Par exemple, il pourrait �tre int�ressant de cr�er par d�faut une seconde table (en plus de la table
DatabaseVersion) pour enregistrer la liste des classes persistantes enregistr�es dans le
contexte QxOrm : ainsi, au lieu d'utiliser la fonction propos�e par Qt "db.tables()", il serait
possible de r�cup�rer toutes les tables mapp�es sur des classes persistantes avec des informations
suppl�mentaires (num�ro de version pour chaque table, nombre de colonnes enregistr�es dans le contexte
QxOrm, description de chaque table, etc.).
|
Comment associer un type SQL � une classe C++ ?
|
Chaque base de donn�es propose des types SQL diff�rents pour stocker l'information.
La biblioth�que QxOrm propose une association par d�faut pour les classes C++ les plus fr�quemment
utilis�es dans un programme.
Voici cette association par d�faut :
"bool" <-> "SMALLINT"
"qx_bool" <-> "SMALLINT"
"short" <-> "SMALLINT"
"int" <-> "INTEGER"
"long" <-> "INTEGER"
"long long" <-> "INTEGER"
"float" <-> "FLOAT"
"double" <-> "FLOAT"
"long double" <-> "FLOAT"
"unsigned short" <-> "SMALLINT"
"unsigned int" <-> "INTEGER"
"unsigned long" <-> "INTEGER"
"unsigned long long" <-> "INTEGER"
"std::string" <-> "TEXT"
"std::wstring" <-> "TEXT"
"QString" <-> "TEXT"
"QVariant" <-> "TEXT"
"QUuid" <-> "TEXT"
"QDate" <-> "DATE"
"QTime" <-> "TIME"
"QDateTime" <-> "TIMESTAMP"
"QByteArray" <-> "BLOB"
"qx::QxDateNeutral" <-> "TEXT"
"qx::QxTimeNeutral" <-> "TEXT"
"qx::QxDateTimeNeutral" <-> "TEXT"
|
Si le type SQL propos� par d�faut par la biblioth�que QxOrm ne correspond pas � la base de donn�es
utilis�e, il peut facilement �tre modifi� (de mani�re globale � toute l'application) en utilisant la
collection suivante :
QHash<QString, QString> * lstSqlType = qx::QxClassX::getAllSqlTypeByClassName();
lstSqlType->insert("QString", "VARCHAR(255)");
lstSqlType->insert("std::string", "VARCHAR(255)");
|
Pour modifier le type SQL de mani�re sp�cifique pour une colonne d'une table de la base de donn�es, il
faut d�finir le type SQL dans la fonction de mapping de QxOrm :
namespace qx {
template <> void register_class(QxClass<MyClass> & t)
{ IxDataMember * p = t.data(& MyClass::m_MyProperty, "my_property");
p->setSqlType("VARCHAR(255)");}}
|
Pour les classes non support�es par d�faut par la biblioth�que QxOrm (voir cette
Question-R�ponse de la FAQ : Comment persister un type dont on
ne poss�de pas le code source (classe provenant d'une biblioth�que tierce par exemple) ?),
il est possible d'associer un type SQL par d�faut en utilisant la macro suivante (en dehors de tout
namespace) :
QX_REGISTER_TRAIT_GET_SQL_TYPE(MyClass, "my_sql_type")
|
|
Comment utiliser le module QxValidator pour valider automatiquement les donn�es
?
|
Le module QxValidator
de la biblioth�que QxOrm permet d'ajouter des contraintes sur les propri�t�s enregistr�es dans
le contexte QxOrm.
Ces contraintes sont d�finies dans la m�thode de mapping : void
qx::register_class<T>.
Si pour une instance de classe donn�e, au moins une contrainte n'est pas respect�e, alors l'instance
est consid�r�e comme invalide : l'objet ne peut alors pas �tre sauvegard� en base de donn�es
(INSERT ou UPDATE).
Il est �galement possible d'utiliser le module QxValidator pour valider les donn�es au niveau
de la couche pr�sentation de l'application : si les donn�es saisies par un utilisateur ne sont pas
valides, un message d'erreur peut �tre signal�, il n'est alors pas n�cessaire d'essayer d'enregistrer
l'instance courante en base de donn�es.
Les r�gles de validation n'ont pas besoin d'�tre dupliqu�es : elles peuvent �tre utilis�es aussi bien
par la couche pr�sentation que par la couche d'acc�s aux donn�es de l'application.
Voici la description de quelques classes du module QxValidator :
Le module QxValidator g�re automatiquement la notion d'h�ritage de classe : si des contraintes
sont d�finies au niveau de la classe de base, alors elles seront automatiquement v�rifi�es pour chaque
validation d'une classe d�riv�e.
Voici un exemple d'utilisation du module QxValidator avec une classe 'person' :
* fichier 'person.h' :
#ifndef _CLASS_PERSON_H_
#define _CLASS_PERSON_H_
class person
{
public:
enum sex { male, female, unknown };
long _id;
QString _firstName;
QString _lastName;
QDateTime _birthDate;
sex _sex;
person() : _id(0), _sex(unknown) { ; }
person(long id) : _id(id), _sex(unknown) { ; }
virtual ~person() { ; }
private:
void isValid(qx::QxInvalidValueX & invalidValues);
};
QX_REGISTER_HPP_MY_EXE(person, qx::trait::no_base_class_defined, 0)
#endif // _CLASS_PERSON_H_
|
* fichier 'person.cpp' :
#include "../include/precompiled.h"
#include "../include/person.h"
#include "../include/global_validator.h"
#include <QxOrm_Impl.h>
QX_REGISTER_CPP_MY_EXE(person)
namespace qx {
template <> void register_class(QxClass<person> & t)
{
t.id(& person::_id, "id");
t.data(& person::_firstName, "firstName");
t.data(& person::_lastName, "lastName");
t.data(& person::_birthDate, "birthDate");
t.data(& person::_sex, "sex");
QxValidatorX<person> * pAllValidator = t.getAllValidator();
pAllValidator->add_NotEmpty("firstName");
pAllValidator->add_NotEmpty("lastName", "a person must have a lastname");
pAllValidator->add_CustomValidator(& person::isValid);
pAllValidator->add_CustomValidator_QVariant(& validateFirstName, "firstName");
pAllValidator->add_CustomValidator_DataType<QDateTime>(& validateDateTime, "birthDate");
}}
void person::isValid(qx::QxInvalidValueX & invalidValues)
{ if ((_sex != male) && (_sex != female))
{ invalidValues.insert("le sexe de la personne doit �tre d�fini : masculin ou f�minin"); }
}
|
* fichier 'global_validator.h' :
void validateFirstName(const QVariant & value, const qx::IxValidator * validator, qx::QxInvalidValueX & invalidValues)
{ if (value.toString() == "admin")
{ invalidValues.insert("la valeur ne peut pas �tre �gale � 'admin'"); }
}
void validateDateTime(const QDateTime & value, const qx::IxValidator * validator, qx::QxInvalidValueX & invalidValues)
{ if (! value.isValid())
{ invalidValues.insert("la date-heure doit �tre renseign�e et doit �tre valide"); }
}
|
* fichier 'main.cpp' :
person personValidate;
personValidate._lastName = "admin";
qx::QxInvalidValueX invalidValues = qx::validate(personValidate);
QString sInvalidValues = invalidValues.text();
qDebug("[QxOrm] test 'QxValidator' module :\n%s", qPrintable(sInvalidValues));
|
A l'ex�cution de ce bout de code, l'instance 'personValidate' est non valide : la collection
'invalidValues' contient quatre �l�ments :
- "la valeur de la propri�t� 'firstName' ne peut pas �tre vide" ;
- "le sexe de la personne doit �tre d�fini : masculin ou f�minin" ;
- "la valeur ne peut pas �tre �gale � 'admin'" ;
- "la date-heure doit �tre renseign�e et doit �tre valide".
Le module QxValidator fournit plusieurs validateurs pour effectuer des v�rifications basiques :
- add_NotNull() : v�rifie que la valeur n'est pas nulle ;
- add_NotEmpty() : v�rifie que la cha�ne de caract�res n'est pas vide ;
- add_MinValue() : v�rifie que la valeur num�rique n'est pas inf�rieure au param�tre ;
- add_MaxValue() : v�rifie que la valeur num�rique n'est pas sup�rieure au param�tre ;
- add_Range() : v�rifie que la valeur num�rique est comprise entre les deux param�tres ;
- add_MinDecimal() : v�rifie que la valeur d�cimale n'est pas inf�rieure au param�tre ;
- add_MaxDecimal() : v�rifie que la valeur d�cimale n'est pas sup�rieure au param�tre ;
- add_RangeDecimal() : v�rifie que la valeur d�cimale est comprise entre les deux
param�tres ;
- add_MinLength() : v�rifie que la cha�ne de caract�res a une taille minimale ;
- add_MaxLength() : v�rifie que la cha�ne de caract�res ne d�passe pas un certain nombre de
caract�res ;
- add_Size() : v�rifie que la taille de la cha�ne de caract�res est comprise entre les deux
param�tres ;
- add_DatePast() : v�rifie que la date-heure est dans le pass� ;
- add_DateFuture() : v�rifie que la date-heure est dans le futur ;
- add_RegExp() : v�rifie que la cha�ne de caract�res est compatible avec l'expression
r�guli�re pass�e en param�tre ;
- add_EMail() : v�rifie que la cha�ne de caract�res correspond � un e-mail.
Comme dans l'exemple de la classe 'person', il est possible de d�finir �galement des
validateurs personnalis�s : ce sont des fonctions ou m�thodes de classe qui seront appel�es
automatiquement par le module QxValidator pour valider une propri�t� ou une instance de
classe.
Il existe trois types de validateurs personnalis�s :
- add_CustomValidator() : m�thode de classe, la signature de la m�thode doit �tre "void
my_class::my_method(qx::QxInvalidValueX &)" ;
- add_CustomValidator_QVariant() : fonction globale avec type QVariant (propri�t�
convertie en QVariant), la signature de la fonction doit �tre "void my_validator(const
QVariant &, const qx::IxValidator *, qx::QxInvalidValueX &)" ;
- add_CustomValidator_DataType() : fonction globale avec le type r�el de la propri�t�, la
signature de la fonction doit �tre "void my_validator(const T &, const qx::IxValidator *,
qx::QxInvalidValueX &)" ;
Remarque : � chaque validateur peut �tre associ� un groupe (param�tre optionnel pour chaque
m�thode add_XXX() de la classe qx::IxValidatorX).
Il est ainsi possible de cr�er des groupes de validation suivant le contexte d'ex�cution : par
exemple, valider la saisie d'une personne sur une IHM A ne n�cessite peut-�tre pas les m�mes
v�rifications que valider une personne sur une IHM B.
Pour ex�cuter la validation d'une instance pour un groupe donn� (par exemple "myGroup"), il
faut appeler la fonction suivante : "qx::QxInvalidValueX invalidValues =
qx::validate(personValidate, "myGroup");".
Autre remarque : le module QxValidator d�finit des messages par d�faut lorsqu'une
contrainte n'est pas v�rifi�e.
Il est possible de red�finir ces messages par d�faut en modifiant la collection suivante : "QHash
* lstMessage = QxClassX::getAllValidatorMessage();
".
Par exemple : "lstMessage->insert("min_value", "la valeur '%NAME%' doit �tre inf�rieure ou �gale �
'%CONSTRAINT%'");".
Les champs %NAME% et %CONSTRAINT% seront automatiquement remplac�s par les valeurs
correspondantes.
Pour modifier le message pour un validateur donn� (et non de mani�re globale), il faut utiliser le
param�tre optionnel disponible pour les m�thodes add_XXX() de la classe
qx::IxValidatorX.
|
Comment utiliser l'interface qx::IxPersistable ?
|
L'interface qx::IxPersistable (ou classe abstraite) dispose uniquement de m�thodes
virtuelles pures.
Elle permet d'avoir une classe de base commune pour appeler les fonctions de persistance sans
conna�tre le type r�el de l'instance courante (notion de polymorphisme).
La biblioth�que QxOrm n'impose pas de travailler avec une classe de base pour enregistrer un type
persistant dans le contexte QxOrm, cependant il est parfois utile de disposer d'une interface afin
d'�crire des algorithmes g�n�riques.
La classe qx::IxPersistable met � disposition les m�thodes virtuelles suivantes (pour plus
d'informations sur ces m�thodes, rendez-vous sur la documentation en ligne de la biblioth�que QxOrm) :
virtual long qxCount(const qx::QxSqlQuery & query = qx::QxSqlQuery(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchById(const QVariant & id = QVariant(), const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchAll(qx::IxCollection & list, const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxFetchByQuery(const qx::QxSqlQuery & query, qx::IxCollection & list, const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxInsert(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxUpdate(const qx::QxSqlQuery & query = qx::QxSqlQuery(), const QStringList & columns = QStringList(), const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxSave(const QStringList & relation = QStringList(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteById(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteAll(QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDeleteByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyById(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyAll(QSqlDatabase * pDatabase = NULL);
virtual QSqlError qxDestroyByQuery(const qx::QxSqlQuery & query, QSqlDatabase * pDatabase = NULL);
virtual qx_bool qxExist(const QVariant & id = QVariant(), QSqlDatabase * pDatabase = NULL);
virtual qx::QxInvalidValueX qxValidate(const QStringList & groups = QStringList());
virtual qx::IxPersistableCollection_ptr qxNewPersistableCollection() const;
virtual qx::IxClass * qxClass() const;
Par exemple, � partir d'une liste de pointeurs de type qx::IxPersistable, il est possible
d'enregistrer les �l�ments dans plusieurs tables diff�rentes de la base de donn�es de la fa�on
suivante :
QList<qx::IxPersistable *> lst = ...;
foreach(qx::IxPersistable * p, lst)
{
QSqlError daoError = p->qxSave();
if (daoError.isValid()) { }}
|
Pour impl�menter l'interface qx::IxPersistable, il faut :
- faire h�riter la classe persistante du type qx::IxPersistable ;
- dans la d�finition de la classe (myClass.h par exemple), ajouter la macro
QX_PERSISTABLE_HPP(myClass) ;
- dans l'impl�mentation de la classe (myClass.cpp par exemple), ajouter la macro
QX_PERSISTABLE_CPP(myClass).
Par exemple, impl�menter l'interface qx::IxPersistable pour la classe author du tutoriel qxBlog revient � �crire (les
modifications par rapport au code du tutoriel apparaissent en gras) :
#ifndef _QX_BLOG_AUTHOR_H_
#define _QX_BLOG_AUTHOR_H_
class blog;
class QX_BLOG_DLL_EXPORT author : public qx::IxPersistable
{
QX_PERSISTABLE_HPP(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 <QxOrm_Impl.h>
QX_REGISTER_CPP_QX_BLOG(author)
QX_PERSISTABLE_CPP(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());
}
|
Remarque : le projet de test ./test/qxDllSample/dll1/ met � disposition une sorte de
'super classe de base' : la classe qx::QxPersistable impl�mente l'interface qx::IxPersistable
et h�rite de QObject.
Le m�canisme SIGNAL-SLOT de Qt peut donc �tre utilis� avec cette classe, ce qui peut �tre
int�ressant par exemple pour la notion de d�clencheurs (ou trigger).
La classe qx::QxPersistable met �galement � disposition des m�thodes virtuelles qu'il est
possible de surcharger pour g�rer notamment la notion de validation des donn�es avec le module QxValidator.
La classe qx::QxPersistable ne fait pas partie de la distribution de QxOrm, mais il est
possible de la copier-coller dans un projet afin de profiter de ses fonctionnalit�s :
|
Comment utiliser le moteur de relations pour r�cup�rer des donn�es associ�es �
plusieurs tables ?
|
La biblioth�que QxOrm supporte quatre types de relations pour lier les classes C++ enregistr�es dans
le contexte QxOrm : one-to-one, one-to-many, many-to-one et
many-to-many.
Pour plus de d�tails sur la d�finition de ces relations, il est conseill� de lire le tutoriel qxBlog.
Nous allons d�tailler dans cette Q&R les diff�rentes m�thodes de r�cup�ration des donn�es (module QxDao, fonctions du namespace qx::dao) :
Le premier param�tre des fonctions fetch_by_id_with_relation, fetch_all_with_relation et fetch_by_query_with_relation correspond � la liste des relations � requ�ter.
Cette liste de relations peut contenir les �l�ments suivants :
- identifiant d'une relation : chaque relation poss�de une cl� d�finie au niveau de la fonction de
param�trage qx::register_class<T> ;
- le mot-cl� "*" signifie "r�cup�rer toutes les relations d�finies dans la fonction de
param�trage qx::register_class<T> sur un niveau" ;
- le mot-cl� "->" signifie jointure de type "LEFT OUTER JOIN" (jointure par d�faut
de la biblioth�que QxOrm) ;
- le mot-cl� ">>" signifie jointure de type "INNER JOIN" entre deux tables.
Remarque : en utilisant le mot-cl� "*" pour indiquer "toutes les relations sur un
niveau", les appels suivants sont �quivalents :
- qx::dao::fetch_by_id_with_relation("*", ...) ==
qx::dao::fetch_by_id_with_all_relation(...) ;
- qx::dao::fetch_by_query_with_relation("*", ...) ==
qx::dao::fetch_by_query_with_all_relation(...) ;
- qx::dao::fetch_all_with_relation("*", ...) ==
qx::dao::fetch_all_with_all_relation(...).
Exemple : � partir du tutoriel qxBlog, il est possible de r�cup�rer les donn�es suivantes avec
une seule requ�te :
1- r�cup�rer un blog et son author ;
2- pour l'author valoris�, r�cup�rer tous les blog qu'il a �crit ;
3- pour chaque blog que l'author a �crit, r�cup�rer tous les comment
associ�s.
blog_ptr my_blog = blog_ptr(new blog(10));
QSqlError daoError = qx::dao::fetch_by_id_with_relation("author_id->list_blog->list_comment", my_blog);
|
Ce qui g�n�re la requ�te SQL suivante :
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,
blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0,
comment_4.comment_id AS comment_4_comment_id_0, comment_4.blog_id AS comment_4_blog_id_0, comment_4.comment_text AS comment_4_comment_text_0, comment_4.date_creation AS comment_4_date_creation_0
FROM blog
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id
LEFT OUTER JOIN comment comment_4 ON comment_4.blog_id = blog_2.blog_id
WHERE blog.blog_id = :blog_id
Autre exemple : il est �galement possible de cr�er une liste de relations � r�cup�rer, comme
ceci par exemple :
blog_ptr my_blog = blog_ptr(new blog(10));
QStringList relation;
relation << "author_id->list_blog->list_comment";
relation << "author_id->list_blog->list_category";
relation << "list_comment";
relation << "list_category";
QSqlError daoError = qx::dao::fetch_by_id_with_relation(relation, my_blog);
|
Ce qui g�n�re la requ�te SQL suivante :
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,
blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0,
category_5.category_id AS category_5_category_id_0, category_5.name AS category_5_name_0, category_5.description AS category_5_description_0,
comment_6.comment_id AS comment_6_comment_id_0, comment_6.blog_id AS comment_6_blog_id_0, comment_6.comment_text AS comment_6_comment_text_0, comment_6.date_creation AS comment_6_date_creation_0,
category_7.category_id AS category_7_category_id_0, category_7.name AS category_7_name_0, category_7.description AS category_7_description_0
FROM blog
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id
LEFT OUTER JOIN category_blog category_blog_5 ON blog_2.blog_id = category_blog_5.blog_id
LEFT OUTER JOIN category category_5 ON category_blog_5.category_id = category_5.category_id
LEFT OUTER JOIN comment comment_6 ON comment_6.blog_id = blog.blog_id
LEFT OUTER JOIN category_blog category_blog_7 ON blog.blog_id = category_blog_7.blog_id
LEFT OUTER JOIN category category_7 ON category_blog_7.category_id = category_7.category_id
WHERE blog.blog_id = :blog_id
Autre exemple : pour r�cup�rer toutes les relations pour un niveau donn�, il faut utiliser le
mot-cl� "*".
Pour r�cup�rer toutes les donn�es de toutes les relations sur trois niveaux, il faut �crire :
blog_ptr my_blog = blog_ptr(new blog(10));
QSqlError daoError = qx::dao::fetch_by_id_with_relation("*->*->*", my_blog);
|
Ce qui g�n�re la requ�te SQL suivante :
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,
blog_2.blog_id AS blog_2_blog_id_0, blog_2.author_id AS blog_2_author_id_0, blog_2.blog_text AS blog_2_blog_text_0, blog_2.date_creation AS blog_2_date_creation_0, blog_2.author_id AS blog_2_author_id_0_2,
author_3.author_id AS author_3_author_id_0, author_3.name AS author_3_name_0, author_3.birthdate AS author_3_birthdate_0, author_3.sex AS author_3_sex_0,
comment_4.comment_id AS comment_4_comment_id_0, comment_4.blog_id AS comment_4_blog_id_0, comment_4.comment_text AS comment_4_comment_text_0, comment_4.date_creation AS comment_4_date_creation_0,
category_5.category_id AS category_5_category_id_0, category_5.name AS category_5_name_0, category_5.description AS category_5_description_0,
comment_6.comment_id AS comment_6_comment_id_0, comment_6.blog_id AS comment_6_blog_id_0, comment_6.comment_text AS comment_6_comment_text_0, comment_6.date_creation AS comment_6_date_creation_0, comment_6.blog_id AS comment_6_blog_id_0_6,
blog_7.blog_id AS blog_7_blog_id_0, blog_7.blog_text AS blog_7_blog_text_0, blog_7.date_creation AS blog_7_date_creation_0, blog_7.author_id AS blog_7_author_id_0_7,
author_8.author_id AS author_8_author_id_0, author_8.name AS author_8_name_0, author_8.birthdate AS author_8_birthdate_0, author_8.sex AS author_8_sex_0,
comment_9.comment_id AS comment_9_comment_id_0, comment_9.blog_id AS comment_9_blog_id_0, comment_9.comment_text AS comment_9_comment_text_0, comment_9.date_creation AS comment_9_date_creation_0,
category_10.category_id AS category_10_category_id_0, category_10.name AS category_10_name_0, category_10.description AS category_10_description_0,
category_11.category_id AS category_11_category_id_0, category_11.name AS category_11_name_0, category_11.description AS category_11_description_0,
blog_12.blog_id AS blog_12_blog_id_0, blog_12.blog_text AS blog_12_blog_text_0, blog_12.date_creation AS blog_12_date_creation_0, blog_12.author_id AS blog_12_author_id_0_12,
author_13.author_id AS author_13_author_id_0, author_13.name AS author_13_name_0, author_13.birthdate AS author_13_birthdate_0, author_13.sex AS author_13_sex_0,
comment_14.comment_id AS comment_14_comment_id_0, comment_14.blog_id AS comment_14_blog_id_0, comment_14.comment_text AS comment_14_comment_text_0, comment_14.date_creation AS comment_14_date_creation_0,
category_15.category_id AS category_15_category_id_0, category_15.name AS category_15_name_0, category_15.description AS category_15_description_0
FROM blog
LEFT OUTER JOIN author author_1 ON author_1.author_id = blog.author_id
LEFT OUTER JOIN blog blog_2 ON blog_2.author_id = author_1.author_id
LEFT OUTER JOIN author author_3 ON author_3.author_id = blog_2.author_id
LEFT OUTER JOIN comment comment_4 ON comment_4.blog_id = blog_2.blog_id
LEFT OUTER JOIN category_blog category_blog_5 ON blog_2.blog_id = category_blog_5.blog_id
LEFT OUTER JOIN category category_5 ON category_blog_5.category_id = category_5.category_id
LEFT OUTER JOIN comment comment_6 ON comment_6.blog_id = blog.blog_id
LEFT OUTER JOIN blog blog_7 ON blog_7.blog_id = comment_6.blog_id
LEFT OUTER JOIN author author_8 ON author_8.author_id = blog_7.author_id
LEFT OUTER JOIN comment comment_9 ON comment_9.blog_id = blog_7.blog_id
LEFT OUTER JOIN category_blog category_blog_10 ON blog_7.blog_id = category_blog_10.blog_id
LEFT OUTER JOIN category category_10 ON category_blog_10.category_id = category_10.category_id
LEFT OUTER JOIN category_blog category_blog_11 ON blog.blog_id = category_blog_11.blog_id
LEFT OUTER JOIN category category_11 ON category_blog_11.category_id = category_11.category_id
LEFT OUTER JOIN category_blog category_blog_12 ON category_11.category_id = category_blog_12.category_id
LEFT OUTER JOIN blog blog_12 ON category_blog_12.blog_id = blog_12.blog_id
LEFT OUTER JOIN author author_13 ON author_13.author_id = blog_12.author_id
LEFT OUTER JOIN comment comment_14 ON comment_14.blog_id = blog_12.blog_id
LEFT OUTER JOIN category_blog category_blog_15 ON blog_12.blog_id = category_blog_15.blog_id
LEFT OUTER JOIN category category_15 ON category_blog_15.category_id = category_15.category_id
WHERE blog.blog_id = :blog_id
|
Comment appeler une proc�dure stock�e ou une requ�te SQL personnalis�e
?
|
La biblioth�que QxOrm fournit deux fonctions pour appeler une proc�dure stock�e ou une requ�te SQL
personnalis�e :
Le premier param�tre de ces deux fonctions, de type qx::QxSqlQuery (ou
qx_query), correspond � la proc�dure stock�e ou � la requ�te SQL personnalis�e.
Pour plus d'informations sur la classe qx::QxSqlQuery, rendez-vous sur cette Q&R de la FAQ de QxOrm : Comment construire une requ�te pour interroger la base de donn�es sans
�crire de SQL avec la classe qx::QxSqlQuery ?
La fonction qx::dao::execute_query<T>() est une fonction template : le type T
doit �tre enregistr� dans le contexte QxOrm (fonction qx::register_class<T>).
Toutes les donn�es renvoy�es par la proc�dure stock�e ou la requ�te SQL personnalis�e qui pourront
�tre associ�es aux membres des classes C++ (de type T) seront valoris�es automatiquement.
Une recherche automatique est effectu�e sur le nom des champs associ�s aux donn�es.
Voici un exemple d'utilisation (disponible dans le projet qxBlog du package QxOrm) :
qx_query testStoredProcBis("SELECT * FROM author");
daoError = qx::dao::execute_query(testStoredProcBis, authorX);
qAssert(! daoError.isValid()); qAssert(authorX.count() > 0);
qx::dump(authorX);
|
La fonction qx::dao::call_query() n'est pas une fonction template : les r�sultats de
la requ�te doivent �tre parcourus manuellement sur la classe qx::QxSqlQuery (ou
qx_query).
Pour r�cup�rer un param�tre de sortie (qui doit �tre pass� � la requ�te en tant que QSql::Out
ou QSql::InOut), il suffit d'utiliser la m�thode : QVariant qx::QxSqlQuery::boundValue(const
QString & sKey) const;.
Pour parcourir la liste des r�sultats de la requ�te, il faut utiliser les m�thodes suivantes :
* long qx::QxSqlQuery::getSqlResultRowCount() const;
* long qx::QxSqlQuery::getSqlResultColumnCount() const;
* QVariant qx::QxSqlQuery::getSqlResultAt(long row, long column) const;
* QVariant qx::QxSqlQuery::getSqlResultAt(long row, const QString & column) const;
* QVector qx::QxSqlQuery::getSqlResultAllColumns() const;
* void qx::QxSqlQuery::dumpSqlResult();
Voici un exemple d'utilisation avec la fonction qx::dao::call_query() :
qx_query query("CALL MyStoredProc(:param1, :param2)");
query.bind(":param1", "myValue1");
query.bind(":param2", 5024, QSql::InOut);
QSqlError daoError = qx::dao::call_query(query);
QVariant vNewValue = query.boundValue(":param2");
query.dumpSqlResult();
|
|
Comment utiliser la classe qx::QxDaoAsync pour appeler des requ�tes de mani�re
asynchrone (multi-thread) ?
|
Il peut �tre parfois int�ressant d'ex�cuter certaines requ�tes � la base de donn�es de mani�re
asynchrone (multi-thread), par exemple pour �viter de bloquer une IHM si une requ�te est trop longue �
s'ex�cuter.
Pour simplifier les requ�tes asynchrones, la biblioth�que QxOrm fournit la classe qx::QxDaoAsync.
Cette classe ex�cute une requ�te dans un thread d�di� et renvoie un SIGNAL
queryFinished() lorsque la requ�te est termin�e.
Pour utiliser la classe qx::QxDaoAsync, il suffit de :
- v�rifier que la requ�te fait appel � une classe qui impl�mente l'interface qx::IxPersistable ;
- cr�er une instance de type qx::QxDaoAsync (par exemple, une propri�t� membre d'une classe
d�rivant du type QWidget) ;
- connecter un SLOT au SIGNAL qx::QxDaoAsync::queryFinished() (par exemple,
un SLOT d�fini dans une classe d�rivant du type QWidget) ;
- ex�cuter une requ�te � la base de donn�es en utilisant l'une des m�thodes commen�ant par
async : qx::QxDaoAsync::asyncXXXX().
Voici un exemple d'utilisation avec une classe nomm�e MyWidget :
class MyWidget : public QWidget
{ Q_OBJECT qx::QxDaoAsync m_daoAsync;Q_SLOTS:
void onQueryFinished(const QSqlError & daoError, qx::dao::detail::QxDaoAsyncParams_ptr pDaoParams);};
|
Et voici l'impl�mentation de la classe MyWidget :
MyWidget::MyWidget() : QObject()
{ QObject::connect((& m_daoAsync), SIGNAL(queryFinished(const QSqlError &, qx::dao::detail::QxDaoAsyncParams_ptr)),
this, SLOT(onQueryFinished(const QSqlError &, qx::dao::detail::QxDaoAsyncParams_ptr)));}
void MyWidget::onQueryFinished(const QSqlError & daoError, qx::dao::detail::QxDaoAsyncParams_ptr pDaoParams)
{
if (! pDaoParams) { return; }
qx::QxSqlQuery query = pDaoParams->query;
if (! daoError.isValid()) { ; } qx::IxPersistable_ptr ptr = pDaoParams->pInstance; qx::IxPersistableCollection_ptr lst = pDaoParams->pListOfInstances;}
|
|
Comment utiliser le module QxModelView pour travailler avec le moteur
model/view de Qt (Qt widgets et vues QML) ?
|
Le module QxModelView
permet d'utiliser le moteur model/view
de Qt avec toutes les classes enregistr�es dans le contexte QxOrm :
- Qt widgets : utilisation de QTableView ou QListView par exemple pour
afficher/modifier le contenu d'une table de base de donn�es ;
- QML : toute propri�t� enregistr�e dans le contexte QxOrm est accessible en QML : le module QxModelView permet
ainsi de faciliter l'int�raction entre QML et les bases de donn�es.
L'interface qx::IxModel
propose une base commune pour tous les mod�les li�s aux classes persistantes d�clar�es dans le
contexte QxOrm. Les m�thodes de cette classe pr�fix�es par 'qx' appellent les fonctions du
namespace 'qx::dao' et communiquent donc directement avec la base de donn�es.
Le projet de test qxBlogModelView pr�sent dans le dossier ./test/ du package QxOrm
montre comment cr�er rapidement un mod�le et l'associer au moteur model/view de Qt (d'abord
dans un widget Qt, puis dans une vue QML).
1- Exemple de cr�ation d'un mod�le pour afficher/modifier les donn�es de la table 'author'
(voir le tutoriel qxBlog pour la d�finition de la
classe 'author') dans un QTableView :
qx::IxModel * pModel = new qx::QxModel<author>();
pModel->qxFetchAll();QTableView tableView;
tableView.setModel(pModel);
tableView.show();
|
Ce qui donne le r�sultat suivant � l'ex�cution :

2- Voici un autre exemple en QML (en Qt5, le module QxModelView �tant
�galement compatible avec Qt4) :
qx::IxModel * pModel = new qx::QxModel<author>();
pModel->qxFetchAll();QQuickView qmlView;
qmlView.rootContext()->setContextProperty("myModel", pModel);
qmlView.setSource(QUrl("qrc:/documents/main.qml"));
qmlView.show();
|
Et voici le contenu du fichier 'main.qml' :
import QtQuick 2.1
import QtQuick.Controls 1.0
Item {
width: 400
height: 300
Row {
height: 20
spacing: 20
Button {
text: "Clear"
onClicked: myModel.clear()
}
Button {
text: "Fetch All"
onClicked: myModel.qxFetchAll_()
}
Button {
text: "Save"
onClicked: myModel.qxSave_()
}
}
ListView {
y: 30
height: 270
model: myModel
delegate: Row {
height: 20
spacing: 10
Text { text: "id: " + author_id }
TextField {
text: name
onTextChanged: name = text
}
}
}
}
|
Ce qui donne le r�sultat suivant � l'ex�cution :

Comme on peut le constater dans le fichier 'main.qml', les propri�t�s 'author_id' et
'name' du mod�le 'author' (variable myModel) sont accessibles automatiquement en
lecture/�criture (car elles ont �t� enregistr�es dans le contexte QxOrm).
De plus, l'interface qx::IxModel propose une liste de m�thodes accessibles en QML (utilisation de
Q_INVOKABLE) pour communiquer directement avec la base de donn�es : ainsi, le bouton
'Save' de l'�cran ci-dessus enregistre le mod�le en base de donn�es depuis QML.
Remarque : un plugin de QxEntityEditor permet de g�n�rer automatiquement le code des
mod�les pour la gestion des relations. Il est ainsi possible de travailler avec des mod�les
imbriqu�s.
|
|