Ruslan Polupan
20 ноября 2018 г. 18:42

Qt - Выбор данных из базы данных QSqlQuery в потоке QThread и создание на их основе модели QAbstractTableModel

Возникла необходимость получить большой объем данных из базы данных для последующей работы пользователя с ними (Справочник товаров в определенном магазине).

За код сильно не пинайте. Программирование для меня это хобби в качестве зарядки для ума.

Запрос сам по себе выполняет достаточно долго. По этому  делаем его в потоке, пользователю отображаем бесконечный ProgressBar с предложением "Откинутся на спинку стула и подождать." :-)


Данные получаемые в запросе храним в векторе объектов класса Articles.

Articles

Класс для хранения одной записи результирующего запроса. Для хранения всех записей используем вектор.

articles.h

  1. #ifndef ARTICLES_H
  2. #define ARTICLES_H
  3.  
  4. #include <QString>
  5.  
  6. class Articles
  7. {
  8. public:
  9. Articles();
  10. void setColParam(int _colParam);
  11. void setID(int _articleID);
  12. void setShortName(QString _shortName);
  13. void setAmount(float _amount);
  14. void setPrice(float _price);
  15. int getColParam();
  16. int getID();
  17. QString getShortName();
  18. float getAmount();
  19. float getPrice();
  20. private:
  21. int colParam; // Количетсво параметров. необходимое для последующего создания модели
  22. int artileID; // Код товара
  23. QString shortname; // Наименование
  24. float amount; // Количество
  25. float price; // Цена
  26. };
  27.  
  28. #endif // ARTICLES_H

articles.cpp

  1. #include "articles.h"
  2.  
  3. Articles::Articles()
  4. {
  5. setColParam(4);
  6. }
  7.  
  8. void Articles::setColParam(int _colParam)
  9. {
  10. colParam = _colParam;
  11. }
  12.  
  13. int Articles::getColParam()
  14. {
  15. return colParam;
  16. }
  17.  
  18. void Articles::setID(int _articleID)
  19. {
  20. artileID = _articleID;
  21. }
  22.  
  23. void Articles::setShortName(QString _shortName)
  24. {
  25. shortname = _shortName;
  26. }
  27.  
  28. void Articles::setAmount(float _amount)
  29. {
  30. amount = _amount;
  31. }
  32.  
  33. void Articles::setPrice(float _price)
  34. {
  35. price = _price;
  36. }
  37.  
  38. int Articles::getID()
  39. {
  40. return artileID;
  41. }
  42.  
  43. QString Articles::getShortName()
  44. {
  45. return shortname;
  46. }
  47.  
  48. float Articles::getAmount()
  49. {
  50. return amount;
  51. }
  52. float Articles::getPrice()
  53. {
  54. return price;
  55. }

Для логирования и отладки использовал методы описанные в этой статье Логирование событий Qt приложения в текстовый файл .

ListArticles

Класс для осуществления выборки из базы данных

listarticles.h

  1. #ifndef LISTARTICLES_H
  2. #define LISTARTICLES_H
  3.  
  4. #include "articles.h"
  5.  
  6. #include <QObject>
  7. #include <QSqlQuery>
  8. #include <QSqlError>
  9. #include <QVector>
  10. #include <QSqlRecord>
  11.  
  12.  
  13. class ListArticles : public QObject
  14. {
  15. Q_OBJECT
  16. public:
  17. explicit ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent = nullptr);
  18.  
  19. signals:
  20. // Сигнал дя отправки данных в основной поток для дальнейшей их обработки
  21. void signalSendArticlesList(QVector<Articles>);
  22. // Сигнал об окончании выборки данных
  23. void finish();
  24.  
  25. public slots:
  26. // Слот для начала выбоки данных, вызываемый из основного потока
  27. void createListGoods();
  28.  
  29. private:
  30. int m_terminalID; // Номер магазина, параметр для запроса
  31. int m_shiftID; // Номер смены, параметр для запроса
  32. QVector<Articles> goods; // Вектор в котором будем хранить результаты запроса и передадим в основной поток
  33. QSqlRecord connRec; // Запись выбранная из другой модели содержащщая параметры подключения к базе данных
  34.  
  35. };
  36.  
  37. #endif // LISTARTICLES_H

