Kapitel 41. Datenbankprogrammierung

Dieses Kapitel enthüllt, wie Sie die Kontrolle über Daten durch SQL und QtSql zurückgewinnen: Sie werden die einfache Logik „Tabelle → Abfrage → Modell” entdecken, das Geheimnis der sicheren Dateneinfügung ohne manuelle String-Konkatenation erfahren und aufdecken, warum professionelle Qt-Entwickler fast immer mit dem richtigen Treiber und der Update-Strategie beginnen – das spart Stunden Debugging und rettet Releases.

Hier zeigen wir 4 grundlegende SQL-Befehle (CREATE/INSERT/SELECT/UPDATE+DELETE), analysieren 3 QtSql-Ebenen und befassen uns praktisch mit QSqlDatabase, QSqlQuery, QSqlTableModel und QSqlRelationalTableModel – einschließlich prepare()+bindValue() und Strategien OnFieldChange / OnManualSubmit.

Wenn Sie dieses Kapitel überspringen, verpassen Sie leicht auch den Moment, in dem „einfache Datenbank” zur Fehlerquelle wird.

Das Kapitel enthält Code-Beispiele, die sofort einsatzbereit sind.

Selbsttest zum Kapitel

Was ist ein Primärschlüssel und warum ist er für eine Datenbanktabelle wichtig?Antwort
Richtige Antwort: Ein Primärschlüssel ist ein eindeutiger Datensatz-Identifier, der aus einer oder mehreren Spalten bestehen kann. Er garantiert die Eindeutigkeit jedes Datensatzes und wird zur Verknüpfung von Tabellen untereinander verwendet.
Welche drei Klassenebenen bietet das QtSql-Modul und wofür ist jede verantwortlich?Antwort
Richtige Antwort: Treiberebene (physischer Datenzugriff), Programmierebene (API für DB-Zugriff über Klassen wie QSqlDatabase und QSqlQuery) und Benutzeroberflächen-Ebene (Modelle zur Datenanzeige in Views).
Was ist der Unterschied zwischen Selektion und Projektion im SELECT-Befehl?Antwort
Richtige Antwort: Selektion ist die Auswahl von Tabellenzeilen (definiert in WHERE), während Projektion die Auswahl von Spalten ist (angegeben nach SELECT). Zusammen bilden sie die resultierende Tabelle mit den benötigten Daten.
Warum sollte das Ergebnis der Methode QSqlDatabase::open() geprüft werden?Antwort
Richtige Antwort: Die Methode kann false bei fehlgeschlagener Verbindung zurückgeben (falsche Credentials, DB nicht verfügbar, Treiberprobleme). Die Prüfung ermöglicht rechtzeitige Fehlererkennung und Details über lastError().
Warum ist die Verwendung von prepare() und bindValue() besser als direkte Datensubstitution in SQL-Abfragen?Antwort
Richtige Antwort: Diese Methoden schützen vor SQL-Injection, escapen automatisch Sonderzeichen und gewährleisten korrekte Datentypisierung. Außerdem können vorbereitete Abfragen bei Mehrfachverwendung schneller ausgeführt werden.
Warum müssen beim Verteilen der Anwendung an Clients die DB-Treiber-Erweiterungsdateien eingeschlossen werden?Antwort
Richtige Antwort: Standardmäßig werden DB-Treiber als Plug-ins an Qt angebunden und nicht automatisch in die ausführbare Datei eingeschlossen. Ohne sie kann sich die Anwendung nicht mit der Datenbank auf dem Client-System verbinden.
Warum ist die Klasse QSqlQueryModel nur zum Lesen von Daten vorgesehen?Antwort
Richtige Antwort: Das Abfragemodell zeigt das Ergebnis einer beliebigen SELECT-Abfrage an, die JOIN, Aggregation und berechnete Felder enthalten kann. Solche Daten können nicht immer eindeutig zurück in die Ursprungstabellen geschrieben werden.
In welchen Fällen ist SQLite serverseitigen DBMS wie MySQL oder PostgreSQL vorzuziehen?Antwort
Richtige Antwort: SQLite ist ideal für lokale Einzelbenutzerdaten, eingebettete Anwendungen und mobile Geräte. Sie benötigt keine separate Serverinstallation, der Treiber ist standardmäßig in Qt6 enthalten, und die DB wird in einer Datei gespeichert.
Warum kann bei den Bearbeitungsstrategien OnFieldChange und OnRowChange nur eine Zeile gleichzeitig eingefügt werden?Antwort
Richtige Antwort: Diese Strategien speichern Änderungen automatisch beim Zellen- oder Zeilenwechsel. Beim Einfügen mehrerer leerer Zeilen löst der erste Wechselversuch das Speichern eines unausgefüllten Datensatzes aus, was DB-Constraints verletzen kann.
Welche Bearbeitungsstrategie sollte gewählt werden, wenn vollständige Kontrolle über den Speicherzeitpunkt benötigt wird?Antwort
Richtige Antwort: OnManualSubmit-Strategie. Daten werden nur beim Aufruf des submitAll()-Slots gespeichert, und Änderungen können über revertAll() verworfen werden. Dies ist praktisch für „Speichern”- und „Abbrechen”-Funktionen.
Wie können nur bestimmte Tabellenspalten bei Verwendung von QSqlTableModel angezeigt werden?Antwort
Richtige Antwort: Die Methode removeColumn() muss für jede nicht anzuzeigende Spalte aufgerufen werden. Das Modell lädt standardmäßig alle Spalten, daher müssen unnötige explizit ausgeblendet werden.
Was passiert, wenn vergessen wird, select() nach setTable() in QSqlTableModel aufzurufen?Antwort
Richtige Antwort: Das Modell führt keine Datenbankabfrage aus und bleibt leer. Die Methode select() lädt tatsächlich die Daten aus der angegebenen Tabelle in das Modell – ohne sie bleibt die View leer.
In welchem Szenario ist die Verwendung von QSqlRelationalTableModel besser als QSqlTableModel?Antwort
Richtige Antwort: Wenn Daten aus verknüpften Tabellen über Fremdschlüssel angezeigt werden sollen. Zum Beispiel statt Status-ID dessen Textbeschreibung aus Nachschlagetabelle zeigen. Das relationale Modell führt automatisch JOIN aus und zeigt lesbare Werte an.

