Возникла необходимость получить большой объем данных из базы данных для последующей работы пользователя с ними (Справочник товаров в определенном магазине).
За код сильно не пинайте. Программирование для меня это хобби в качестве зарядки для ума.
Запрос сам по себе выполняет достаточно долго. По этому делаем его в потоке, пользователю отображаем бесконечный ProgressBar с предложением "Откинутся на спинку стула и подождать." :-)
Данные получаемые в запросе храним в векторе объектов класса Articles.
Articles
Класс для хранения одной записи результирующего запроса. Для хранения всех записей используем вектор.
articles.h
- #ifndef ARTICLES_H
- #define ARTICLES_H
- #include <QString>
- class Articles
- {
- public:
- Articles();
- void setColParam(int _colParam);
- void setID(int _articleID);
- void setShortName(QString _shortName);
- void setAmount(float _amount);
- void setPrice(float _price);
- int getColParam();
- int getID();
- QString getShortName();
- float getAmount();
- float getPrice();
- private:
- int colParam; // Количетсво параметров. необходимое для последующего создания модели
- int artileID; // Код товара
- QString shortname; // Наименование
- float amount; // Количество
- float price; // Цена
- };
- #endif // ARTICLES_H
articles.cpp
- #include "articles.h"
- Articles::Articles()
- {
- setColParam(4);
- }
- void Articles::setColParam(int _colParam)
- {
- colParam = _colParam;
- }
- int Articles::getColParam()
- {
- return colParam;
- }
- void Articles::setID(int _articleID)
- {
- artileID = _articleID;
- }
- void Articles::setShortName(QString _shortName)
- {
- shortname = _shortName;
- }
- void Articles::setAmount(float _amount)
- {
- amount = _amount;
- }
- void Articles::setPrice(float _price)
- {
- price = _price;
- }
- int Articles::getID()
- {
- return artileID;
- }
- QString Articles::getShortName()
- {
- return shortname;
- }
- float Articles::getAmount()
- {
- return amount;
- }
- float Articles::getPrice()
- {
- return price;
- }
Для логирования и отладки использовал методы описанные в этой статье Логирование событий Qt приложения в текстовый файл .
ListArticles
Класс для осуществления выборки из базы данных
listarticles.h
- #ifndef LISTARTICLES_H
- #define LISTARTICLES_H
- #include "articles.h"
- #include <QObject>
- #include <QSqlQuery>
- #include <QSqlError>
- #include <QVector>
- #include <QSqlRecord>
- class ListArticles : public QObject
- {
- Q_OBJECT
- public:
- explicit ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent = nullptr);
- signals:
- // Сигнал дя отправки данных в основной поток для дальнейшей их обработки
- void signalSendArticlesList(QVector<Articles>);
- // Сигнал об окончании выборки данных
- void finish();
- public slots:
- // Слот для начала выбоки данных, вызываемый из основного потока
- void createListGoods();
- private:
- int m_terminalID; // Номер магазина, параметр для запроса
- int m_shiftID; // Номер смены, параметр для запроса
- QVector<Articles> goods; // Вектор в котором будем хранить результаты запроса и передадим в основной поток
- QSqlRecord connRec; // Запись выбранная из другой модели содержащщая параметры подключения к базе данных
- };
- #endif // LISTARTICLES_H
listarticles.cpp
- #include "listarticles.h"
- #include "loggingcategories.h"
- ListArticles::ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent) : QObject(parent)
- {
- // Получаем входные данные
- m_terminalID = terminal_D;
- m_shiftID = shift;
- connRec =rec;
- }
- void ListArticles::createListGoods()
- {
- // Создаем объект для хранения одной записи
- Articles ar;
- // регистрируем тип для передачи его через механизм сигнал слот.
- typedef QVector<Articles> vek;
- qRegisterMetaType<vek>("vektor");
- // Из Асистента
- // "Соединение может использоваться только внутри создавшего его потока. Перемещение соединений между потоками и создание запросов в другой поток не поддерживается.
- // Поэтому создаем новое соединение
- QSqlDatabase dbth = QSqlDatabase::addDatabase("QIBASE","thcentr");
- dbth.setHostName(connRec.value("conn_host").toString());
- dbth.setDatabaseName(connRec.value("conn_db").toString());
- dbth.setUserName(connRec.value("conn_user").toString());
- dbth.setPassword(connRec.value("conn_pass").toString());
- if(!dbth.open()) {
- qCritical(logCritical()) << Q_FUNC_INFO << "Не могу подключится к центральной базе"
- << endl << dbth.lastError().text();
- return;
- }
- // Привязыем запрос к соединению
- QSqlQuery q = QSqlQuery(dbth);
- // Формируем строку с запросом.
- // Такого типа запросы предпочитаю создавать ввиде строки и тестировать их в непосредственно в менеджере базы данных
- QString strSQL = QString("SELECT A.GARTICLE_ID, GA.SHORTNAME, SL.AMOUNT, "
- "(SELECT FIRST 1 NEWPRICE FROM HISTORY_PRICES HP "
- "WHERE HP.TERMINAL_ID = SL.TERMINAL_ID "
- "AND HP.GARTICLE_ID = A.GARTICLE_ID "
- "AND HP.DATEOP < SH.DATCLOSE "
- "ORDER BY HP.DATEOP DESC) AS PRICE "
- "FROM GET_ASALDOS (%1, %2, NULL, 0) AS SL "
- "INNER JOIN ARTICLES A ON A.TERMINAL_ID = SL.TERMINAL_ID AND A.ARTICLE_ID = SL.ARTICLE_ID "
- "LEFT JOIN SHIFTS SH ON SH.TERMINAL_ID = SL.TERMINAL_ID AND SH.SHIFT_ID = SL.SHIFT_ID "
- "LEFT JOIN GARTICLES GA ON GA.GARTICLE_ID = A.GARTICLE_ID "
- "WHERE SL.AMOUNT > 0 "
- "ORDER BY A.GARTICLE_ID" )
- .arg(m_terminalID)
- .arg(m_shiftID);
- // Выполняем запрос
- if(!q.exec(strSQL)) {
- qInfo(logInfo()) << "Errog goodlist" << q.lastError().text();
- emit finish();
- }
- // Цикл получения записей, и добавления их в вектор
- while (q.next()){
- ar.setID(q.value("GARTICLE_ID").toInt());
- ar.setShortName(q.value("SHORTNAME").toString().trimmed());
- ar.setAmount(q.value("AMOUNT").toFloat());
- ar.setPrice(q.value("PRICE").toFloat());
- goods.append(ar);
- }
- //Передаем результат в основной поток
- emit signalSendArticlesList(goods);
- //Поток закончил работу
- emit finish();
- }
Запуск потока выборки данных
- // Выбор данных происходит при переходе на определенную странцу QWizardPage
- void ArticlePage::initializePage()
- {
- // Создаем объект класса и передаем ему параметры
- ListArticles *lsArticles = new ListArticles(recrodConn, field("terminalID").toInt(),field("shiftID").toInt());
- // Создаем поток в которм будут производиться наша выборка
- QThread *thread = new QThread();
- // Перемещаем объект класса в поток
- lsArticles->moveToThread(thread);
- //// Сигналы и слоты для взаимидействия с потоком
- // при старте потока выполняем некоторые действия в текущем потоке.
- // В моем случае на просто засекаю начало выбоки данных
- connect(thread,&QThread::started,this,&ArticlePage::slotStartArticlesList);
- // При старте потока начинаем выборку данных
- connect(thread,&QThread::started,lsArticles,&ListArticles::createListGoods);
- // Передача результирующего объекта QVertor из дочернего потока в основной
- connect(lsArticles,&ListArticles::signalSendArticlesList,this,&ArticlePage::slotGetArticlesList,Qt::DirectConnection);
- // Окончание работы потока по завершению выбрки данных
- connect(lsArticles,&ListArticles::finish,thread,&QThread::quit);
- // Удаляем объект в потоке
- connect(lsArticles,&ListArticles::finish,lsArticles,&ListArticles::deleteLater);
- // Вы полняем действия по в основном потоке после завершения дочернего
- connect(lsArticles,&ListArticles::finish,this,&ArticlePage::slotFinishArticlesList);
- // Прощаемся с дочерним потоком
- connect(thread,&QThread::finished,thread,&QThread::deleteLater);
- // Запускаем поток
- thread->start();
- }
Слоты обмена данными с потоком
- void ArticlePage::slotGetArticlesList(QVector<Articles> ls)
- {
- //Получаем вектор с результами из потока
- goods = ls;
- }
- void ArticlePage::slotStartArticlesList()
- {
- qInfo(logInfo()) << "Начали получать список товаров" << QTime::currentTime().toString("hh:mm:ss.zzz");
- }
- void ArticlePage::slotFinishArticlesList()
- {
- // на основании полученного вектора с данными создаем модель данных типа VectorModel и выводим ее в QTableView
- qInfo(logInfo()) << "Закончили получать список товаров" << QTime::currentTime().toString("hh:mm:ss.zzz");
- ui->frameProgress->hide();
- ui->groupBoxAdd->show();
- modelArticles = new VektorModel(goods);
- ui->tableView->setModel(modelArticles);
- ui->tableView->verticalHeader()->hide();
- ui->tableView->setAlternatingRowColors(true);
- ui->tableView->resizeColumnsToContents();
- // Минимальная высота строк в QTableView
- ui->tableView->verticalHeader()->setDefaultSectionSize(ui->tableView->verticalHeader()->minimumSectionSize());
- }
VektorModel
Класс наследник от QAbstractTableModel создающий модель для отображения данных запроса.
vektormodel.h
- #ifndef VEKTORMODEL_H
- #define VEKTORMODEL_H
- #include "articles.h"
- #include <QObject>
- #include <QAbstractTableModel>
- #include <QVariant>
- class VektorModel : public QAbstractTableModel
- {
- Q_OBJECT
- QVector<Articles> ar;
- public:
- VektorModel(const QVector<Articles> vek);
- QVariant data(const QModelIndex &index, int role) const;
- QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
- int rowCount(const QModelIndex &parent) const;
- int columnCount(const QModelIndex &parent) const;
- };
- #endif // VEKTORMODEL_H
vectormodel.cpp
- #include "vektormodel.h"
- #include "articles.h"
- VektorModel::VektorModel(const QVector<Articles> vek)
- {
- ar = vek;
- }
- // отображение данных модели
- QVariant VektorModel::data(const QModelIndex &index, int role) const
- {
- if ( !index.isValid() ) { return QVariant(); }
- Articles a = ar[index.row()];
- switch (role) {
- case Qt::DisplayRole:
- switch (index.column()) {
- case 0: return a.getID(); // 1-й столбец ID
- case 1: return a.getShortName(); // 2-й столбец Наименование
- case 2: return a.getAmount(); // 3-й столбец Количетсво
- case 3: return QString::number(a.getPrice(),'f',2); // 4-й столбец Цена
- default: break;
- }
- break;
- // выравниваем 2-й и 3-й столбез по правому краю
- case Qt::TextAlignmentRole:
- if(index.column() == 2 || index.column() == 3 )
- return Qt::AlignRight;
- break;
- default:
- break;
- }
- return QVariant();
- }
- QVariant VektorModel::headerData(int section, Qt::Orientation orientation, int role) const
- {
- //Cоздаем заголовки столбцов модели
- if( role != Qt::DisplayRole ) {
- return QVariant();
- }
- if( orientation == Qt::Vertical ) {
- return section;
- }
- switch( section ) {
- case 0:
- return tr( "Гл.Код" );
- case 1:
- return tr( "Наименование" );
- case 2:
- return tr( "Доступно" );
- case 3:
- return tr( "Цена" );
- }
- return QVariant();
- }
- int VektorModel::rowCount(const QModelIndex &parent) const
- {
- // Количетсво строк
- return ar.size();
- }
- int VektorModel::columnCount(const QModelIndex &parent) const
- {
- // Количество столбцов
- Articles a;
- return a.getColParam();
- }
В результате получаем следующее:
В процессе отладки на реальных базах данных столкнулся с проблемой что перенос данных из объекта QSqlQuery в вектор выполняется достаточно медленно.
Проблема решилась замена названий в value на номера.
Добрый день, вывел в поток ресурсоемкую функцию, интерфейс работает не нарадуешься, но после завершения потока теряется соединение с базой данных в основоном, не могу сообразить куда смотреть. В потоке создается свое соединение с базой данных.
В потоке надо создавать свое соединение с БД с другим именем.
Если в:
QSQLDatabase db_thread = QSQLDatabase::addDatabase("MYSQL","db_new_name");
крашится после запуска сразу
Вы полностью создаете новое соединение?
И при создании объекта QSqlQuery или модели указываете алиас подключения?
Упс, видимо нет, буду проверять. У меня просто собраны функции работы с базой данных, и подключение новое, но в функция
Как пример
Перенес в класс потока все функции для работы с БД, но все по старому когда закрываю поток основное соединение тоже закрывается
Соединение в потоке используется только в потоке. Т.е выбирает данные сохраняете в какой-нибудь контейнер и передаете его основному потоку.
Я наверное слишком туп, можете пример привести как Вы это делаете?
Так эта статья как раз об этом.
Кстати сегодня почему-то все заработало :)
Луна стала в нужную фазу :-)