Datenbanken unter C/C++
Willemers Informatik-Ecke
Weitere Themen Diese Seite beschreibt den Zugriff auf Datenbanken von C++ aus. Grundlagenwissen über Datenbanken wird an dieser Stelle vorausgesetzt. Die Syntax von SQL wird hier ebensawenig beschrieben.

Java ist auf Portabilität ausgelegt und hat darum die Standardschnittstelle JDBC für den Zugriff auf Datenbanken definiert. C++ orientiert sich an einer Nähe zur API und nutzt die Möglichkeiten der verschiedenen Datenbanken optimal aus. Das allerdings erschwert einen Wechsel der Datenbank.

Auch für die Portabilität gibt es bei C++ Lösungen. So bieten die bekannte Frameworks Qt und wxWidgets portable Zugriffe an, mit denen verschiedene Datenbanken eingesetzt werden können. Die Portabilität kann auch per ODBC erreicht werden. Auch die hier vorgestellte Klassenbibliothek CppDB ermöglicht den Zugriff auf verschiedene Datenbanken.

SQLite

SQLite verwendet keinen Datenbank-Serverprozess, sondern legt seine Datenbanktabellen in einer lokalen Datei des Dateisystems ab. Auf diese Weise kann die Anwendung ihre strukturierten Daten wie in einer Datenbank verwalten, ohne den Aufwand einer Netzwerkkomponente und eines Hintergrundprozesses.

Installation

Linux-Benutzer sind wieder etwas konfortabler dran.
sudo apt install libsqlite3-dev
Andere Plattformen bedienen sich bei https://www.sqlite.org/download.html.

Hauptprogramm

#include <iostream>
#include <string>
#include <sqlite3.h>
using namespace std;

int main()
{
    sqlite3* db;
    if(sqlite3_open("datenbank.db", &db) != SQLITE_OK)
    {
        cerr << "Fehler beim Öffnen: " << sqlite3_errmsg(db) << endl;
        return 1; // Fehlernummer
    }
    // Tue etwas mit der Datenbank

    // Anschließend muss die Datei geschlossen werden.
    sqlite3_close(db);
    return 0; // alles ok
}
Das Programm kann mit dem folgenden Befehl übersetzt werden:
g++ sqlite.cpp -l sqlite3
Die Funktion createTable erzeugt eine Tabelle PERSON.
int createTable(sqlite3 *db)
{
    const char *sqlBefehl = "CREATE TABLE PERSON("  \
        "ID             INT PRIMARY KEY NOT NULL," \
        "NAME           VARCHAR         NOT NULL," \
        "ADRESSE        CHAR(50)," \
        "GEHALT         REAL );";
    char *fehlerMeldung = nullptr;
    int rc = sqlite3_exec(db, sqlBefehl, callback, 0, &fehlerMeldung);
    if (rc != SQLITE_OK)
    {
        cerr << "SQL Fehler: " << fehlerMeldung << endl;
        sqlite3_free(fehlerMeldung);
        return rc;
    }
    return SQLITE_OK;
}
Die Funktion sqlite3_exec übergibt die Adresse einer Funktion callback. Diese wird erst später beim SELECT wirklich gebraucht, muss aber natürlich vor ihrer ersten Erwähnung definiert werden. In dieser Version wird sie die übergebenen Daten auf dem Bildschirm ausgeben.
int callback(void *NotUsed, int argc, char **argv, char **azColName)
{
    for(int i = 0; i<argc; i++) {
        cout << azColName[i] << " = " << (argv[i]?argv[i]:"NULL") << endl;
    }
    return 0;
}
Für die Person wird eine Klasse erstellt und diese wird durch die Funktion insert in die Tabelle eingetragen.
class Person
{
public:
     int id;
     string name;
     string adresse;
     double gehalt;
};

int insert(sqlite3 *db, Person* person)
{
    // "INSERT INTO PERSON (ID, NAME, ADRESSE, GEHALT) " 
    // "VALUES (1, 'Otto', 'Emden', 2.00 ) " 
    string sqlString = "INSERT INTO PERSON " \
        "(ID,NAME,ADRESSE,GEHALT) VALUES ("
        +to_string(person->id)+", '"
        +person->name+"', '"+person->adresse+"', "
        +to_string(person->gehalt)+");";
    char *fehlerMeldung = nullptr;
    int rc = sqlite3_exec(db, sqlString.c_str(), callback, 0, &fehlerMeldung);
    if (rc != SQLITE_OK)
    {
        cerr <<  "SQL Fehler: " << fehlerMeldung << endl;
        sqlite3_free(fehlerMeldung);
        return rc;
    }
    return SQLITE_OK;
}
Die einfache Variante der SELECT-Anfrage ruft die Callback-Funktion auf, die sich dann um die Daten kümmern soll.
int select(sqlite3 *db)
{
    const char* sqlBefehl =  "SELECT * from PERSON";
    char *fehlerMeldung = nullptr;
    int rc = sqlite3_exec(db, sqlBefehl, callback, 0, &fehlerMeldung);
    if (rc != SQLITE_OK)
    {
        cerr << "SQL Fehler: " << fehlerMeldung << endl;
        sqlite3_free(fehlerMeldung);
    }
    return SQLITE_OK;
}
Die andere Version geht schrittweise durch die Ergebnisliste und füllt einen Vektor, der per Parameter an den Aufrufer übergeben wird.
#include <vector>