listarticles.cpp

  1. #include "listarticles.h"
  2. #include "loggingcategories.h"
  3.  
  4. ListArticles::ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent) : QObject(parent)
  5. {
  6. // Получаем входные данные
  7. m_terminalID = terminal_D;
  8. m_shiftID = shift;
  9. connRec =rec;
  10. }
  11.  
  12. void ListArticles::createListGoods()
  13. {
  14. // Создаем объект для хранения одной записи
  15. Articles ar;
  16.  
  17. // регистрируем тип для передачи его через механизм сигнал слот.
  18. typedef QVector<Articles> vek;
  19. qRegisterMetaType<vek>("vektor");
  20.  
  21. // Из Асистента
  22. // "Соединение может использоваться только внутри создавшего его потока. Перемещение соединений между потоками и создание запросов в другой поток не поддерживается.
  23. // Поэтому создаем новое соединение
  24.  
  25. QSqlDatabase dbth = QSqlDatabase::addDatabase("QIBASE","thcentr");
  26.  
  27. dbth.setHostName(connRec.value("conn_host").toString());
  28. dbth.setDatabaseName(connRec.value("conn_db").toString());
  29. dbth.setUserName(connRec.value("conn_user").toString());
  30. dbth.setPassword(connRec.value("conn_pass").toString());
  31.  
  32.  
  33. if(!dbth.open()) {
  34. qCritical(logCritical()) << Q_FUNC_INFO << "Не могу подключится к центральной базе"
  35. << endl << dbth.lastError().text();
  36. return;
  37. }
  38. // Привязыем запрос к соединению
  39. QSqlQuery q = QSqlQuery(dbth);
  40.  
  41. // Формируем строку с запросом.
  42. // Такого типа запросы предпочитаю создавать ввиде строки и тестировать их в непосредственно в менеджере базы данных
  43.  
  44. QString strSQL = QString("SELECT A.GARTICLE_ID, GA.SHORTNAME, SL.AMOUNT, "
  45. "(SELECT FIRST 1 NEWPRICE FROM HISTORY_PRICES HP "
  46. "WHERE HP.TERMINAL_ID = SL.TERMINAL_ID "
  47. "AND HP.GARTICLE_ID = A.GARTICLE_ID "
  48. "AND HP.DATEOP < SH.DATCLOSE "
  49. "ORDER BY HP.DATEOP DESC) AS PRICE "
  50. "FROM GET_ASALDOS (%1, %2, NULL, 0) AS SL "
  51. "INNER JOIN ARTICLES A ON A.TERMINAL_ID = SL.TERMINAL_ID AND A.ARTICLE_ID = SL.ARTICLE_ID "
  52. "LEFT JOIN SHIFTS SH ON SH.TERMINAL_ID = SL.TERMINAL_ID AND SH.SHIFT_ID = SL.SHIFT_ID "
  53. "LEFT JOIN GARTICLES GA ON GA.GARTICLE_ID = A.GARTICLE_ID "
  54. "WHERE SL.AMOUNT > 0 "
  55. "ORDER BY A.GARTICLE_ID" )
  56. .arg(m_terminalID)
  57. .arg(m_shiftID);
  58. // Выполняем запрос
  59. if(!q.exec(strSQL)) {
  60. qInfo(logInfo()) << "Errog goodlist" << q.lastError().text();
  61. emit finish();
  62. }
  63. // Цикл получения записей, и добавления их в вектор
  64. while (q.next()){
  65. ar.setID(q.value("GARTICLE_ID").toInt());
  66. ar.setShortName(q.value("SHORTNAME").toString().trimmed());
  67. ar.setAmount(q.value("AMOUNT").toFloat());
  68. ar.setPrice(q.value("PRICE").toFloat());
  69. goods.append(ar);
  70. }
  71. //Передаем результат в основной поток
  72. emit signalSendArticlesList(goods);
  73. //Поток закончил работу
  74. emit finish();
  75. }

Запуск потока выборки данных

  1. // Выбор данных происходит при переходе на определенную странцу QWizardPage
  2. void ArticlePage::initializePage()
  3. {
  4. // Создаем объект класса и передаем ему параметры
  5. ListArticles *lsArticles = new ListArticles(recrodConn, field("terminalID").toInt(),field("shiftID").toInt());
  6. // Создаем поток в которм будут производиться наша выборка
  7. QThread *thread = new QThread();
  8. // Перемещаем объект класса в поток
  9. lsArticles->moveToThread(thread);
  10.  
  11. //// Сигналы и слоты для взаимидействия с потоком
  12.  
  13. // при старте потока выполняем некоторые действия в текущем потоке.
  14. // В моем случае на просто засекаю начало выбоки данных
  15. connect(thread,&QThread::started,this,&ArticlePage::slotStartArticlesList);
  16. // При старте потока начинаем выборку данных
  17. connect(thread,&QThread::started,lsArticles,&ListArticles::createListGoods);
  18.  
  19. // Передача результирующего объекта QVertor из дочернего потока в основной
  20. connect(lsArticles,&ListArticles::signalSendArticlesList,this,&ArticlePage::slotGetArticlesList,Qt::DirectConnection);
  21. // Окончание работы потока по завершению выбрки данных
  22. connect(lsArticles,&ListArticles::finish,thread,&QThread::quit);
  23. // Удаляем объект в потоке
  24. connect(lsArticles,&ListArticles::finish,lsArticles,&ListArticles::deleteLater);
  25. // Вы полняем действия по в основном потоке после завершения дочернего
  26. connect(lsArticles,&ListArticles::finish,this,&ArticlePage::slotFinishArticlesList);
  27. // Прощаемся с дочерним потоком
  28. connect(thread,&QThread::finished,thread,&QThread::deleteLater);
  29.  
  30. // Запускаем поток
  31. thread->start();
  32. }

