Qt: QSqlDatabase object in class (how to declare?)

I am trying to create a class that should handle all data to and from a sqlite database. However, I am fairly new to QT and C ++ and am wondering about declaring a database object in a class. I may need some advice on what I am doing right and wrong, and how it usually should or can be done. My goal was to create a separate QSqlDatabase for the class and use it for each function within the class.

At the moment, I have the following code:

main.cpp

#include "mainwindow.h"
#include "database.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Database db;
    MainWindow w;

    if(db.createStructure())
    {
        w.show();
    }

    return a.exec();
}

      

database.h

#ifndef DATABASE_H
#define DATABASE_H

#include <QObject>
#include <QSqlDatabase>

class Database : public QObject
{
    Q_OBJECT
public:
    explicit Database(QObject *parent = 0);

    // FUNCTIONS
    bool createStructure();

signals:

public slots:

private:
    // VARIABLES
    QSqlDatabase m_db;

    // FUNCTIONS
    bool open();
    void close();
    bool transaction();
    bool commit();
};

#endif // DATABASE_H

      

database.cpp

#include "database.h"
#include <QCoreApplication>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QList>

Database::Database(QObject *parent) :
    QObject(parent)
{
    m_db = QSqlDatabase::addDatabase("QSQLITE");
    m_db.setHostName("localhost");
    m_db.setDatabaseName(QCoreApplication::applicationDirPath() + "/events.db");
}

// PRIVATE

bool Database::open()
{
    return m_db.open();
}

void Database::close()
{
    return m_db.close();
}

bool Database::transaction()
{
    return m_db.transaction();
}

bool Database::commit()
{
    return m_db.commit();
}

// PUBLIC

bool Database::createStructure()
{
    bool prepared;
    QList<QString> commands;

    commands.append("CREATE TABLE...;");
    commands.append("CREATE TABLE...;");
    commands.append("CREATE TABLE...;");

    if (!Database::open())
    {
        return false;
    }
    else
    {
        if (!Database::transaction())
        {
            Database::close();
            return false;
        }
        else
        {
            foreach(QString command, commands)
            {
                QSqlQuery query;
                prepared = query.prepare(command);

                if(!prepared)
                {
                    if (!Database::commit())
                    {
                        Database::close();
                        return false;
                    }
                    else
                    {
                        Database::close();
                        return false;
                    }
                }
                else
                {
                    if(!query.exec())
                    {
                        if (!Database::commit())
                        {
                            Database::close();
                            return false;
                        }
                        else
                        {
                            Database::close();
                            return false;
                        }
                    }
                }
            }

            if (!Database::commit())
            {
                Database::close();
                return false;
            }
            else
            {
                Database::close();
                return true;
            }
        }
    }
}

      

This code works.

However, the QSQLITE database is not added in one go to the m_db object, but every time you call the function in the class, because ...

Database::Database(QObject *parent) :
    QObject(parent)
{
    m_db = QSqlDatabase::addDatabase("QSQLITE");
    m_db.setHostName("localhost");
    m_db.setDatabaseName(QCoreApplication::applicationDirPath() + "/events.db");
}

      

... codeblock is executed every time. The current default connection is just overridden and since the new one is the same it doesn't affect the program, but it doesn't look like a neat solution.

So I tried to replace this code block with a declaration function that I can call from main.cpp once ...

main.cpp

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Database db;
    MainWindow w;

    db.declare("QSQLITE", "localhost", QCoreApplication::applicationDirPath() + "/events.db");

    if(db.createStructure())
    {
        w.show();
    }

    return a.exec();
}

      

database.cpp

void Database::declare(QString driver, QString host, QString path)
{
    m_db = QSqlDatabase::addDatabase(driver);
    m_db.setHostName(host);
    m_db.setDatabaseName(path);
}

      

... but the values ​​for the m_db object are of course only available in the function-declaration, not for other functions that I call afterwards.

My best guess for a solution would be to declare QSqlDatabase in main.cpp and pass it to the function that it should call:

main.cpp

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QSqlDatabase qdb = QSqlDatabase::addDatabase("QSQLITE");
    qdb.setHostName("localhost");
    qdb.setDatabaseName(QCoreApplication::applicationDirPath() + "/events.db");

    Database db;
    MainWindow w;

    if(db.createStructure(qdb))
    {
        w.show();
    }

    return a.exec();
}

      

database.cpp

bool Database::open(QSqlDatabase qdb)
{
    return qdb.open();
}

void Database::close(QSqlDatabase qdb)
{
    return qdb.close();
}

bool Database::transaction(QSqlDatabase qdb)
{
    return qdb.transaction();
}

