where should I declare a database object - c++

I am using the Qt libraries in a C++ project but I have a design question: where should a database be declared? I would prefer not to declare global variables.
Currently I am dealing with this problem in this way. I have a mainwindow and I have declared the DB in there so I perform the queries in the main window and pass the results to the dialogs using different signals and slots.
I start the DB when the main window starts and close it when the window has been closed. I don't know if this is ok
Now I need the DB connection in another class as well so I can pass a reference to the DB or make the DB global
I don't like these solutions.. is there a standard pattern to deal with this situation?
edit:
My class now looks like:
class Database
{
public:
bool open(void);
bool close(void);
static Database* getDatabase(void);
// various methods like loadThisTable(), saveThisTable() etc
private:
Database(); // disable constructor
~Database(); // disable destructor
Database(const Database&); // disable copy constructor
Database& operator=(const Database&); // disable assignment
static Database* instance_; // database instance
QSqlDatabase qtDB; // qt db database
}
If I want I can add the add and remove methods but I have a single DB instance.

If you're using QSqlDatabase, you don't really need to make it a global variable. Just set up the connection when you first start your application, then use the static QSqlDatabase methods to access the connection when you need it in different modules.
Example
QSqlDatabase db; // set up the default connection
// alternative: set up a named connection
// QSqlDatabase db("conn-name");
// set the connection params and open the connection
// ... later on
QSqlDatabase db = QSqlDatabase::database(); // retrieve the default connection
// alternative: retrieve the named connection
// QSqlDatabase db = QSqlDatabase::database("conn-name");
From the docs:
QSqlDatabase is a value class. Changes made to a database connection via one instance of QSqlDatabase will affect other instances of QSqlDatabase that represent the same connection. Use cloneDatabase() to create an independent database connection based on an existing one.
Note: If you're application is multi-threaded, you have to be careful to only use a connection in the thread in which it was created.

You need a singleton pattern. It's a global class which have only one instance. Someone calls it antipattern (and sometimes it is), but it is the best way to handle resources like database connections. And dont forget that you can use QSqlDatabase QSqlDatabase::database ( const QString & connectionName = QLatin1String( defaultConnection ), bool open = true ) [static] method to get QSqlDatabase instance by name (name can be set via QSqlDatabase QSqlDatabase::addDatabase ( QSqlDriver * driver, const QString & connectionName = QLatin1String( defaultConnection ) ) [static] method) to avoid creating singleton just for storing QSqlDatabase instances.

Related

QSqlTableModel: change database after constructing

I'm creating an application which uses QSqlDatabase and QSqlTableModel for inserting and retaining data from an SQLite database file.
The database instance is being created in MyApplication:
MyApplication.h
#include <QSqlDatabase>
// ...
class MyApplication
{
public:
// ....
private:
QSqlDatabase _database;
};
MyApplication.cpp
MyApplication::start()
{
// ...
_database = QSqlDatabase::database();
// ...
}
For data model handling I'm using an overloaded class of QSqlTableModel:
SqlContactModel.h
#include <QSqlTableModel>
class SqlContactModel : public QSqlTableModel
{
public:
// ...
void setDatabase(const QSqlDatabase& database) { _database = database };
private:
QSqlDatabase _database;
};
SqlContactModel overloads the typical methods such as data(), roleNames() etc.
My instance of SqlContactModel is used in QML, thus I'm creating the instance in my main.cpp as follows:
main.cpp
int main()
{
// ...
qmlRegisterType<SqlContactModel>("io.taibsu.qxmt", 1, 0, "SqlContactModel");
}
Now since it's being created via qmlRegisterType, I can't pass any parameters in the constructor to set a different database in the SqlContactModel.
I need to pass a different database to it since I'm using multiple different overloaded QSqlTableModel classes (e.g., the other class is called SqlConversationModel and shall use the same _database instance which is used in MyApplication).
Now there are two ways to solve this: either pass the database somehow to my SqlTableModel subclasses via QML or find another way to tell the TableModel to not use its own database instance but the one already there.
Now my questions are:
How can I use a single database in different table models?
Is there a way to pass parameters when constructing a class instance in QML?
The best way probably is to set up your database from a controller class in C++, give that controller a (read only & constant) properly contactsModel and expose the controller as a singleton to QML. This way you can setup the database from C++ (where your business logic belongs) while you can access your data from QML. There probably isn’t even a need to have that property be another type than QAbstractItemModel* so you keep the freedom to modify it to another model type if needed in the future without affecting your QML. Such decoupling is desirable.

Creating common database connection for sevaral forms using qt c++