Слоты обмена данными с потоком

  1. void ArticlePage::slotGetArticlesList(QVector<Articles> ls)
  2. {
  3. //Получаем вектор с результами из потока
  4. goods = ls;
  5. }
  6.  
  7. void ArticlePage::slotStartArticlesList()
  8. {
  9. qInfo(logInfo()) << "Начали получать список товаров" << QTime::currentTime().toString("hh:mm:ss.zzz");
  10.  
  11. }
  12.  
  13. void ArticlePage::slotFinishArticlesList()
  14. {
  15. // на основании полученного вектора с данными создаем модель данных типа VectorModel и выводим ее в QTableView
  16.  
  17. qInfo(logInfo()) << "Закончили получать список товаров" << QTime::currentTime().toString("hh:mm:ss.zzz");
  18. ui->frameProgress->hide();
  19. ui->groupBoxAdd->show();
  20.  
  21. modelArticles = new VektorModel(goods);
  22.  
  23. ui->tableView->setModel(modelArticles);
  24. ui->tableView->verticalHeader()->hide();
  25. ui->tableView->setAlternatingRowColors(true);
  26. ui->tableView->resizeColumnsToContents();
  27. // Минимальная высота строк в QTableView
  28. ui->tableView->verticalHeader()->setDefaultSectionSize(ui->tableView->verticalHeader()->minimumSectionSize());
  29.  
  30. }

VektorModel

Класс наследник от QAbstractTableModel создающий модель для отображения данных запроса.

vektormodel.h

  1. #ifndef VEKTORMODEL_H
  2. #define VEKTORMODEL_H
  3.  
  4. #include "articles.h"
  5. #include <QObject>
  6. #include <QAbstractTableModel>
  7. #include <QVariant>
  8.  
  9.  
  10.  
  11. class VektorModel : public QAbstractTableModel
  12. {
  13. Q_OBJECT
  14. QVector<Articles> ar;
  15.  
  16. public:
  17. VektorModel(const QVector<Articles> vek);
  18. QVariant data(const QModelIndex &index, int role) const;
  19. QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
  20. int rowCount(const QModelIndex &parent) const;
  21. int columnCount(const QModelIndex &parent) const;
  22. };
  23.  
  24. #endif // VEKTORMODEL_H

vectormodel.cpp

  1. #include "vektormodel.h"
  2. #include "articles.h"
  3.  
  4. VektorModel::VektorModel(const QVector<Articles> vek)
  5. {
  6. ar = vek;
  7. }
  8. // отображение данных модели
  9. QVariant VektorModel::data(const QModelIndex &index, int role) const
  10. {
  11. if ( !index.isValid() ) { return QVariant(); }
  12. Articles a = ar[index.row()];
  13. switch (role) {
  14. case Qt::DisplayRole:
  15. switch (index.column()) {
  16. case 0: return a.getID(); // 1-й столбец ID
  17. case 1: return a.getShortName(); // 2-й столбец Наименование
  18. case 2: return a.getAmount(); // 3-й столбец Количетсво
  19. case 3: return QString::number(a.getPrice(),'f',2); // 4-й столбец Цена
  20. default: break;
  21. }
  22. break;
  23.  
  24. // выравниваем 2-й и 3-й столбез по правому краю
  25. case Qt::TextAlignmentRole:
  26. if(index.column() == 2 || index.column() == 3 )
  27. return Qt::AlignRight;
  28. break;
  29. default:
  30. break;
  31. }
  32.  
  33. return QVariant();
  34.  
  35. }
  36.  
  37. QVariant VektorModel::headerData(int section, Qt::Orientation orientation, int role) const
  38. {
  39. //Cоздаем заголовки столбцов модели
  40. if( role != Qt::DisplayRole ) {
  41. return QVariant();
  42. }
  43.  
  44. if( orientation == Qt::Vertical ) {
  45. return section;
  46. }
  47.  
  48. switch( section ) {
  49. case 0:
  50. return tr( "Гл.Код" );
  51. case 1:
  52. return tr( "Наименование" );
  53. case 2:
  54. return tr( "Доступно" );
  55. case 3:
  56. return tr( "Цена" );
  57. }
  58.  
  59. return QVariant();
  60. }
  61.  
  62. int VektorModel::rowCount(const QModelIndex &parent) const
  63. {
  64. // Количетсво строк
  65. return ar.size();
  66. }
  67.  
  68. int VektorModel::columnCount(const QModelIndex &parent) const
  69. {
  70. // Количество столбцов
  71. Articles a;
  72. return a.getColParam();
  73. }