int select(sqlite3 *db, vector<Person> &personen)
{
    sqlite3_stmt *stmt;
    const char *sql = "SELECT ID, NAME FROM PERSON";
    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
    if (rc != SQLITE_OK)
    {
        cerr << "SQL Fehler: " << sqlite3_errmsg(db) << endl;
        return rc;
    }
    while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
        int id           = sqlite3_column_int (stmt, 0);
        const unsigned char* utf8cstring = sqlite3_column_text(stmt, 1);
        string str = std::string(reinterpret_cast<const char*>(utf8cstring));
        Person person = {id, str, "hier", 2.4};
        personen.push_back(person);
    }
    if (rc != SQLITE_DONE) {
        cerr << "SQL Fehler: " << sqlite3_errmsg(db) << endl;
    }
    sqlite3_finalize(stmt);
    return rc;
}
Das Hauptprogramm muss nun um die Aufrufe ergänzt werden.
int main()
{
    sqlite3* db;
    if(sqlite3_open("datenbank.db", &db) != SQLITE_OK)
    {
        cerr << "Fehler beim Öffnen: " << sqlite3_errmsg(db) << endl;
        return 1; // Fehlernummer
    }
    // Tue etwas mit der Datenbank
    createTable(db);
    Person personen[] =
        {
            {7, "James Bond", "London", 10E6},
            {22, "Otto", "Emden", 2.0},
            {23, "Ännchen", "Tönsfeld", 2.0},
        };
    for (Person person : personen)
    {
        insert(db, &person);
    }
    select(db);

    vector<Person> liste;
    select(db, liste);

    sqlite3_close(db);
    return 0; // alles ok
}

CppDB

Installation und Einbindung

Homepage von CppDB

Unter Debian, Ubuntu und Linux Mint einfach aus den Repositories installieren:

apt install libcppdb-dev libcppdb-postgresql0
Die Übersetzung von der Konsole:
g++ meinprogramm.cpp -l cppdb
Bei Verwendung von Windows müssen die Pfade für die Includes und Libraries explizit gesetzt werden. In der IDE müssen die Projekteinstellungen entsprechend gesetzt werden.

Verbindungsaufnahme

Durch Anlegen eines Objekts von cppdb::session wird eine Verbindung zur Datenbank gebaut. Der Parameter des Konstruktors erhält den Verbinds-String.
#include <cppdb/frontend.h>

int main()
{
    try
    {
        cppdb::session db("postgresql:host=localhost;"
                "dbname=person;user=person;password='person'");
        // Tue etwas mit der Datenbank
    }
    catch(std::exception const &e) {
            std::cerr << "Fehler: " << e.what() << std::endl;
            return 1;
    }
    return 0;
}
Der String zur Datenbank ist je nach Datenbank unterschiedlich. Unten die beiden Strings für PostgreSQL und MySQL:
postgresql:host=localhost;dbname=person;user=anton;password='geheim'
mysql:database=person;user=anton;password='geheim'

createTable

Die SQL-Befehle werden per Umleitungsoperator auf das Session-Objekt umgeleitet. Zur Durchführung wird cppdb::exec übergeben.
int createTable(cppdb::session db)
{
    db << "DROP TABLE IF EXISTS person1" << cppdb::exec;
    db << "CREATE TABLE person1 ( "
            "ID             INT PRIMARY KEY NOT NULL,"
            "NAME           VARCHAR         NOT NULL,"
            "ADRESSE        CHAR(50),"
            "GEHALT         REAL )  " << cppdb::exec;
}

insert

Wird neben dem Befehl selbst noch Daten übergeben, können in den Befehls-String noch Fragezeichen eingesetzt werden, die durch weitere Umleitungen gefüllt werden.
class Person
{
public:
     int id;
     std::string name;
     std::string adresse;
     double gehalt;
};

int insert(cppdb::session db, Person* person)
{
    cppdb::statement stat;
    stat = db <<
            "INSERT INTO person1 (ID,NAME,ADRESSE,GEHALT) "
            "VALUES(?,?,?,?) "
            << person->id << person->name
            << person->adresse << person->gehalt;
    stat.exec();
}
Hier wird der Befehl in ein cppdb::statement-Objekt umgeleitet. Dadurch ist es möglich, den SQL-Befehl wieder zu verwenden. Dazu wird über das Statement reset aufgerufen und mit bind nacheinander neue Werte eingefüllt. Zum Schluss kann noch einmal exec aufgerufen werden.

select

Beim Auslesen der Tabelle wird das Ergebnis der Anfrage in einem cppdb::result-Objekt gehalten. Dieses kann mit dem Eingabe-Operator in die Variablen geschoben werden. Die Methode next holt das nächste Ergebnis der Abfrage. Die Methode empty stellt fest, ob überhaupt ein Ergebnis vorliegt.
int select(cppdb::session db, std::vector<Person> &personen)
{
    cppdb::result res = db 
            << "SELECT ID,NAME,ADRESSE,GEHALT FROM person1";
    while(res.next()) {
        Person person;
        res >> person.id >> person.name >> person.adresse >> person.gehalt;
        personen.push_back(person);
    }
}

PostgreSQL

Die C++-API für PostgreSQL heißt libpqxx. Die Bibliothek unterliegt einer BSD-Lizenz, was bedeutet, dass sie kostenlos verwendet werden kann.

Beschreibung der C++-API für PostgreSQL liegt unter pqxx.org/development/libpqxx .

Einbinden des Datenbanktreibers

g++ meineanwendung.cpp -lpqxx -lpq

Weitere Links

Tutorialspoint

MySQL

Die C-API für MySQL wird auf der Seite über MySQL ausführlicher beschrieben.

Links

MySQL-Datenbank und C++ verwenden