I am creating a simple qt application to provide details and login to the application and retrieve details from the database.It has mainly 2 forms(MainWindow and Dialog) A DBconncetion class has been written to obtain a database connection!
I used the DBconnection class to log into application by giving details via the MainWindow form! but I don't know how to keep the connection that I opened in the MainWindow form and use it to retreive the data to the tableview in the Dialog form.
mycode is as follows
DBconnection.h (working successfully)
public:
QSqlDatabase mydb;
bool connOpen(QString uname,QString pword,QString ip,int port,QString dbname){
mydb=QSqlDatabase::addDatabase("QOCI","MyDB");
mydb.setUserName(uname);
mydb.setPassword(pword);
mydb.setHostName(ip);
mydb.setPort(port);
mydb.setDatabaseName(dbname);
mydb.open();
return true;
}
MainWindow.cpp (working successfully)
void MainWindow::on_pushButton_clicked()
{
DBconnection con;
if(con.connOpen(ui->lineEdit->text(),ui->lineEdit_2->text(),ui->lineEdit_3->text(),ui->lineEdit_4->text().toInt(),ui->lineEdit_5->text())){
Dialog dialog1;
dialog1.setModal(true);
dialog1.exec();
}
}
Dialog.cpp (not working)
void Dialog::on_pushButton_clicked()
{
QSqlQueryModel *modal = new QSqlQueryModel();
con.connOpen();
QSqlQuery* qry=new QSqlQuery(con.mydb);
qry->prepare("select NAME FROM TEST1");
qry->exec();
modal->setQuery(*qry);
ui->tableView->setModel(modal);
}
How can I adjust my code so that I can retrieve data to the tablewidget in Dialog form from the connection I made from the MainWindow form?
You could either pass a reference to the connection to your dialog, or make the connection static/global.
e.g.1
class Dialog()
{
DBconnection &con;
Dialog(DBconnection &con) : con(con) {};
};
Instead of a reference, you might also want to use a std::shared_ptr.
A nice way of making the connection global/static would be through the Service locator pattern. This pattern uses a central registry known as the "service locator", which on request returns the information necessary to perform a certain task.
You might also want to have a look into things related to "Dependency injection"

c++/Qt: How to get a reference to database in the MainWindow class?

In Qt-creator, I created SQLite database in a class called databaseManager, as follow:
QString DatabaseManager::open_db()
{
QSqlDatabase db;
QString path = "/Users/me/Documents/workspace/Muasaa/";
db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName(path+"Database v.1");
if (db.open()){
return "Database is created, open, and ready ...";
} else {
return db.lastError().text();
}
}
Then I define the following in the header file of the MainWindow class:
Public:
DatabaseManager *db_manager;
In the source file, I call it as follow:
db_manager->open_db();
which creates and open the database.
However, I would like to use a reference to same database to use it in many functions in the MainWindow source file. How can I do that ?!
Move the QSqlDatabase db variable into your class header, and add a method for getting it. As long as you create an instance of your databaseManager class and maintain a pointer to it in MainWindow you'll be able to retrieve it.
QSqlDatabase::database() is a static function that returns a QSqlDatabase.
If you have more than one database, you have to provide a connection name in addDatabase() and in database()
Maybe a design solution may help?
Whatever your goal is, consider turning your DatabaseManager class into a Singleton. This way you'll be able to use your manager in all the gui classes. Something like DatabaseManager::instance()->your_method()
Advantages:
You are sure the connections are managed the right way
Less oportunities for build problems
By the way, I'm not sure, but your program crash may be the result of using your db_manager pointer before it was initialized, in a slot maybe, or (more probable) it's an internal connection error. Have you checked the same connection attributes in a minimum possible example?

Returning a QSqlQuery result from a QThread

I am accessing a MySQL 5.6 database using Qt 5.3.1 SQL module. Currently I try to move some of that code from the main thread to a custom thread to allow the GUI thread to stay responsive during DB updates.
I understood that everything (including establishing of the connection) must be moved to the custom thread. I am using queued signals and slots to achieve this and it works properly.
However there is one thing I am not sure about: How can I return query results back to main thread? Of course I will use a signal for that. But what kind of object should I return in that signal?
Should I return the QSqlQuery? I suppose this will be dangerous since QSqlQuery is attached to the connection/database in some way.
Should I return a list of QSqlRecord objects taken from the query using record()? Unfortunately the documentation does not say a word if this is safe.
What is the right container/way to safely return the results?
If, for example, the database contained personal details, you could create a separate class, derived from QObject: -
class Person : public QObject
{
Q_OBJECT
public:
Person();
private:
QString m_firstName;
QString m_surname;
QString m_address
QDateTime m_dateOfBirth;
};
Then, having registered its metadata for using with signals and slots, retrieve the database record, populate the Person object and send it with signals and slots. The classes you create can then represent the tables in the database.
However, a much simpler method would be to use a QMap and emit a signal with that instead: -
QMap personMap;
personMap["name"] = sqlRecord.value().toString("name");
personMap["surname"] = sqlRecord.value().toString("surname");
personMap["address"] = sqlRecord.value().toString("address");
...etc
It may be a good idea to emit a function that takes a token and the map, where the token denotes the type of information that the map contains:-
emit RetrievedData("Person", personMap);
I would avoid sending the SqlRecord or anything to do with the underlying method of storing the data. It's always good to use loosely coupled classes. This way, you could decide to replace the database storage with another mechanism, without having to refactor all the other code.
----------- In response to comments ------------
Populate a map with the sql record. For simplicity, we assume all returned items are strings.
If record items are numbers, simply convert to string before storing in the map.
QMap PopulateMap(SQLRecord& sqlRecord)
{
QMap map;
for(int i=0; i<sqlRecord.count(); ++i)
{
map[sqlRecord.fieldName(i)] = sqlRecord.value(i).toString();
}
return map;
}

