What is QxOrm ?
|
QxOrm is a C++ library designed to provide Object Relational Mapping (ORM) feature to C++ users.
QxOrm is developed by Lionel Marty, a software development engineer since 2003.
QxOrm provides many functionalities starting from a simple C++ setting function by class :
- persistence : communication with a lot of databases (with 1-1, 1-n, n-1 and n-n relationships)
- serialization : binary and xml format
- reflection (or introspection) : access to classes definitions, retrieve properties and call classes methods
|
How to contact QxOrm to report a bug or ask a question ?
|
If you find a bug or if you have a question about QxOrm library, you can send an e-mail to : support@qxorm.com.
QxOrm is also available on the SourceForge site : platform hosting development projects of free software.
A forum dedicated to QxOrm is available by clicking here.
|
How to build QxOrm library ?
|
QxOrm uses qmake process from Qt library to create makefile and build the project.
qmake is portable and multi-platform, so it works perfectly on Windows, Linux (Unix) and Mac.
To build QxOrm library, just execute following commands :
qmake
make debug
make release
On Windows, *.vcproj and *.sln files are available for Visual C++ 2008 and Visual C++ 2010.
*.pro files are readable by Qt Creator, and some plugins are available to interface to other C++ IDE.
mingw_build_all_debug.bat and mingw_build_all_release.bat scripts in the directory ./tools/ can quickly built QxOrm library and all tests with MinGW compiler on Windows.
gcc_build_all_debug.sh and gcc_build_all_release.sh scripts in the directory ./tools/ can quickly built QxOrm library and all tests with GCC compiler on Unix.
Note : depending on your development environment, it may be necessary to modify QxOrm.pri file to set boost package configuration :
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"
|
What are the databases supported by QxOrm ?
|
QxOrm uses the engine QtSql of Qt based on a system of plug-in.
A detailed list of supported databases is available on the website of Qt here. The plug-in ODBC (QODBC) ensures compatibility with many databases. For optimal performances, it is possible to use a plug-in specific to a database :
- QMYSQL : MySQL
- QPSQL : PostgreSQL (versions 7.3 and above)
- QOCI : Oracle Call Interfaces Driver
- QSQLITE : SQLite version 3
- QDB2 : IBM DB2 (version 7.1 and above)
- QIBASE : Borland InterBase
- QTDS : Sybase Adaptive Server
|
Why QxOrm is dependent on two libraries : boost and Qt ?
|
QxOrm uses many functionalities available in excellent libraries : boost and Qt.
In addition, these two libraries are used in many projects both professional and open source.
A large number of forums, tutorials, and a whole community are available to answer any issue that could arise.
The QxOrm objective is not to redevelop features that already exist but to provide a powerful tool for access to databases such as it exists in other languages (Java with Hibernate, .Net with NHibernate, Ruby, Python, etc...).
 |
boost : many of boost's founders are on the C++ standard committee and several boost libraries have been accepted for incorporation into C++1x (new standard for the C++ programming language).
The boost's libraries are aimed at a wide range of C++ users and application domains.
QxOrm uses the following features of boost : smart_pointer, serialization, type_traits, multi_index_container, unordered_container, any, tuple, foreach, function.
It is recommended to install the latest version of boost available at the following address : http://www.boost.org/ |
 |
