FAQ Qt
FAQ QtConsultez toutes les FAQ
Nombre d'auteurs : 26, nombre de questions : 298, dernière mise à jour : 15 juin 2021
- Qu'est-ce que QxOrm ?
- Quelles sont les bases de données prises en compte par QxOrm ?
- Comment compiler QxOrm ?
- Comment contacter QxOrm pour indiquer un bogue ou poser une question ?
- Pourquoi QxOrm nécessite-t-il un en-tête précompilé (precompiled header) pour être utilisé ?
- Est-il possible de diminuer les temps de compilation d'un projet ?
- Comment activer/désactiver le module QxMemLeak pour la détection automatique des fuites mémoire ?
- Quels sont les différents types de sérialisation disponibles ?
- Comment déclarer une classe définie dans un espace de nom (namespace) dans le contexte QxOrm ?
- Pourquoi QxOrm fournit-il un nouveau type de conteneur qx::QxCollection<Key, Value> ?
- Pourquoi QxOrm est dépendant de deux bibliothèques : boost et Qt ?
- Pourquoi QxOrm fournit un nouveau type de pointeur intelligent qx::dao::ptr<T> ?
- Faut-il utiliser les pointeurs intelligents ?
- Comment utiliser les déclencheurs ?
- Comment définir une clé primaire sur plusieurs colonnes (composite key) ?
- La clé primaire est de type long par défaut. Est-il possible d'utiliser une clé de type QString ou autre ?
- Comment déclarer une classe abstraite dans le contexte QxOrm ?
- Comment utiliser les déclencheurs ?
- Comment utiliser les sessions pour simplifier la gestion des transactions des bases de données ?
- Comment utiliser le mécanisme de suppression logique (soft delete) ?
- Comment gérer la notion d'héritage avec la base de données ?
- Faut-il utiliser QString ou std::string ?
QxOrm est une bibliothèque C++ open source de gestion de données (object relational mapping, ORM) sous licence LGPL.
À 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 et XML) ;
- 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).
Lien : Tutoriel présentant les fonctionnalités de base sur la notion de persistance
QxOrm utilise le moteur QtSql de Qt, basé sur un système de plug-ins. Une liste détaillée des bases de données supportées est disponible dans la documentation de Qt. Le plug-in ODBC (QODBC) assure une compatibilité avec de nombreuses bases de données. Pour des performances optimales, il est conseillé d'utiliser le plug-in spécifique à une base de données :
- QMYSQL : MySQL ;
- QPSQL : PostgreSQL (version 7.3 et supérieures) ;
- QOCI : Oracle Call Interface Driver ;
- QSQLITE : SQLite version 3 ;
- QDB2 : IBM DB2 (version 7.1 et supérieures) ;
- QIBASE : Borland InterBase ;
- QTDS : Sybase Adaptive Server.
QxOrm utilise 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 EDI Visual C++ 2008 et Visual C++ 2010.
Les fichiers *.pro sont lisibles par Qt Creator et des plug-ins 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.
Enfin, 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 Unix.
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"
Si vous trouvez un bogue ou si vous avez une question concernant le fonctionnement de la bibliothèque QxOrm, vous pouvez retrouver la communauté française de QxOrm sur le forum de Developpez.com.
Un forum (en anglais) dédié à QxOrm est également disponible.
Enfin, il est possible d'envoyer un mail à .
QxOrm utilise les techniques de métaprogrammation C++ pour fournir une grande partie de ses fonctionnalités. Il n'est pas nécessaire 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 coûteuse en temps de compilation. En utilisant un fichier d'en-tête précompilé, 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.
Oui, si la sérialisation 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 sérialisation XML, il faut ouvrir le fichier QxConfig.h et modifier la constante _QX_SERIALIZE_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). Ainsi, au lieu d'utiliser de manière intensive la métaprogrammation, l'implémentation des classes polymorphiques est basée sur la notion d'héritage. Cette fonctionnalité réduit donc les temps de compilation ainsi que la taille de l'exécutable généré. En contrepartie, 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 activer cette fonctionnalité dans QxOrm, vous devez modifier la constante _QX_SERIALIZE_POLYMORPHIC du fichier QxConfig.h.
Attention : les fonctions de sérialisation 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.
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, dans le style de la MFC de Microsoft).
Ce module a été développé par Wu Yongwei et a subi quelques modifications pour intégration à 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.
Tous les fichiers source *.cpp doivent inclure le fichier suivant pour profiter de la fonctionnalité : #include <QxMemLeak.h>. Le mode de fonctionnement du module QxMemLeak est de surcharger les opérateurs new et delete et d'utiliser les macros __FILE__ et __LINE__ (compatibles avec la plupart des compilateurs) afin de lister toutes les créations/destructions d'instances. Un compte-rendu est disponible à la fin de l'exécution du programme. Par exemple, pour une fuite mémoire détectée dans le fichier main.cpp à la ligne 74, les traces suivantes sont générées :
[QxOrm] Leaked object at 0xf52ad8 (size 16, src\main.cpp:74)
[QxOrm] **** 1 memory leaks found ****
QxOrm utilise le framework de sérialisation proposé par la bibliothèque boost. Plusieurs types de sérialisation sont disponibles : binaire, XML, texte. Le fichier QxConfig.h permet d'activer et désactiver les différents types de sérialisation.
- binaire : le plus petit et rapide, non portable ;
- texte : plus gros et lent, portable ;
- XML : le plus gros et le plus lent, portable.
Remarque : le type binary n'est pas portable, ce qui signifie que des données ne peuvent pas s'échanger entre un système Windows et un système Unix. Si vous devez faire transiter des données sur un réseau à travers plusieurs systèmes d'exploitation, il faut utiliser les types de sérialisation texte ou XML.
QxOrm propose également une autre solution : le type de sérialisation portable_binary. Le type portable_binary possède les mêmes caractéristiques que le type binary, mais permet d'échanger des données de manière portable. Cependant, ce type de sérialisation 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.
Autre remarque : le mécanisme de sérialisation XML est lent car il nécessite de convertir toutes les propriétés d'une classe au format texte et de procéder à un parsing XML pour déchiffrer les informations. Voici un exemple de sérialisation XML d'une liste d'objets de type std::vector<T>, T ayant trois propriétés (id, name et desc) :
<std.vector-boost.shared_ptr-drug--
class_id
=
"0"
tracking_level
=
"0"
version
=
"0"
>
<count>
3</count>
<item_version>
1</item_version>
<item
class_id
=
"1"
tracking_level
=
"0"
version
=
"1"
>
<px
class_id
=
"2"
tracking_level
=
"1"
version
=
"1"
object_id
=
"_0"
>
<id>
1</id>
<name
class_id
=
"3"
tracking_level
=
"0"
version
=
"0"
>
name1</name>
<desc>
desc1</desc>
</px>
</item>
<item>
<px
class_id_reference
=
"2"
object_id
=
"_1"
>
<id>
2</id>
<name>
name2 modified</name>
<desc>
desc2 modified</desc>
</px>
</item>
<item>
<px
class_id_reference
=
"2"
object_id
=
"_2"
>
<id>
3</id>
<name>
name3</name>
<desc>
desc3</desc>
</px>
</item>
</std.vector-boost.shared_ptr-drug-->
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 QX_REGISTER_COMPLEX_CLASS_NAME_HPP… et QX_REGISTER_COMPLEX_CLASS_NAME_CPP…
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 "_".
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)
Il existe de nombreux conteneurs dans les bibliothèques stl, boost et Qt. Il est donc légitime de se demander à quoi sert qx::QxCollection<Key, Value>.
qx::QxCollection<Key, Value> est un nouveau conteneur (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 conteneurs sauf pour qx::QxCollection<Key, Value>. Dans ce cas, l'élément renvoyé correspond au type Value (voir l'exemple d'utilisation). La macro _foreach est donc compatible avec tous les conteneurs (stl, Qt, boost, etc.), puisqu'elle utilise la macro BOOST_FOREACH.
Autre remarque : qx::QxCollection<Key, Value> est particulièrement adaptée 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> :
/* définition d'une classe 'produit' avec 3 propriétés : 'code', 'name', 'description' */
class
produit {
public
: QString
code; QString
name; QString
desc; }
;
/* pointeur intelligent associé à la classe 'produit' */
typedef
boost::
shared_ptr<
produit>
produit_ptr;
/* collection de produits (accès rapide à un élément de la collection par la propriété 'code') */
qx::
QxCollection<
QString
, produit_ptr>
lst;
/* création de 3 nouveaux produits */
produit_ptr d1; d1.reset(new
produit()); d1->
code =
"code1"
; d1->
name =
"name1"
; d1->
desc =
"desc1"
;
produit_ptr d2; d2.reset(new
produit()); d2->
code =
"code2"
; d2->
name =
"name2"
; d2->
desc =
"desc2"
;
produit_ptr d3; d3.reset(new
produit()); d3->
code =
"code3"
; d3->
name =
"name3"
; d3->
desc =
"desc3"
;
/* insertion des 3 produits dans la collection */
lst.insert(d1->
code, d1);
lst.insert(d2->
code, d2);
lst.insert(d3->
code, d3);
/* parcours de la collection en utilisant le mot-clé '_foreach' */
_foreach(produit_ptr p, lst)
{
qDebug
() <<
qPrintable
(p->
name) <<
" "
<<
qPrintable
(p->
desc); }
/* parcours de la collection en utilisant une boucle 'for' */
for
(long
l =
0
; l <
lst.count(); ++
l)
{
produit_ptr p =
lst.getByIndex(l);
QString
code =
lst.getKeyByIndex(l);
qDebug
() <<
qPrintable
(p->
name) <<
" "
<<
qPrintable
(p->
desc);
}
/* parcours de la collection en utilisant le style Java avec 'QxCollectionIterator' */
qx::
QxCollectionIterator<
QString
, produit_ptr>
itr(lst);
while
(itr.next())
{
QString
code =
itr.key();
qDebug
() <<
qPrintable
(itr.value()->
name) <<
" "
<<
qPrintable
(itr.value()->
desc);
}
/* effectue un tri croissant par clé (propriété 'code') et décroissant par valeur */
lst.sortByKey(true
);
lst.sortByValue(false
);
/* accès rapide à un produit par son 'code' */
produit_ptr p =
lst.getByKey("code2"
);
/* accès rapide à un produit par son index (position) dans la collection */
produit_ptr p =
lst.getByIndex(2
);
/* teste si un produit existe dans la collection et si la liste est vide */
bool
bExist =
lst.exist("code3"
);
bool
bEmpty =
lst.empty();
/* supprime de la collection le 2e élément */
lst.removeByIndex(2
);
/* supprime de la collection l'élément avec le code 'code3' */
lst.removeByKey("code3"
);
/* efface tous les éléments de la collection */
lst.clear();
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 tutoriels 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 et ActiveRecord, Python et SQLAlchemy, PHP avec Doctrine, etc.).
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 orienté 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.
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 et sa nouvelle licence LGPL, Qt est, sans arrière-pensée, 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.
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>, il 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 de la méthode 'isDirty()'
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("|"
))); }
// met à jour uniquement la propriété 'm_text' de l'instance 'blog_isdirty'
daoError =
qx::dao::
update_optimized(blog_isdirty);
qAssert
(!
daoError.isValid() &&
!
blog_isdirty.isDirty());
qx::
dump(blog_isdirty);
// exemple d'utilisation de la méthode 'isDirty()' avec une liste d'objets
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("|"
))); }
// met à jour la propriété 'm_name' en position 1, la propriété 'm_birthdate' en position 2 et ne change rien en position 0
daoError =
qx::dao::
update_optimized(container_isdirty);
qAssert
(!
daoError.isValid() &&
!
container_isdirty.isDirty());
qx::
dump(container_isdirty);
// récupère uniquement la propriété 'm_dt_creation' du blog
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);
QxOrm conseille fortement d'utiliser les pointeurs intelligents de boost ou Qt, car le langage C++ ne possède pas de garbage collector comme Java ou C#. L'utilisation des pointeurs intelligents 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 pointeurs intelligents font partie de la nouvelle norme C++, C++11. Il est donc essentiel aujourd'hui de connaître les classes suivantes :
- shared_ptr, scoped_ptr et weak_ptr pour les pointeurs intelligents de la bibliothèque boost ;
- QSharedPointer, QScopedPointer et QWeakPointer pour les pointeurs intelligents de la bibliothèque Qt.
Pour plus d'informations sur les pointeurs intelligents du framework Qt, regardez l'excellent article de Thiago Macieira et Harald Fernengel, traduit par Thibaut Cuvelier.
Les déclencheurs (triggers) 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
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); }
}
;
}
// namespace detail
}
// namespace dao
}
// namespace qx
#endif
// _QX_BASE_CLASS_TRIGGER_H_
#include
"../include/precompiled.h"
#include
"../include/BaseClassTrigger.h"
#include
<QxMemLeak.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"
;
}
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
:
// -- clé composée (clé primaire définie sur plusieurs colonnes dans la base de données)
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
typedef
boost::
shared_ptr<
blog>
blog_ptr;
typedef
std::
vector<
blog_ptr>
list_blog;
// -- enum
enum
enum_sex {
male, female, unknown }
;
// -- propriétés
type_composite_key m_id;
QString
m_name;
QDate
m_birthdate;
enum_sex m_sex;
list_blog m_blogX;
// -- constructeur, destructeur virtuel
author() : m_id(""
, 0
, ""
), m_sex(unknown) {
; }
virtual
~
author() {
; }
// -- méthodes
int
age() const
;
// -- méthodes d'accès à la clé composée
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); }
// -- méthodes de modification de la clé composée
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
<QxMemLeak.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());
}
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<T>::type 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
:
// -- propriétés
QString
m_id;
QString
m_name;
// -- constructeur, destructeur virtuel
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_
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.
- déclarer la classe avec la méthode void register_class, comme n'importe quelle autre classe ;
- utiliser la macro QX_REGISTER_ABSTRACT_CLASS(className) juste après la définition de la classe.
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 aucune modification n'est 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 :
{
// Ouverture d'un scope où une session sera instanciée
// Création d'une session : une connexion valide à la BDD est assignée à la session et une transaction est démarrée
qx::
QxSession session;
// Exécution d'une série d'opérations avec la BDD (en utilisant l'opérateur += de la classe qx::QxSession et la connexion de la 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());
// Si la session n'est pas valide (donc une erreur s'est produite) => affichage de la 1re erreur de la session
if
(!
session.isValid()) {
qDebug
("[QxOrm] session error : '%s'"
, qPrintable
(session.firstError().text())); }
}
// Fermeture du scope : la session est détruite (transaction => commit ou rollback automatique)
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 connexion à la base de données de la session à chaque fonction qx::dao::... (en utilisant la méthode session.database()). De plus, il est possible d'initialiser une session avec sa propre connexion (provenant d'un pool de connexions, par exemple) en utilisant le constructeur de la classe qx::QxSession.
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 si la ligne est supprimée ou non. Cette colonne peut contenir soit un booléen (1 pour une ligne supprimée, 0 ou vide pour une ligne non supprimée), soit la date et l'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().
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 :
- Single Table Inheritance (une seule table regroupant toutes les propriétés d'une hiérarchie d'héritage de classes) ;
- Class Table Inheritance (à chaque classe d'une hiérarchie d'héritage est associée une table dans la base de données) ;
- Concrete Table Inheritance (une table par classe concrète dans la hiérarchie d'héritage).
QxOrm utilise par défaut la stratégie Concrete Table Inheritance (les autres stratégies ne sont pas fonctionnelles à l'heure actuelle).
Un exemple d'utilisation avec une classe de base se trouve dans le dossier ./test/qxDllSample/dll2/ de la distribution de QxOrm avec la classe BaseClassTrigger.
QxOrm conseille d'utiliser la classe QString pour la gestion des chaînes de caractères.
Même si le framework 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 différents encodages : ASCII, UTF-8, UTF-16, etc.
Cependant, QxOrm est compatible avec std::string et std::wstring si vous préférez utiliser ce type de chaînes de caractères. Le type std::wstring permet de manipuler les chaînes de caractères Unicode. Pour plus d'informations sur la gestion des chaînes de caractères de la bibliothèque standard, rendez-vous sur la FAQ C++ de developpez.com.