Доброго времени суток, Хабровчане! В этой статье я хочу рассказать о своем опыте упрощения взаимодействия с базами данных SQL при разработке десктопного приложения с помощью класса QSqlRelationalTableModel кроссплатформенной библиотеки Qt.
С Qt я познакомился еще будучи студентом 1 курса, только начиная программировать на C++, тогда же и серьезно заинтересовался библиотекой и, с тех пор слежу за ее апдейтами. Несколько месяцев назад на работе мне дали ТЗ, в котором требовалось разработать приложение, взаимодействующее с БД SQLite. Структура базы фиксирована и заранее известна мне из ТЗ.
Приложение должно уметь удобно для оператора представлять данные, хранящиеся в базе, позволять добавлять новые записи, удалять и изменять уже существующие.
Далее я кратко опишу процесс разработки с приведением кусков кода и попытаюсь аргументированно объяснить, почему в данном случае был сделан выбор в пользу QSqlRelationalTableModel.
Изначально было принято решение основать взаимодейстие с бд с помощью простых запросов к базе, т.е. SELECT, INSERT, DELETE, которые позволяют реализовать все необходимые функции приложения.
Для этого нам потребуются классы QSqlDatabase и QSQlQuery:
После этого все операции над базой совершаются следующим образом:
Select statement'ы выполняются аналогично, за исключением того, что данные еще нужно получить и куда-то положить:
Delete-statement'ы выполняются в точности так же, как Insert из-за того, что ничего не возвращают.
И правда, ведь можно все реализовать через эти выражения и запросы, зачем нам модели?
Когда у нас есть одна ни с чем не связанная таблица, то все кажется очень простым и не требующим введения дополнительных инструментов. А теперь представьте, что у нас таких таблиц, например, 5, в каждой по 5 столбцов, не включая id. Причем каждая имеет связь с предыдущей с помощью foreign key через id, т.е. при удалении необходимо каскадно удалять все «дочерние» записи. Это приводит к огромному количеству запросов, работа приложения сильно замедляется, более того, необходимо каждый раз обновлять таблицу и ее представление в интерфейсе, что приводит к написанию дополнительных функций для обновления, появлению багов или к риску их возникновения, да и в целом к снижению читаемости кода.
Именно по этой причине в процессе разработки пришлось отказаться от концепции использования голых SQL запросов.
Дальнейший выбор был сделан в пользу QSqlRelationalTableModel в связке с QTableView. Есть еще более простая версия реализации модели — QSqlTableModel , первая наследована от нее, имеет все те же методы, но добавляет возможность создания связи QSqlRelation, что очень удобно, если пользователю нужно показать не id записи, а название записи «родителя», с которой она связана.
Приведу отрывки пода, показывающие реализацию model/view.
В заголовочном файле:
В конструкторе:
В строчке ниже заключается одна из самых удобных фишек и преимуществ модели перед sql запросами — она редактирует, добавляет, удаляет, в зависимости от контекста, данные в sql таблице при изменении из в QTableView. Удобство в том, что больше не нужно контролировать корректность каскадного удаления данных и их обновление в рамках одного QTableView.
Далее идет еще одна удобная фишка, предоставляемая этим классом: устанавливается связь между двумя колонками разных таблиц:
Далее все более стандартно: select() выполнит SELECT выражение, а setHeaderData() установит текст в заголовки QTableView:
Теперь модель и tableView работают вместе и выполняют свои функции. По ссылке на github будут оставлены все исходники, в них я реализовал добавление записи в модель, ее удаление, а также фильтры.
В этой статье я хотел призвать всех тех, кто уже работает с БД в Qt отказываться от голых sql запросов для проектов уже хотя бы среднего уровня сложности и переходить на работу с моделями, чтобы упростить себе жизнь, сделать код более читабельным и универсальным, ну и просто сделать что-то хорошее и новое.
На этом все! Надеюсь, что мой опыт работы с данными классами поможет читателям успешно решить схожую проблему!
Пролог
С Qt я познакомился еще будучи студентом 1 курса, только начиная программировать на C++, тогда же и серьезно заинтересовался библиотекой и, с тех пор слежу за ее апдейтами. Несколько месяцев назад на работе мне дали ТЗ, в котором требовалось разработать приложение, взаимодействующее с БД SQLite. Структура базы фиксирована и заранее известна мне из ТЗ.
Приложение должно уметь удобно для оператора представлять данные, хранящиеся в базе, позволять добавлять новые записи, удалять и изменять уже существующие.
Далее я кратко опишу процесс разработки с приведением кусков кода и попытаюсь аргументированно объяснить, почему в данном случае был сделан выбор в пользу QSqlRelationalTableModel.
Начало разработки
Изначально было принято решение основать взаимодейстие с бд с помощью простых запросов к базе, т.е. SELECT, INSERT, DELETE, которые позволяют реализовать все необходимые функции приложения.
Для этого нам потребуются классы QSqlDatabase и QSQlQuery:
QSqlDatabase db; //создаем объект нашей базы db = QSqlDatabase::addDatabase("QSQLITE"); //добавляем базу конкретного типа db.setHostName("localhost"); //в общем случае, может быть локалхостом db.setDatabaseName(path); //QString path - путь к базе //проверка корректности открытия if(db.open()){ qDebug() << "db opened OK..."; }else{ qDebug() << " db opening failed..."; } }else{ qDebug() << "file doesnot exist"; exit(0); //в случае, когда файл базы не существует, //просто завершим приложение }
После этого все операции над базой совершаются следующим образом:
//сформируем запрос, значение возьмем из <b>QLineEdit</b>'а QString query = "INSERT INTO Table (column) VALUES ('" + ui->Input->text() + "')"; QSqlQuery sqlQuery(db); //cсоздаем объект запроса qDebug() << "QUERY: " << query; //вывод //понятно без пояснений if(sqlQuery.exec(query)){ qDebug() << "query failed..."; } else{ qDebug() << "query failed..."; }
Select statement'ы выполняются аналогично, за исключением того, что данные еще нужно получить и куда-то положить:
QString query = "SELECT id, ka FROM Table"; QSqlQuery sqlQ(db); if(!sqlQ.exec(query)) { qDebug() << "query failed..."; return; } //отличия только в этой части //необходимо проверять, остались ли еще значения и получать их while (sqlQ.next()){ //получим значение и например добавим его в комбобокс со всеми значениями ui->ComboBox->addItem(sqlQ.value(1).toString(),sqlQ.value(0).toInt()); }
Delete-statement'ы выполняются в точности так же, как Insert из-за того, что ничего не возвращают.
Все хорошо, в чём проблема?
И правда, ведь можно все реализовать через эти выражения и запросы, зачем нам модели?
Когда у нас есть одна ни с чем не связанная таблица, то все кажется очень простым и не требующим введения дополнительных инструментов. А теперь представьте, что у нас таких таблиц, например, 5, в каждой по 5 столбцов, не включая id. Причем каждая имеет связь с предыдущей с помощью foreign key через id, т.е. при удалении необходимо каскадно удалять все «дочерние» записи. Это приводит к огромному количеству запросов, работа приложения сильно замедляется, более того, необходимо каждый раз обновлять таблицу и ее представление в интерфейсе, что приводит к написанию дополнительных функций для обновления, появлению багов или к риску их возникновения, да и в целом к снижению читаемости кода.
Именно по этой причине в процессе разработки пришлось отказаться от концепции использования голых SQL запросов.
Дальнейший выбор был сделан в пользу QSqlRelationalTableModel в связке с QTableView. Есть еще более простая версия реализации модели — QSqlTableModel , первая наследована от нее, имеет все те же методы, но добавляет возможность создания связи QSqlRelation, что очень удобно, если пользователю нужно показать не id записи, а название записи «родителя», с которой она связана.
Посмотрим уже на реализацию с моделями
Приведу отрывки пода, показывающие реализацию model/view.
В заголовочном файле:
QSqlRelationalTableModel *model;
В конструкторе:
//необходимо соединить сигнал, эмитируемый при нажатии //на ячейку QTableView со слотом, вызываемым этим сигналом //QModelIndex хранит в себе индекс ячейки, на которую было совершено нажатие connect(ui->tableView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(onTableClicked(const QModelIndex &))); model = new QSqlRelationalTableModel(parent, db); //создадим модель, QSqlDatabase в качестве параметра model->setTable("Table"); //свяжем модель с таблицей
В строчке ниже заключается одна из самых удобных фишек и преимуществ модели перед sql запросами — она редактирует, добавляет, удаляет, в зависимости от контекста, данные в sql таблице при изменении из в QTableView. Удобство в том, что больше не нужно контролировать корректность каскадного удаления данных и их обновление в рамках одного QTableView.
model->setEditStrategy(QSqlRelationalTableModel::OnFieldChange);
Далее идет еще одна удобная фишка, предоставляемая этим классом: устанавливается связь между двумя колонками разных таблиц:
//ParentTable - таблица, с колонкой которой устанавливаем соответствие //id - параметр, по которому будет осуществлен выбор и установлено соответствие //name - параметр, который будет отображаться в колонке нашей таблицы model->setRelation(1,QSqlRelation("ParentTable", "id", "name"));
Далее все более стандартно: select() выполнит SELECT выражение, а setHeaderData() установит текст в заголовки QTableView:
model->select(); model->setHeaderData(0, Qt::Horizontal, tr("id")); model->setHeaderData(1, Qt::Horizontal, tr("id_sub")); model->setHeaderData(2, Qt::Horizontal, tr("count")); model->setHeaderData(3, Qt::Horizontal, tr("number")); model->setHeaderData(4, Qt::Horizontal, tr("data_word")); model->setHeaderData(5, Qt::Horizontal, tr("time")); model->setHeaderData(6, Qt::Horizontal, tr("name")); model->setHeaderData(7, Qt::Horizontal, tr("description")); ui->tableView->setModel(model); //установим нашу модель на нужный QTableView
Теперь модель и tableView работают вместе и выполняют свои функции. По ссылке на github будут оставлены все исходники, в них я реализовал добавление записи в модель, ее удаление, а также фильтры.
Заключение
В этой статье я хотел призвать всех тех, кто уже работает с БД в Qt отказываться от голых sql запросов для проектов уже хотя бы среднего уровня сложности и переходить на работу с моделями, чтобы упростить себе жизнь, сделать код более читабельным и универсальным, ну и просто сделать что-то хорошее и новое.
На этом все! Надеюсь, что мой опыт работы с данными классами поможет читателям успешно решить схожую проблему!
