Qu'est-ce que QxOrm ?
|
QxOrm est une bibliothèque C++ open source de gestion de données (Object Relational Mapping, ORM) sous licence LGPL.
QxOrm est développé par Lionel Marty, 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 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).
La bibliothèque QxOrm a été retenue pour faire partie du programme Qt Ambassador.
|
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.
QxOrm est également disponible sur le site SourceForge : plate-forme d'hébergement de projets de développements de logiciels libres.
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 compiler QxOrm ?
|
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 et Visual C++ 2010.
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.).
 |
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/ |
 |
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 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://qt.nokia.com/ |
|
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 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).
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 activer cette fonctionnalité dans QxOrm, vous devez modifier la constante _QX_SERIALIZE_POLYMORPHIC du fichier QxConfig.h.
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.
|
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, texte, etc.
Le fichier QxConfig.h 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 ?
|
Il est possible de définir un identifiant unique de type QString ou autre avec la bibliothèque QxOrm.
Par défaut, l'identifiant unique est de type long.
Pour indiquer qu'une classe a un identifiant unique de type QString ou autre, il faut spécialiser le template qx::trait::get_primary_key.
Pour simplifier, vous pouvez utiliser la macro : QX_REGISTER_PRIMARY_KEY(myClass, QString).
Attention : la macro QX_REGISTER_PRIMARY_KEY doit être utilisée avant la macro QX_REGISTER_HPP_... dans la définition de la classe, sinon une erreur de compilation se produit.
|
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 <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());
}
|
|
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 :
- Single Table Inheritance ;
- Class Table Inheritance ;
- Concrete Table Inheritance.
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 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); }
};
}}}
#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";
}
|
|
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)
|
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.
|
|