Qt - tworzenie połączenia z bazą danych i wysyłanie zapytań SQL do serwera

Stronę tą wyświetlono już: 25 razy

Wstęp

Do tworzenia połączenia z bazą danych SQL w Qt służy klasa QSqlDatabase. Niestety połączenie z bazą danych uruchomioną na lokalnym serwerze lub gdzieś na serwerze, do którego dostęp jest umożliwiony poprzez sieć lokalną czy też globalną wymaga odpowiedniego sterownika. W przypadku MySQL sterownik ten nie jest domyślnie dostępny. Oznacza to, że aby możliwe było realizowanie połączenia z bazą danych MySQL trzeba ściągnąć z strony dev.mysql.com/downloads/connector/c/ odpowiednią paczkę ze sterownikami zwracając uwagę na wersję Qt Creator-a zainstalowaną na komputerze, gdyż dla wersji 32-bitowej jest inna paczka a dla 64-bitowej inna. W moim przypadku pobrałem wersję 32-bitową i ją rozpakowałem a następnie plik libmysql.dll znajdujący się w folderze lib rozpakowanych plików należy przekopiować do folderu kompilatora, który w moim przypadku znajduje się w:

Listing 1
  1. sciezka_do_folderu_z_Qt/Qt/Qt5.6.3/5.6.3/mingw49_32/bin

To jednak nie wszystko, albowiem zaprawdę powiadam wam, że konieczne jest jeszcze w pliku .pro dodanie ścieżki do plików nagłówkowych z rozpakowanej paczki:

Listing 2
  1. INCLUDEPATH += sciezka_do_folderu_z_paczką/mysql-connector-c-6.1.11-win32/include

Skoro już jestem w pliku .pro to dodaję jeszcze następującą linijkę:

Listing 3
  1. QT += sql

dzięki której będzie możliwy dostęp do plików nagłówkowych związanych z obsługą baz danych SQL.

Tworzenie w MySQL przykładową bazę danych do testów

Do testów konieczne jest utworzenie bazy danych oraz jakiegoś testowego użytkownika, który ma dostęp i pełne uprawnienia jedynie do tej bazy danych. Robię to w konsoli systemowej logując się w następujący sposób:

Listing 4
  1. mysql -u root -p

Parametr -u oznacza, że wprowadzam nazwę użytkownika, zaś -p, że chcę podać hasło. Po zalogowaniu tworzę bazę danych i użytkownika oraz jakąś prostą tabelkę z jednym rekordem za pomocą następującego kody SQL:

Listing 5
  1. CREATE DATABASE library CHARACTER SET utf8 COLLATE utf8_general_ci
  2. CREATE USER gienek@localhost IDENTIFIED BY 'password';
  3. GRANT ALL ON library.* TO gienek@localhost;
  4. USE library;
  5. CREATE TABLE books (
  6. id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  7. title CHAR(255) NOT NULL,
  8. author CHAR(255) NOT NULL
  9. );
  10. INSERT INTO books (title, author)
  11. VALUES
  12. ('Rio Anaconda', 'Wojciech Cejrowski');

W pierwszej linijce powyższego kodu tworzę bazę danych o nazwie library, w której kodowanie znaków zostało ustawione na UTF-8. W trzeciej linijce kodu tworzę użytkownika o dźwięcznej i niewiele znaczącej nazwie gienek, który ma dostęp do bazy danych jedynie przez localhost. Gienek to sprytny gość, więc do logowania używa niewymagającego zapamiętywania hasła o nazwie password. W piątej linijce gienkowi przydzielany jest przywilej korzystania z bazy danych library na wszystkie w niej znajdujące się tabele z możliwością wykonywania wszystkich dostępnych operacji. W siódmej linijce wchodzę do bazy danych library by po chwili z najdzikszą rozkoszą sięgającą granic pojmowania ludzkiego umysłu tworzę tabelę o przebiegłej nazwie books. Tabela ta zawiera pole id będące kluczem typu unsigned int z ustawioną opcją automatycznego przyrostu wartości przy dodawaniu rekordów do bazy danych. Kolejne pola tej tabeli to title i author, których maksymalna długość może osiągnąć zawrotną wartość 255 znaków. Po utworzeniu w tak żmudnym procesie tabeli niezwłocznie należy oddać się dzikiej rozkoszy dodania do niej jednej pozycji książkowej, co też i czynię w linijce piętnastej.

