Возникла необходимость получить большой объем данных из базы данных для последующей работы пользователя с ними (Справочник товаров в определенном магазине).
За код сильно не пинайте. Программирование для меня это хобби в качестве зарядки для ума.
Запрос сам по себе выполняет достаточно долго. По этому делаем его в потоке, пользователю отображаем бесконечный 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 или модели указываете алиас подключения?
Упс, видимо нет, буду проверять. У меня просто собраны функции работы с базой данных, и подключение новое, но в функция
Как пример
Перенес в класс потока все функции для работы с БД, но все по старому когда закрываю поток основное соединение тоже закрывается
Соединение в потоке используется только в потоке. Т.е выбирает данные сохраняете в какой-нибудь контейнер и передаете его основному потоку.
Я наверное слишком туп, можете пример привести как Вы это делаете?
Так эта статья как раз об этом.
Кстати сегодня почему-то все заработало :)
Луна стала в нужную фазу :-)