qt simultaneous MySQL queries in different threads = crash

I have a multithreaded Qt5 app written in C++ where each thread is writing to the same database.
I share the same database QSqlDatabase variable across threads, but each thread creates its own query. I am regularly getting crashes and I've captured this info at the time of a crash:
Driver error [QMYSQL3: Unable to store statement results]
Database error [Commands out of sync; you can't run this command now]
Type [2]
Number [2014]
First of all, can I make simultaneous MySQL queries in multiple threads? If so, do I need to protect SQL calls with a mutex? Or do I need to post more info....
UPDATE: I have a seperate DBManager thread which handles opening/closing the DB, and simple database writes. I did this because my DB often goes offline, and I don't want other threads to hang for up to 2 minutes while a db open fails. No I have more threads which do reports, and must retrieve substantial amounts of data from the db.
As noted below, sharing the db handle across threads is not permitted. So now perhaps this is more of a design question - what is the best way to handle this situation? I don't want each thread that does DB access to attempt it's own open and wait for 2 minutes in case the DB is offline
Read the documentation for the SQL module, in particular the threads section that states each connection can only be used within the thread that created it.
So adding a mutex is not good enough, you will have to either create a new connection object in each thread, or pass the required data to/from a dedicated thread that performs all DB queries.
I generally handle this problem by providing a factory method on my database manager class, something that looks similar to this (semi-pseudocode):
static QHash<QThread, QSqlDatabase> DatabaseManager::s_instances;
static QMutex DatabaseManager::s_databaseMutex;
QSqlDatabase DatabaseManager::database() {
QMutexLocker locker(s_databaseMutex);
QThread *thread = QThread::currentThread();
// if we have a connection for this thread, return it
if (s_instances.contains(thread))
return s_instances[thread];
}
// otherwise, create a new connection for this thread
QSqlDatabase connection =
QSqlDatabase::cloneDatabase(existingConnection, uniqueNameForNewInstanceOnThisThread);
// open the database connection
// initialize the database connection
s_instances.insert(thread, connection);
return connection;
}
As long as you make sure you create an initial connection (you can modify the factory to include that, or just create the initial connection in your DatabaseManager constructor), you should be able to replace most existing cases where you're using a QSqlDatabase with:
QSqlQuery query(DatabaseManager::database());
query.exec(<some sql>);
without worrying about which thread you're calling from. Hope that helps!
This is just mbroadst's excellent answer with minor changes. This code should be complete and directly compilable:
database_manager.h:
#ifndef DATABASEMANAGER_H
#define DATABASEMANAGER_H
#include <QMutex>
#include <QHash>
#include <QSqlDatabase>
class QThread;
class DatabaseManager
{
public:
static QSqlDatabase database(const QString& connectionName = QLatin1String(QSqlDatabase::defaultConnection));
private:
static QMutex s_databaseMutex;
static QHash<QThread*, QSqlDatabase> s_instances;
};
#endif // DATABASEMANAGER_H
database_manager.cpp:
#include "database_manager.h"
#include <QSqlDatabase>
#include <QMutexLocker>
#include <QThread>
#include <stdexcept>
QMutex DatabaseManager::s_databaseMutex;
QHash<QThread*, QHash<QString, QSqlDatabase>> DatabaseManager::s_instances;
QSqlDatabase DatabaseManager::database(const QString& connectionName)
{
QMutexLocker locker(&s_databaseMutex);
QThread *thread = QThread::currentThread();
// if we have a connection for this thread, return it
auto it_thread = s_instances.find(thread);
if (it_thread != s_instances.end()) {
auto it_conn = it_thread.value().find(connectionName);
if (it_conn != it_thread.value().end()) {
return it_conn.value();
}
}
// otherwise, create a new connection for this thread
QSqlDatabase connection = QSqlDatabase::cloneDatabase(
QSqlDatabase::database(connectionName),
QString("%1_%2").arg(connectionName).arg((int)thread));
// open the database connection
// initialize the database connection
if (!connection.open()) {
throw std::runtime_error("Unable to open the new database connection.");
}
s_instances[thread][connectionName] = connection;
return connection;
}
Usage: Instead of QSqlDatabase::database(), use DatabaseManager::database()
Edited 2016-07-01: fixed bug with multiple connection names