Praktische Aufgaben

Einfaches Level

Notizbuch mit SQLite
Erstellen Sie eine Qt-Anwendung mit einer SQLite-Datenbank zur Speicherung persönlicher Notizen. Die Tabelle sollte folgende Felder enthalten: id (Primärschlüssel), title (Überschrift), content (Notiztext), created_at (Erstellungsdatum). Implementieren Sie das Hinzufügen neuer Notizen und die Anzeige aller Notizen in QTableView.
Tipps: Verwenden Sie QSqlTableModel für einfache Anzeige. Fügen Sie der Projektdatei QT += sql hinzu. Verwenden Sie insertRow() und setData() zum Einfügen. Vergessen Sie nicht, submitAll() zum Speichern der Änderungen aufzurufen. Setzen Sie die Bearbeitungsstrategie über setEditStrategy().

Mittleres Level

Bibliothek mit Suche und Filterung
Entwickeln Sie eine Anwendung zur Buchverwaltung mit zwei Tabellen: books (id, title, author, year, genre_id) und genres (id, name). Verwenden Sie QSqlRelationalTableModel, um das Genre nach Namen statt ID anzuzeigen. Fügen Sie QLineEdit für die Suche nach Autor oder Titel über setFilter() und QComboBox zur Filterung nach Genres hinzu. Implementieren Sie Datenbearbeitung mit Speicherbestätigung.
Tipps: Verwenden Sie setRelation() zur Verknüpfung mit der genres-Tabelle. Bilden Sie für die Suche WHERE-Bedingung mit LIKE. Wenden Sie OnManualSubmit-Strategie mit „Speichern”/„Abbrechen”-Buttons an, die submitAll()/revertAll() aufrufen. Behandeln Sie Fehler über lastError().

Schwieriges Level

Projektverwaltungssystem mit Aufgaben
Erstellen Sie eine voll funktionsfähige Anwendung zur Projektverwaltung mit drei verknüpften Tabellen: projects (id, name, description, status_id), tasks (id, project_id, title, description, priority, completed), statuses (id, name, color). Implementieren Sie: hierarchische Anzeige von Projekten und Aufgaben, Drag-and-Drop zur Änderung der Aufgabenpriorität, Filterung nach Status und Erledigung, Export ausgewählter Daten nach CSV, Eingabevalidierung (Prüfung von Pflichtfeldern), Anzeige nicht gespeicherter Änderungen.
Tipps: Verwenden Sie QTreeView mit einem von QSqlRelationalTableModel abgeleiteten Custom-Modell. Überschreiben Sie für Drag-and-Drop die Methoden dropMimeData() und mimeData(). Implementieren Sie ein „Dirty Data”-Flag bei Änderungen. Durchlaufen Sie für den Export ausgewählte Zeilen über selectionModel(). Implementieren Sie Validierung durch Überschreiben von setData(). Wenden Sie Transaktionen (database().transaction(), commit(), rollback()) für Atomarität der Operationen an.

💬 Beteiligen Sie sich an der Diskussion!

Haben Sie das QtSql-Modul und seine drei Klassenebenen verstanden? Sind Fragen zur Wahl zwischen QSqlQueryModel und QSqlTableModel aufgekommen?

Teilen Sie Ihre Erfahrungen mit verschiedenen DBMS, berichten Sie über Fallstricke bei der Verwendung relationaler Modelle oder stellen Sie Fragen zur SQL-Abfrageoptimierung!

Leave a Reply

Your email address will not be published. Required fields are marked *