Baza danych - pierwszy kontakt

Oczywiście do utworzenia bazy danych jak i połączenia się z nią konieczna jest obecność w systemie uruchomionego serwera SQL w moim przypadku używam XAMPP-a, który zainstalował mi cały pakiet tego, co potrzebne jest do uruchomienia serwera SQL na localhost. W moim przypadku połączenie uzyskam wykorzystując następujący kod w pliku mainwindow.h:

Listing 6
  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3. #include <QMainWindow>
  4. #include <QDebug>
  5. #include <QMessageBox>
  6. #include <QStringList>
  7. #include <QSqlDatabase>
  8. #include <QSqlError>
  9. #include <QSqlQuery>
  10. #include <mysql.h>
  11. namespace Ui {
  12. class MainWindow;
  13. }
  14. class MainWindow : public QMainWindow
  15. {
  16. Q_OBJECT
  17. QSqlDatabase db;
  18. void readRecordsToListWidget();
  19. public:
  20. explicit MainWindow(QWidget *parent = 0);
  21. ~MainWindow();
  22. private slots:
  23. void on_btAdd_clicked();
  24. void on_records_doubleClicked(const QModelIndex &index);
  25. void on_btRemove_clicked();
  26. private:
  27. Ui::MainWindow *ui;
  28. };
  29. #endif // MAINWINDOW_H

W pliku mainwindow.cpp:

