Версия для печати темы
Форум на CrossPlatform.RU _ Qt Разработка баз данных _ QSqlDatabase в отдельном потоке
Автор: Sterr 12.4.2009, 12:51
Есть gui приложение работающее с базой. Пытаюсь сделать так, чтобы вся работа с базой была выделена в отдельном потоке. После того как поток запущен ему будет передаваться через сигнал-слот указатель на класс Command, который наследуется остальными классами инкапсулирующими специфику команды. Набросал макет:
CODE
class Database : public QThread
{
private:
QSqlDatabase *db;
public:
Database();
~Database();
void run();
void initDatabase(QString user, QString pass);
public slots:
void sqlExec(Command *);
};
CODE
Database::Database()
{
}
void Database::run()
{
db =new QSqlDatabase::addDatabase("QPSQL");
db->setHostName("192.168.1.2");
db->setDatabaseName("dreamservice");
db->setUserName("me");
db->setPassword("123456");
if(!db->open())
{
qDebug() << "Error opening bd";
}
this->exec();
}
void Database::sqlExec(Command *command)
{
if(!command->start(db))
{
}
}
Database::~Database()
{
}
CODE
class Command : public QObject
{
Q_OBJECT
public:
Command();
~Command();
virtual bool startCommand(QSqlDatabase *db);
};
class CommandRequestList : public Command
{
Q_OBJECT
private:
QSqlQuery *sqlQuery;
public:
CommandRequestList();
~CommandRequestList();
void setAttributes(int id);
bool startCommand(QSqlDatabase *db);
};
CommandRequestList::CommandRequestList()
{
}
CommandRequestList::~CommandRequestList()
{
}
bool CommandRequestList::startCommand(QSqlDatabase *db)
{
if (db != 0)
{
sqlQuery = new QSqlQuery(*db);
QString query = "SELECT request.id_request, cl.company_name, agr.arg_service, request.status, request.tdate_create FROM repair_request request, agreement agr, client cl WHERE (request.id_agreement = agr.id_agr) AND (agr.id_client = cl.id_client)";
if (!sqlQuery->exec(query))
{
QSqlError qwe = sqlQuery->lastError();
}
delete sqlQuery;
}
return true;
}
Вопросов 2:
1) db =new QSqlDatabase::addDatabase("QPSQL"); не работает =(( Я так понимаю что надо или разименование, или создавать объект? Но ведь для того чтобы он выполнялся в отдельном потоке он должен создаваться в секции run ??
2) Реально ли реализовать то что здесь описанно, в смысле.. правильном ли путём иду? Тоесть ещё раз если в кратце:
два потока - главный gui и поток работы с бд. Например при щелчке по форме с поиском вызывается слот который задаёт в соответствующий класс наследованный от Command нужные значения и высылает сигнал с указателем на этот класс. Этот сигнал соединяется со слотом из потока по работе бд sqlExex, в котором потом вызывается Command.startCommand(QSqlDatabase *db) внутри которого создаётся QSqlQuery и идёт вся работа запроса с базой.
Автор: Litkevich Yuriy 12.4.2009, 13:05
Цитата(Гость_Sterr_* @ 12.4.2009, 16:51)
db =new QSqlDatabase::addDatabase("QPSQL");
а откуда вообще такая запись взялась (оператор
new)? http://doc.crossplatform.ru/qt/4.5.0/qsqldatabase.html#details, как использовать имеющееся.
Цитата(Гость_Sterr_* @ 12.4.2009, 16:51)
Реально ли реализовать то что здесь описанно,
да, оговорки смотри здесь http://doc.crossplatform.ru/qt/4.5.0/threads.html#threads-and-the-sql-module
Автор: Sterr 12.4.2009, 13:08
Цитата
db = new QSqlDatabase();
db->addDatabase("QPSQL");
что-то я туплю с утра чуток=( не знаю почему меня addDatabase смутил..
А как быть в остальном на ваш взгляд?
Автор: Sterr 12.4.2009, 13:12
Фразу
"Соединение может использоваться только внутри создавшего его потока. Перемещение соединений между потоками и создание запросов в другой поток не поддерживается.
Кроме того, используемые драйверами QSqlDriver сторонние библиотеки могут наложить дополнительные ограничения на использование модуля SQL в многопоточной программе. За дополнительной информацией обращайтесь к руководству по клиенту вашей базы данных."
Я читал но не совсем понимаю.. Набросок команды
bool CommandRequestList::startCommand(QSqlDatabase *db)
{
if (db != 0)
{
sqlQuery = new QSqlQuery(*db);
QString query = "SELECT request.id_request, cl.company_name, agr.arg_service, request.status, request.tdate_create FROM repair_request request, agreement agr, client cl WHERE (request.id_agreement = agr.id_agr) AND (agr.id_client = cl.id_client)";
if (!sqlQuery->exec(query))
{
QSqlError qwe = sqlQuery->lastError();
}
delete sqlQuery;
}
return true;
}
Ведь вызывается в потоке бд и следовательно работать должен...
Вообще я хотел именно спросить совета в плане проектного решения будет ли это работать и есть ли хорошие альтернативы..
Автор: Litkevich Yuriy 12.4.2009, 13:21
QSqlDatabase::addDatabase("QPSQL"); не возвращает ссылку.
В потоке создаешь соединение:
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL");
и затем это соединение используешь в этом же потоке.
чтобы его использовать в другой функции этого же потока делаешь так:
QSqlDatabase db = QSqlDatabase::database();
QSqlQuery q(db);
// пишешь свои запросы
Автор: Sterr 12.4.2009, 13:36
Если правильно вас понял то в run() создаю соединение QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL"); Как я понимаю даже после того как объект db уничтожен всё равно можно получить соединение бд в вспомогательной функции через QSqlDatabase db = QSqlDatabase::database(); ?
Спасибо!
Автор: Litkevich Yuriy 12.4.2009, 14:25
Цитата(Гость_Sterr_* @ 12.4.2009, 17:36)
Как я понимаю даже после того как объект db уничтожен всё равно можно получить соединение бд в вспомогательной функции через QSqlDatabase db = QSqlDatabase::database(); ?
да. Правильнее этот класс было бы назвать QSqlConnection, т.к. описывает не БД а соединение с ней. Соединений в программе может быть несколько, в том числе и с одной БД.
При вызове статической функции QSqlDatabase::addDatabase("QPSQL"); в нутри этого класса создается соединение.
Этот класс необычный (ты невызываешь конструктор для создания объекта, а статическую функцию). Он подобен "Одиночке" (шаблон проектирования).
Этот вариант называется соеденение по умолчанию (без указания имени). А еще можно задать имя соединения. подробности в описании класса (у нас на сайте на русском языке)
П.С. наиболее употребительные классы модуля SQL создаются как обычные переменные, а не как указатели. И менно благодаря тому, что к соединению можно получить доступ из любого места программы, но незабыв про оговорку насчет потоков.
Если нужно получать доступ к одной и тойже БД из разных потоков, можно просто в каждом потоке создать по соединению
Автор: defnull 18.4.2009, 21:12
Добрый день. Столкнулся с такой проблемой.. Есть поток DatabaseThread и класс Database, в котором находится QSqlDatabase. Для того чтобы работа шла параллельно Database создаётся именно в секции run и соотвественно до этого имеем просто указатель на Database. Но возникает проблема в том, что я не могу соединить слоты Database с объектами вне DatabaseThread до тех пор, пока не сработает метод run.. в результате получаю геморой в создании лишних слотов и сигналов для DatabaseThread. Как можно разрулить ситуацию? или проще вообще не заморачиваться над дополнительным потоком с базой ? (проект учебный в принципе, но хотелось быделать по уму)
з.ы. были мысли по поводу соединения сигнала с сигналом, но всё равно надо делать какую-то проверку на то что Database уже выделена память.
Автор: Litkevich Yuriy 18.4.2009, 21:27
Цитата(defnull @ 19.4.2009, 1:12)
Есть поток DatabaseThread и класс Database, в котором находится QSqlDatabase.
а для чего они так разделены? Почему не одним классом?
Автор: defnull 18.4.2009, 21:38
Цитата(Litkevich Yuriy @ 18.4.2009, 22:27)
Цитата(defnull @ 19.4.2009, 1:12)
Есть поток DatabaseThread и класс Database, в котором находится QSqlDatabase.
а для чего они так разделены? Почему не одним классом?
По причине того, что надо обрабатывать в потоке классы Command, в которых содержиться команда SQL и происходит запись и обработка данных. Таким образом Database исключительно обрабатывает ошибки с которыми не может справиться Command (переподключение к БД например) Можно было бы конечно QSqlDatabase наследовать классом Database, но всё равно остаётся проблемы, так как чтобы передавать этот класс Command от например Gui класса классу Database приходится идти через поток. =(
з.ы. Sterr и есть я, просто не со своего компа всё делал..
Автор: Litkevich Yuriy 18.4.2009, 21:43
defnull, я к тому, что может быть лучше утяжелить класс потока и избавится от дополнительного класса Database.
Я структуру программы постичь немогу, что и как взамодействует.
Автор: padla 21.4.2009, 17:52
Небольшой вопрос. А что произойдет если во время работы программы соединение с БД разорвется?
Автор: SABROG 21.4.2009, 21:59
Цитата(padla @ 21.4.2009, 18:52)
Небольшой вопрос. А что произойдет если во время работы программы соединение с БД разорвется?
Это риторический вопрос или он адресован к конкретной реализации взаимодействия с базой данных через разные потоки?
Если первое, то зависит от того написал ли ты проверку на возвращение ошибок при работе с запросами. Если не написал, то возможен любой исход от продолжения нормальной работы программы до AV. А вот с поделками на Delphi обычно это заканчивается выведением окна (иногда бесконечное их создание) о разрыве соединения без дальнейшей возможности к переподключению до перезагрузки программы.
Если вопрос с подтекстом типа, что будет если в этот момент передавались данные, то обычно происходит откат и это никак не влияет на целостность базы данных. Но она должна поддерживать команды BEGIN TRANSACTION ... END TRANSACTION.
Автор: defnull 21.4.2009, 23:55
Цитата
defnull, я к тому, что может быть лучше утяжелить класс потока и избавится от дополнительного класса Database.
Я структуру программы постичь немогу, что и как взамодействует.
Честно говоря я не понимаю как вы предлагаете утяжелить поток. Структура предполагалась следующая. Есть поток например DatabaseThread который должен обрабатывать всё что связанно с БД. Есть класс Command в котором содержиться sql запрос и работа с этим запросом. Есть главное окно программы пусть будет Window. Планировалось сделать следующее: есть интерфейс который поддерживают все классы наследующие Command. Когда мы хотим обратиться к бд мы вызываем сигнал который содержит указательна соответствующую команду унаследованную от Command. Сигнал передаётся потоку и далее функция Command назовём её например start работает уже в потоке где создавалось подключение к БД. ТОесть идея в том что всё что связанно с БД обрабатывается в потоке отвечающим за БД. Как это сделать утяжелив исключительно DataThread я не знаю. Поэтому и решил что нужен дополнительный класс Database который создаётся в секции run() потока DataThread и соединяется потом с кем нужно дял выполнения команд вида Command и отсылания сигналов об ошибках.
Можете привести пример как можно это на ваш взгляд упростить??
з.з.ы чот пытался процитировать а похожу вместо этого ставил + и - за сообщение %)
Автор: padla 22.4.2009, 17:02
То-есть мне придется ручками опять устанавливать соединение?
Форум Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)