Qt : cross-platform application development framework : ihm (QtGui), network (QtNetwork), xml (QtXml), database (QtSql)...
Qt provides excellent support and documentation. Using Qt, you can write simple and powerful C++ code.
Qt is produced by Nokia's Qt Development Frameworks division and is available under LGPL license.
QxOrm is compatible with a lot of Qt's objects : QObject, QString, QDate, QTime, QDateTime, QList, QHash, QSharedPointer, QScopedPointer...
It is recommended to install the latest version of Qt available at the following address : http://qt.nokia.com/ |
|
Why does QxOrm require a precompiled header to be used ?
|
QxOrm uses the techniques of C++ meta-programming to provide most of its functionalities.
You do not need to know how to use meta-programming to work with QxOrm library.
Indeed, QxOrm is simple to use and the C++ code written with Qt and QxOrm is easy to read, therefore easy to develop and to maintain.
However, meta-programming is costly in compilation times.
By using a precompiled.h file, your project will be compiled much more quickly.
Last but not least, another advantage is that the file QxOrm.h includes the basic functionalities of libraries boost and Qt.
It is thus not necessary anymore to write #include <QtCore/QString.h> to use the class QString of Qt for example.
In the same way, there is no need anymore to write #include <boost/shared_ptr.hpp> to use smart pointers of boost library.
|
Is it possible to accelerate the compilation times of my project ?
|
Yes, if the serialization of your data in xml format is not used in your project, you can disable this functionality.
The compilation times will be then reduced but you will not have anymore access to the namespace qx::serialization:xml.
To disable xml serialization, it is necessary to open the QxConfig.h file and to modify the constant _QX_SERIALIZE_XML.
A recompilation of QxOrm library is necessary to take into account this modification.
Another possibility is to use the polymorphic classes of the library boost::serialization (instead of template).
This feature reduces compilation times and the size of the executable that is generated.
However, the speed of execution of your program will be reduced since part of the work carried out during compilation will be done during the execution of your application.
To activate this feature in QxOrm, you must modify the constant _QX_SERIALIZE_POLYMORPHIC of the QxConfig.h file.
Warning : the serialization functions will be then accessible from the following namespace : qx::serialization::polymorphic_binary, qx::serialization::polymorphic_text and qx::serialization::polymorphic_xml.
A recompilation of QxOrm library is necessary to take into account this modification.
|
What are all types of serialization available ?
|
QxOrm is based on boost serialization library.
There are several types of serialization available : binary, xml, text, etc...
QxConfig.h file can enable and/or disable some types of serialization.
Each type of serialization has its own characteristics :
* binary : smallest, fastest, non-portable
* text : larger, slower, portable
* xml : largest, slowest, portable
Note : binary type is not portable, so you can't transfer data between Windows and Unix for example.
If you need to transfer data over network between different platforms, you have to use text or xml serialization.
QxOrm provides another solution : portable_binary serialization.
portable_binary has the same characteristics as binary type and can serialize data in a portable way.
However, portable_binary is not provided officially by boost library, so it's necessary to test before using in a production software.
|
Why does QxOrm provide a new type of container qx::QxCollection<Key, Value> ?
|
There are many containers in stl, boost and Qt libraries.
It is therefore legitimate to ask this question : what is qx::QxCollection<Key, Value> ?
qx::QxCollection<Key, Value> is a new container (based on the excellent library boost::multi_index_container) which has the following functionalities :
- preserves the insertion order of elements in the list
- quick access to an element by its index : is equivalent to std::vector<T> or QList<T> for example
- quick access to an element by a key (hash-map) : is equivalent to QHash<Key, Value> or boost::unordered_map<Key, Value> for example
- sort by Key type and by Value type
Note : qx::QxCollection<Key, Value> is compatible with the foreach macro provided by Qt library and the BOOST_FOREACH macro provided by boost library.
However, each element returned by these 2 macros corresponds to an object of type std::pair<Key, Value>.
To obtain a more natural and more readable result, it is advised to use the _foreach macro : this macro uses BOOST_FOREACH for all the containers except for qx::QxCollection<Key, Value>.
In this case, the returned element corresponds to the Value type (cf. sample).
The macro _foreach is compatible with all containers (stl, Qt, boost...) since it uses the macro BOOST_FOREACH.
Additional note : qx::QxCollection<Key, Value> is particularly suited to receive data resulting from a database. Indeed, these data can be sorted (by using ORDER BY in a sql request for example), it is thus important to preserve the insertion order of the elements in the list.
Furthermore, each data resulting from a database has a unique id. It is thus important to be able to access quickly to an element based on this single identifier (hash-map).
Sample :
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();
|
|
Why does QxOrm provide a new smart-pointer qx::dao::ptr<T> ?
|
QxOrm can be used with smart-pointers of boost and Qt libraries.
QxOrm smart-pointer is based on QSharedPointer and provides new features with 'qx::dao::...' functions.
qx::dao::ptr<T> keeps automatically values from database.
So it's possible to detect if an instance has been modified using the method 'isDirty()' : this method can return list of properties changed.
qx::dao::ptr<T> can also be used with the function 'qx::dao::update_optimized()' to update in database only properties changed.
qx::dao::ptr<T> can be used with a simple object and with many containers : stl, boost, Qt and qx::QxCollection<Key, Value>.
Sample :
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);
|
|
Should I use QString or std::string ?
|
QxOrm advises to use the QString class for the management of the character strings.
Even if boost provides many functionalities with its module boost::string_algo, the QString class is easier to use and supports many formats : ASCII, Utf8, Utf16...
However, QxOrm is compatible with std::string and std::wstring if you prefer to use this kind of character strings.
|
Is it necessary to use smart-pointers ?
|
QxOrm strongly advises to use boost or Qt smart-pointers.
The C++ language does not have Garbage Collector like Java or C# for example.
The use of smart-pointers simplifies the memory management in C++.
The ideal in a C++ program is not to have any call to delete or delete[].
Furthermore, smart-pointer is a new functionality of the new C++ standard : C++1x.
It is thus essential to know the following classes today :
|
The primary key is long type by default. Is it possible to use a key of QString type or other ?
|
It is possible to define a unique id of QString type or other with QxOrm library.
By default, the unique id is long type.
To indicate that a class has a single identifier of QString type or other, it is necessary to specialize the template qx::trait::get_primary_key.
To simplify, you can use the macro : QX_REGISTER_PRIMARY_KEY(myClass, QString).
Warning : the macro QX_REGISTER_PRIMARY_KEY must be used before the macro QX_REGISTER_HPP_... in the definition of your class, otherwise a compilation error occurs.
|
How to define a 'multi-columns primary key' (composite key) ?
|
QxOrm supports 'multi-columns primary key'.
The class id must be defined with following type :
* QPair or std::pair to define 2 columns
* boost::tuple to define from 2 columns to 9 columns
It is necessary to use the macro QX_REGISTER_PRIMARY_KEY() to specialize the template and to map class id with multi-columns in database.
The list of multi-columns names must be defined with '|' character : 'column1|column2|column3|etc...'.
Sample with class 'author' from project 'qxBlogCompositeKey', this class has an id mapped to 3 columns in database :
#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());
}
|
|
How to enable/disable the module QxMemLeak for automatic detection of memory leaks ?
|
QxMemLeak module provides a fast detection of memory leaks in Debug mode once the execution of the program is finished (with indication of the file and the line => style MFC from Microsoft).
This module is developed by Wu Yongwei and has undergone some modifications to be integrated in QxOrm.
If another tool is already used in your projects (Valgrind for example), this functionality should not be activated.
To enable/disable QxMemLeak module, all is needed is to modify the constant _QX_USE_MEM_LEAK_DETECTION defined in the QxConfig.h. file.
A recompilation of QxOrm library is necessary to take into account this modification.
|
How to manage inheritance and database ?
|
With ORM tools, there is usually 3 strategies to manage inheritance and database :
* Single Table Inheritance
* Class Table Inheritance
* Concrete Table Inheritance
QxOrm works by default with Concrete Table Inheritance strategy (others are not supported yet).
Many tutorials and forums are available on internet to more details about ORM inheritance and database.
You can find a sample in the directory ./test/qxDllSample/dll2/ with the class BaseClassTrigger.
|
How to define a 'Trigger' with QxOrm ?
|
With QxOrm Trigger, it is possible to execute process before and/or after an insert, update or delete query in the database.
You can find a sample in the directory ./test/qxDllSample/dll2/ with the class BaseClassTrigger.
The class BaseClassTrigger contains 5 properties : m_id, m_dateCreation, m_dateModification, m_userCreation and m_userModification.
Each property will be automatically auto-updated for all derived classes from BaseClassTrigger (see Foo class and Bar class in the same project).
It is necessary to specialize 'QxDao_Trigger' template to work with this feature.
#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";
}
|
|
How to register an abstract class into QxOrm context ?
|
A C++ abstract class (with at least one pure virtual method) cannot be mapped to a table of a database (because it cannot be instantiated).
However, in some case, it can be interesting to define properties into abstract class used by a persistant object (by inheritance).
A sample of abstract class registered into QxOrm context is available in the directory ./test/qxDllSample/dll2/ of QxOrm package with the class BaseClassTrigger.
To register an abstract class into QxOrm context, you have to :
- register the class with 'void register_class' like any other class ;
- use macro QX_REGISTER_ABSTRACT_CLASS(className) just after the class definition.
|
How to register a class defined into a namespace into QxOrm context ?
|
If a class is defined into a namespace, a compilation error occurs using macros : QX_REGISTER_HPP and QX_REGISTER_CPP.
To avoid this compilation error, it is necessary to use followings macros : QX_REGISTER_COMPLEX_CLASS_NAME_HPP and QX_REGISTER_COMPLEX_CLASS_NAME_CPP.
You can find a sample in the directory ./test/qxDllSample/dll1/ of QxOrm package with the class CPerson defined into namespace qx::test :
* QX_REGISTER_COMPLEX_CLASS_NAME_HPP_QX_DLL1(qx::test::CPerson, QObject, 0, qx_test_CPerson)
|
|