Listing 7
  1. #include "mainwindow.h"
  2. #include "ui_mainwindow.h"
  3. // https://dev.mysql.com/downloads/connector/c/
  4. MainWindow::MainWindow(QWidget *parent) :
  5. QMainWindow(parent),
  6. ui(new Ui::MainWindow)
  7. {
  8. ui->setupUi(this);
  9. db = QSqlDatabase::addDatabase("QMYSQL"); // tworzę bazę danych z wykorzystaniem sterownika QMYSQL
  10. db.setDatabaseName("library"); // ustawiam nazwę biblioteki, pod którą chcę się podpiąć
  11. db.setHostName("localhost"); // nazwa serwera
  12. db.setPassword("password"); // hasło gienka
  13. db.setPort(3306); // port połączenia z bazą danych
  14. db.setUserName("gienek"); // nazwa użytkownika
  15. if(!db.open()){ // próbuję otworzyć bazę danych i jak się nie otworzy to
  16. qDebug() << "ERROR load: " << db.lastError().text(); // wyświetlam błąd ładowania w konsoli kompilatora
  17. }else{
  18. qDebug() << "Is opened"; // wszystko cacy
  19. readRecordsToListWidget(); // więc wczytuję dane do mojej kontrolki QListWidget
  20. }
  21. }
  22. void MainWindow::readRecordsToListWidget(){
  23. QSqlQuery query(db); // tworzę obiekt zapytania i podpinam go pod otwartą bazę danych
  24. ui->records->clear(); // usuwam wszystkie elementy kontrolki
  25. if(query.exec("SELECT id, title, author FROM books")){ // wysyłam zapytanie do bazy danych
  26. while(query.next()){ // jeżeli są jakieś wyniki to
  27. // wstawiam rekord do kontrolki QListWidget
  28. ui->records->addItem(new QListWidgetItem(query.value(0).toString() + " \"" + query.value(1).toString() + "\" " + query.value(2).toString(), ui->records));
  29. }
  30. }else{
  31. qDebug() << query.lastError().text(); // wyświetlam w konsoli Qt Creatora informację błędu, gdy zapytanie zakończy się niepowodzeniem
  32. }
  33. }
  34. MainWindow::~MainWindow()
  35. {
  36. delete ui;
  37. }
  38. void MainWindow::on_btAdd_clicked() // dodawanie elementu do bazy danych
  39. {
  40. // sprawdzanie, czy kontrolki edAuthor i edTitle nie zawierają niedozwolonych znaków
  41. if(ui->edAuthor->text().indexOf("\"") != -1){
  42. ui->edAuthor->setText( ui->edAuthor->text().replace("\"", "") );
  43. QMessageBox::information(this, "Ostrzeżenie", "Pole autora książki zawierało niedozwolony znak \", który został usunięty");
  44. }
  45. if(ui->edTitle->text().indexOf("\"") != -1){
  46. ui->edTitle->setText( ui->edTitle->text().replace("\"", "") );
  47. QMessageBox::information(this, "Ostrzeżenie", "Pole tytułu książki zawierało niedozwolony znak \", który został usunięty");
  48. }
  49. if(ui->edAuthor->text().size() && ui->edTitle->text().size()){ // jeżeli pola tekstowe nie są puste to
  50. QSqlQuery query(db); // tworzę obiekt zapytania
  51. // przygotowuję zapytanie SQL, sprawdzające, czy w bazie danych jest już książka o podanym tytule i autorze
  52. query.prepare("SELECT title, author FROM books WHERE title = :title AND author = :author LIMIT 1;");
  53. // podpinam pod nie dane
  54. query.bindValue(":title", QVariant::fromValue(ui->edTitle->text())); // tekst z kontrolki edTitle zostanie oddzielnie wysłany a następnie wstawiony do zapytania za :title
  55. query.bindValue(":author", QVariant::fromValue(ui->edAuthor->text())); // to samo co powyżej
  56. if(
  57. query.exec() && (!query.next() // jak zapytanie zakończyło się powodzeniem i zwróciło element lub użytkownik postanowił dodać zduplikowany rekord to
  58. ||
  59. QMessageBox::warning( // jeżeli
  60. this,
  61. "Ostrzeżenie",
  62. "Autor i tytuł już istnieje na liście, czy chcesz"
  63. " mimo to dodać tę pozycję ponownie?",
  64. QMessageBox::Yes | QMessageBox::No
  65. ) == QMessageBox::Yes)){
  66. // przygotowuję nowe zapytanie
  67. query.prepare("INSERT INTO books (title, author) VALUES (:title, :author);");
  68. // binduję wartości
  69. query.bindValue(":title", QVariant::fromValue(ui->edTitle->text()));
  70. query.bindValue(":author", QVariant::fromValue(ui->edAuthor->text()));
  71. // wykonuję zapytanie
  72. if(query.exec()){
  73. readRecordsToListWidget(); // i jeżeli zakończy się ono powodzeniem to odświerzam listę rekordów
  74. }
  75. }
  76. }
  77. }
  78. void MainWindow::on_records_doubleClicked(const QModelIndex &index) // zdarzenie kliknięcia dwukrotnego w oknie kontrolki
  79. {
  80. QString recordData = index.data().toString();
  81. QStringList record = recordData.split(" \"");
  82. QStringList record2 = record[1].split("\" ");
  83. record.removeAt(1);
  84. record += record2;
  85. if(record.size() == 3){
  86. ui->edTitle->setText(record[1]); // wczytuję rekord do kontrolki tytułu
  87. ui->edAuthor->setText(record[2]); // wczytuję rekord do kontrolki autora
  88. }
  89. }
  90. void MainWindow::on_btRemove_clicked() // zdarzenie kliknięcia przycisku usunięcia zaznaczonego elementu
  91. {
  92. if(ui->records->currentItem()){
  93. QString recordData = ui->records->currentItem()->data(Qt::DisplayRole).toString();
  94. QStringList record = recordData.split(" \"");
  95. bool ok = false;
  96. int index = record[0].toInt(&ok);
  97. if(ok){
  98. QSqlQuery query(db);
  99. query.prepare("DELETE FROM books WHERE id = :id;"); // zapytanie usuwające wybrany rekord
  100. query.bindValue(":id", index);
  101. if(query.exec()){
  102. readRecordsToListWidget();
  103. }else{
  104. qDebug() << query.lastError();
  105. }
  106. }
  107. }
  108. }

Poniżej można zobaczyć screen programu umożliwiającego dodawanie i usuwanie rekordów z bazy danych.

Przykład programu łączącego się z bazą danych MySQL
Rys. 1
Przykład programu łączącego się z bazą danych MySQL na localhoście
Strony powiązane
strony powiązane
  1. doc.qt.io/qt-5/qsqldatabase.html - opis kasy QSqlDatabase na stronie dokumentacji Qt
  2. doc.qt.io/qt-4.8/qsqlquery.html - opis kasy QSqlQuery na stronie dokumentacji Qt

Komentarze