bool Database::commit(QSqlDatabase qdb)
{
    return qdb.commit();
}

bool Database::createStructure(QSqlDatabase qdb)
{
    bool prepared;
    QList<QString> commands;

    commands.append("CREATE TABLE...;");
    commands.append("CREATE TABLE...;");
    commands.append("CREATE TABLE...;");

    if (!Database::open(qdb))
    {
        return false;
    }
    else
    {
        if (!Database::transaction(qdb))
        {
            Database::close(qdb);
            return false;
        }
        else
        {
            foreach(QString command, commands)
            {
                QSqlQuery query;
                prepared = query.prepare(command);

                if(!prepared)
                {
                    if (!Database::commit(qdb))
                    {
                        Database::close(qdb);
                        return false;
                    }
                    else
                    {
                        Database::close(qdb);
                        return false;
                    }
                }
                else
                {
                    if(!query.exec())
                    {
                        if (!Database::commit(qdb))
                        {
                            Database::close(qdb);
                            return false;
                        }
                        else
                        {
                            Database::close(qdb);
                            return false;
                        }
                    }
                }
            }

            if (!Database::commit(qdb))
            {
                Database::close(qdb);
                return false;
            }
            else
            {
                Database::close(qdb);
                return true;
            }
        }
    }
}

      

Is it possible to somehow store a reusable QSqlDatabase object in the class? If so, how? I really appreciate your help!

EDIT 1

Some code generated by the constructor I am using a function.

mainwindows.cpp

void MainWindow::on_pushButton_24_clicked()
{
    Database db;
    bool b = db.createStructure();

    QMessageBox::information(this, "test", QString(b));
}

      

+3


source to share


1 answer


I'll stick with your source code for the explanation.


Disclaimer: I have not compiled any of my suggestions, forgive me if there are syntax errors.


First of all, you are probably looking for the Singleton Pattern (which I don't like anymore, but for your purpose, it could be argued that this can be considered appropriate):

Your class definition should contain the following:

class Database : public QObject
{
    Q_OBJECT

public:
    static Database* instance();
private:
    static Database* m_instance;
    Database();
    ~Database() {}; // it can be necessary to have this public in some cases, if 
                    // you ever get a linker error related to deletion, this is 
                    // probably the reason.

public: 

    // FUNCTIONS
    ...
};

      

And the following in your .cpp file:

// init singleton pointer to NULL
Database* Database::m_instance = NULL;

Database* Database::instance()
{
    if( !m_instance )
    {
        m_instance = new Database();
    }
    return m_instance;
}

      

Then you can access this singleton for example

if( Database::instance()->createStructure() )
{
    w.show();
}

      

What does it do? At the beginning of the program, the line



Database* Database::m_instance = NULL;

      

initializes your m_instance variable to NULL

. The first time Database::instance()

it is called, it realizes that m_instance is still NULL and creates a new object and points m_instance

to that object. From now on, a pointer to this object will always be returned, but no object will be created Database

.


In your function, createStructure()

you commit()

are using your database even when an error occurs. The usual procedure is commit()

on success and rollback()

on failure. Before fixing this, be sure to read the following point:


The third thing I would recommend is getting used to being suspicious when you see multiple occurrences of the same lines. This usually screams a subfunction.

I'm talking about

Database::close();
return false;

      

Take a look at how I rewrote your method createStructure()

, introducing a different method and leaving else{ }

it where it wasn't needed:

bool Database::createStructure()
{
    QStringList commands;

    commands.append("CREATE TABLE...;");
    commands.append("CREATE TABLE...;");
    commands.append("CREATE TABLE...;");

    if (!Database::open()) return false;

    // at this point you can be sure the database is open

    if (!Database::transaction())
    { 
        Database::close();
        return false;
    }

    // at this point you can be sure the database is open and a transaction was started

    if (!Database::executeCommands(commands))
    {
        // an error occurred - we need to rollback what we did so far
        Database::rollback();
        Database::close();
        return false;
    }

    // everything was executed properly, but the transaction is still active
    // => commit the changes we've made
    bool committed = Database::commit();

    // no matter if the commit was successful or not, close the database,
    // then return the result we've stored
    Database::close();
    return committed;
}

bool Database::executeCommands(const QStringList& commands)
{
    // This method simply executes the queries and is relieved from
    // transaction-related code.

    foreach(QString command, commands)
    {
        QSqlQuery query;
        bool prepared = query.prepare(command);

        if(!prepared) return false;

        if(!query.exec()) return false;        
    }
    return true;
}

      

This can be refactored further, it is just an example of how to make your code simpler and generally less error prone.

+1


source







All Articles