В результате получаем следующее:

По статье задано0вопрос(ов)

3

Вам это нравится? Поделитесь в социальных сетях!

Ruslan Polupan
  • 27 ноября 2018 г. 17:36

В процессе отладки на реальных базах данных столкнулся с проблемой что перенос данных из объекта QSqlQuery в вектор выполняется достаточно медленно.

  1. // Цикл получения записей, и добавления их в вектор
  2. while (q.next()){
  3. ar.setID(q.value("GARTICLE_ID").toInt());
  4. ar.setShortName(q.value("SHORTNAME").toString().trimmed());
  5. ar.setAmount(q.value("AMOUNT").toFloat());
  6. ar.setPrice(q.value("PRICE").toFloat());
  7. goods.append(ar);
  8. }

Проблема решилась замена названий в value на номера.

  1. while (q.next()){
  2. ar.setID(q.value(0).toInt());
  3. ar.setShortName(q.value(1).toString().trimmed());
  4. ar.setAmount(q.value(2).toFloat());
  5. ar.setPrice(q.value(3).toFloat());
  6. goods << ar;
  7. i++;
  8. }



s
  • 29 ноября 2020 г. 15:27
  • (ред.)

Добрый день, вывел в поток ресурсоемкую функцию, интерфейс работает не нарадуешься, но после завершения потока теряется соединение с базой данных в основоном, не могу сообразить куда смотреть. В потоке создается свое соединение с базой данных.

Ruslan Polupan
  • 30 ноября 2020 г. 13:32

В потоке надо создавать свое соединение с БД с другим именем.

s
  • 30 ноября 2020 г. 22:40

Если в:
QSQLDatabase db_thread = QSQLDatabase::addDatabase("MYSQL","db_new_name");

крашится после запуска сразу

Ruslan Polupan
  • 1 декабря 2020 г. 13:43

Вы полностью создаете новое соединение?
И при создании объекта QSqlQuery или модели указываете алиас подключения?

s
  • 2 декабря 2020 г. 0:21

Упс, видимо нет, буду проверять. У меня просто собраны функции работы с базой данных, и подключение новое, но в функция

  1. #ifndef FUNCTIONS_H
  2. #define FUNCTIONS_H
  3.  
  4. #include <QTextCodec>
  5. #include <QDesktopServices>
  6. #include <QUrl>
  7. #include <QAxObject>
  8. #include <QDebug>
  9. #include <QFile>
  10. #include <QMessageBox>
  11. #include <QSqlDatabase>
  12. #include <QSqlQuery>
  13. #include <QSqlRecord>
  14. #include <QSqlError>
  15. #include <QVariant>
  16.  
  17. extern QStringList gsf_GetRecoredsetFields(const QString & m_sql);
  18. extern QList<QStringList> gsf_GetRecoredsetData(const QString & m_sql);
  19. extern QString gsf_GetRecordsetValueString(const QString & m_sql);
  20. extern QByteArray gsf_GetRecordsetBinary(const QString & m_sql);
  21. extern bool gsf_ExecuteSQL(const QString & m_sql);
  22. extern bool gsf_RecordIsValid(const QString & m_sql);
  23.  
  24. #endif // FUNCTIONS_H
  1. bool gsf_ExecuteSQL(const QString & m_sql)
  2. {
  3. QSqlQuery query(m_sql);
  4.  
  5. if(!query.exec()){
  6. QString m_message = QString("Запрос завершился с ошибкой: %1, обратитесь к Администратору.")
  7. .arg(query.lastError().text());
  8.  
  9. QMessageBox::warning(nullptr, "Ошибка!", m_message);
  10.  
  11. return false;
  12. }
  13.  
  14. return true;
  15. }

Как пример

s
  • 3 декабря 2020 г. 3:10

Перенес в класс потока все функции для работы с БД, но все по старому когда закрываю поток основное соединение тоже закрывается

Ruslan Polupan
  • 3 декабря 2020 г. 12:03

Соединение в потоке используется только в потоке. Т.е выбирает данные сохраняете в какой-нибудь контейнер и передаете его основному потоку.

s
  • 4 декабря 2020 г. 1:13

Я наверное слишком туп, можете пример привести как Вы это делаете?

Ruslan Polupan
  • 4 декабря 2020 г. 20:27

Так эта статья как раз об этом.

s
  • 4 декабря 2020 г. 21:11

Кстати сегодня почему-то все заработало :)

Ruslan Polupan
  • 7 декабря 2020 г. 12:43

Луна стала в нужную фазу :-)

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь