Kapitel 4. Schreiben von MySQL-basierten Programmen

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

4.0 Einleitung

In diesem Kapitel wird die Verwendung von MySQL im Kontext einer Allzweckprogrammiersprache erläutert. Es behandelt die grundlegenden Operationen der Anwendungsprogrammierschnittstelle (API), die die Basis für die in späteren Kapiteln entwickelten Programmierrezepte bilden. Zu diesen Operationen gehören die Verbindung zum MySQL-Server, die Ausführung von Anweisungen und das Abrufen der Ergebnisse.

MySQL-basierte Client-Programme können in vielen Sprachen geschrieben werden. Dieses Buch behandelt die in Tabelle 4-1aufgeführten Sprachen und Schnittstellen (Informationen zum Bezug der Schnittstellensoftware findest du im Vorwort).

Tabelle 4-1. In diesem Buch behandelte Sprachen und Schnittstellen
SpracheSchnittstelle
PerlPerl DBI
RubyMysql2 gem
PHPPDO
PythonDB-API
GehGo sql
JavaJDBC

Die MySQL-Client-APIs bieten die folgenden Funktionen, die jeweils in einem Abschnitt dieses Kapitels behandelt werden:

Verbinden mit dem MySQL-Server, Auswählen einer Datenbank und Trennen der Verbindung mit dem Server

Jedes Programm, das MySQL verwendet, muss zunächst eine Verbindung zum Server herstellen. Die meisten Programme wählen auch eine Standarddatenbank aus, und brave MySQL-Programme schließen die Verbindung zum Server, wenn sie damit fertig sind.

Prüfen auf Fehler

Jede Datenbankoperation kann fehlschlagen. Wenn du weißt, wie du herausfindest, wann und warum das passiert, kannst du geeignete Maßnahmen ergreifen, z. B. das Programm beenden oder den Benutzer über das Problem informieren.

Ausführen von SQL-Anweisungen und Abrufen von Ergebnissen

Der Zweck einer Verbindung zu einem Datenbankserver ist die Ausführung von SQL Anweisungen. Jede API bietet mindestens eine Möglichkeit, dies zu tun, sowie Methoden zur Verarbeitung der Anweisungsergebnisse.

Umgang mit Sonderzeichen und NULL Werten in Anweisungen

Datenwerte können direkt in Anweisungsstrings eingebettet werden. Einige Zeichen wie Anführungszeichen und Backslashes haben jedoch eine besondere Bedeutung, so dass ihre Verwendung bestimmte Vorsichtsmaßnahmen erfordert. Das Gleiche gilt für NULL Werte. Wenn du damit nicht richtig umgehst, können deine Programme SQL-Anweisungen erzeugen, die fehlerhaft sind oder unerwartete Ergebnisse liefern. Wenn du Daten aus externen Quellen in deine Abfragen einbeziehst, kann dein Programm zum Ziel von SQL-Injection-Angriffen werden. Mit den meisten APIs kannst du diese Probleme vermeiden, indem du Platzhalter verwendest, die symbolisch auf Datenwerte in einer auszuführenden Anweisung verweisen und diese Werte separat bereitstellen. Die API fügt die Daten in den Anweisungsstring ein, nachdem sie alle Sonderzeichen oder NULL Werte ordnungsgemäß kodiert hat. Platzhalter werden auch als Parametermarker bezeichnet.

Identifizierung von NULL Werten in Ergebnismengen

NULL Werte sind nicht nur bei der Konstruktion von Anweisungen etwas Besonderes, sondern auch bei den Ergebnissen, die von ihnen zurückgegeben werden. Jede API bietet eine Konvention, um sie zu erkennen und mit ihnen umzugehen.

Unabhängig davon, welche Programmiersprache du verwendest, musst du wissen, wie die grundlegenden Datenbank-API-Vorgänge ausgeführt werden. Deshalb wird in diesem Kapitel jeder Vorgang in allen fünf Sprachen gezeigt. Wenn du siehst, wie jede API einen bestimmten Vorgang handhabt, kannst du die Zusammenhänge zwischen den APIs leichter erkennen und die Rezepte in den folgenden Kapiteln besser verstehen, selbst wenn sie in einer Sprache geschrieben sind, die du nicht oft verwendest. (In den späteren Kapiteln werden die Rezepte in der Regel nur in einer oder zwei Sprachen umgesetzt).

Wenn du dich nur für eine bestimmte API interessierst, kann es dich überfordern, jedes Rezept in mehreren Sprachen zu lesen. In diesem Fall empfehlen wir dir, nur den einleitenden Teil zu lesen, in dem du den allgemeinen Hintergrund erfährst, und dann direkt zu dem Abschnitt für die Sprache zu gehen, an der du interessiert bist. Überspringe die anderen Sprachen; wenn du dich später für sie interessierst, kommst du zurück und liest sie nach.

In diesem Kapitel werden auch die folgenden Themen behandelt, die nicht direkt zu den MySQL-APIs gehören, dir aber helfen, sie einfacher zu nutzen:

Bibliotheksdateien schreiben

Wenn du ein Programm nach dem anderen schreibst, stellst du fest, dass du bestimmte Vorgänge immer wieder ausführst. Bibliotheksdateien ermöglichen es, den Code für diese Vorgänge zu kapseln, so dass sie leicht von mehreren Skripten aus ausgeführt werden können, ohne den Code in jedem einzelnen Skript zu wiederholen. So wird der Code nicht doppelt ausgeführt und deine Programme werden portabel. In diesem Kapitel wird gezeigt, wie du für jede API eine Bibliotheksdatei schreibst, die eine Routine für die Verbindung zum Server enthält - eine Operation, die jedes Programm, das MySQL verwendet, ausführen muss. In späteren Kapiteln werden zusätzliche Bibliotheksroutinen für andere Operationen entwickelt.

Zusätzliche Techniken zur Ermittlung der Verbindungsparameter

Ein früher Abschnitt über das Herstellen von Verbindungen zum MySQL-Server verlässt sich auf fest im Code verankerte Verbindungsparameter. Es gibt jedoch auch andere (und bessere) Möglichkeiten, Parameter zu erhalten, z. B. indem sie in einer separaten Datei gespeichert werden oder der Benutzer sie zur Laufzeit angeben kann.

Um zu vermeiden, dass du die Beispielprogramme manuell eintippst, besorge dir eine Kopie von der recipes Quellendistribution (siehe Vorwort). Wenn es in einem Beispiel heißt: " Erstelle eine Datei mit dem Namen xyz, die die folgenden Informationen enthält", kannst du die entsprechende Datei aus der recipes Distribution verwenden. Die meisten Skripte für dieses Kapitel befinden sich im api-Verzeichnis; die Bibliotheksdateien befinden sich im lib-Verzeichnis.

Die primäre Tabelle, die für die Beispiele in diesem Kapitel verwendet wird, heißtprofile . Sie taucht zum ersten Mal in Rezept 4.4 auf, was du wissen solltest, falls du im Kapitel herumspringst und dich fragst, woher sie stammt. Siehe auch den Abschnitt ganz am Ende des Kapitels, in dem es darum geht, die Tabelle profile auf einen bekannten Zustand zurückzusetzen, um sie in anderen Kapiteln zu verwenden.

Hinweis

Die hier besprochenen Programme können über die Kommandozeile ausgeführt werden. Anweisungen zum Aufrufen von Programmen für jede hier behandelte Sprache findest du unter cmdline.pdf in der Rezeptdistribution.

Annahmen

Um das Material in diesem Kapitel möglichst effektiv zu nutzen, solltest du diese Voraussetzungen erfüllen :

  • Installiere die MySQL-Programmierunterstützung für alle Sprachen, die du verwenden willst (siehe Vorwort).

  • Du solltest bereits ein MySQL-Benutzerkonto für den Zugriff auf den Server und eine Datenbank für die Ausführung von SQL-Anweisungen eingerichtet haben. Wie in Rezept 1.1 beschrieben, verwenden die Beispiele in diesem Buch ein MySQL-Konto mit den Benutzernamen und Passwörtern cbuser und cbpass. Wir verbinden uns mit einem MySQL-Server, der auf dem lokalen Host läuft, um auf eine Datenbank namens cookbook zuzugreifen. Wie du das Konto oder die Datenbank erstellst, erfährst du in diesem Rezept.

  • Im Folgenden wird gezeigt, wie die einzelnen API-Sprachen für die Durchführung von Datenbankoperationen verwendet werden, wobei ein grundlegendes Verständnis der jeweiligen Sprache vorausgesetzt wird. Wenn ein Rezept Programmierkonstrukte verwendet, mit denen du nicht vertraut bist, ziehe eine allgemeine Referenz für die betreffende Sprache zu Rate.

  • Für die ordnungsgemäße Ausführung einiger Programme kann es erforderlich sein, dassdu bestimmte Umgebungsvariablen setzt. Die allgemeine Syntax dafür findest du unter cmdline.pdf in der Rezeptverteilung (siehe Vorwort). Einzelheiten zu Umgebungsvariablen, die sich speziell auf den Speicherort von Bibliotheksdateien beziehen, findest du in Rezept 4.3.

MySQL Client API Architektur

Jede in diesem Buch behandelte MySQL-Programmierschnittstelle verwendet eine zweistufigeArchitektur:

  • Die obere Ebene bietet datenbankunabhängige Methoden, die den Datenbankzugriff auf eine portable Art und Weise implementieren, die unabhängig davon ist, ob du MySQL, PostgreSQL, Oracle oder was auch immer verwendest.

  • Die untere Ebene besteht aus einer Reihe von Treibern, von denen jeder die Details für ein einzelnes Datenbanksystem implementiert.

Diese zweistufige Architektur ermöglicht es Anwendungsprogrammen, eine abstrakte Schnittstelle zu verwenden, die nicht an Details eines bestimmten Datenbankservers gebunden ist. Das verbessert die Portabilität deiner Programme: Wenn du ein anderes Datenbanksystem verwenden willst, wählst du einfach einen anderen Treiber auf unterer Ebene. Perfekte Portabilität ist jedoch schwer zu erreichen:

  • Die Schnittstellenmethoden, die von der oberen Ebene der Architektur bereitgestellt werden, sind unabhängig vom verwendeten Treiber konsistent, aber es ist trotzdem möglich, SQL-Anweisungen zu schreiben, die Konstrukte verwenden, die nur von einem bestimmten Server unterstützt werden. MySQL hat zum Beispiel SHOW Anweisungen, die Informationen über die Datenbank- und Tabellenstruktur liefern, aber die Verwendung von SHOW mit einem Nicht-MySQL-Server wird wahrscheinlich zu einem Fehler führen.

  • Treiber auf niedrigerer Ebene erweitern oft die abstrakte Schnittstelle, um den Zugriff auf datenbankspezifische Funktionen zu vereinfachen. Der MySQL-Treiber für Perl DBI macht zum Beispiel den aktuellsten Wert von AUTO_INCREMENT als Datenbank-Handle-Attribut verfügbar, das als $dbh->{mysql_insertid} zugänglich ist. Solche Funktionen machen ein Programm zwar einfacher zu schreiben, aber weniger portabel. Um das Programm mit einem anderen Datenbanksystem zu verwenden, muss es umgeschrieben werden.

Trotz dieser Faktoren, die die Portabilität bis zu einem gewissen Grad beeinträchtigen, bieten die allgemeinen Portabilitätsmerkmale der zweistufigen Architektur erhebliche Vorteile für MySQL-Entwickler.

Ein weiteres gemeinsames Merkmal der in diesem Buch verwendeten APIs ist, dass sie objektorientiert sind. Egal, ob du in Perl, Ruby, PHP, Python, Java oder Go schreibst, die Operation, die eine Verbindung zum MySQL-Server herstellt, gibt ein Objekt zurück, mit dem du Anweisungen objektorientiert verarbeiten kannst. Wenn du dich zum Beispiel mit dem Datenbankserver verbindest, erhältst du ein Datenbankverbindungsobjekt, mit dem du weiter mit dem Server interagieren kannst. Die Schnittstellen stellen auch Objekte für Anweisungen, Ergebnismengen, Metadaten usw. bereit.

Sehen wir uns nun an, wie man diese Programmierschnittstellen verwendet, um die grundlegendsten MySQL-Operationen auszuführen: die Verbindung zum Server herstellen und die Verbindung zum Server trennen.

4.1 Verbinden, eine Datenbank auswählen und die Verbindung trennen

Problem

Du musst eine Verbindung zum Datenbankserver herstellen und die Verbindung schließen, wenn du fertig bist.

Lösung

Jede API bietet Routinen zum Verbinden und Trennen der Verbindung. Die Verbindungsroutinen erfordern, dass du Parameter angibst, die den Host, auf dem der MySQL-Server läuft, und das zu verwendende MySQL-Konto angeben. Du kannst auch eine Standarddatenbank auswählen.

Diskussion

Dieser Abschnitt zeigt, wie du einige grundlegende Operationen ausführst, die in den meisten MySQL-Programmen vorkommen:

Herstellen einer Verbindung zum MySQL-Server

Jedes Programm, das MySQL nutzt, tut dies, unabhängig davon, welche API du verwendest. Die Details zur Angabe von Verbindungsparametern sind von API zu API unterschiedlich, und einige APIs bieten mehr Flexibilität als andere. Es gibt jedoch viele gemeinsame Parameter, z. B. den Host, auf dem der Server läuft, und den Benutzernamen und das Passwort des MySQL-Kontos, das für den Zugriff auf den Server verwendet werden soll.

Auswählen einer Datenbank

Die meisten MySQL-Programme wählen eine Standarddatenbank aus.

Trennen der Verbindung mit dem Server

Jede API bietet eine Möglichkeit, eine offene Verbindung zu schließen. Am besten tust du das, sobald du den Server nicht mehr benutzt. Wenn dein Programm die Verbindung länger als nötig offen hält, kann der Server keine Ressourcen freigeben, die für die Bedienung der Verbindung vorgesehen sind. Es ist auch besser, die Verbindung explizit zu schließen. Wenn ein Programm einfach beendet wird, merkt der MySQL-Server das irgendwann, aber ein explizites Schließen auf der Benutzerseite ermöglicht es dem Server, die Verbindung sofort ordnungsgemäß zu schließen.

Dieser Abschnitt enthält Beispielprogramme, die zeigen, wie die einzelnen APIs verwendet werden, um eine Verbindung zum Server herzustellen, die Datenbank cookbook auszuwählen und die Verbindung zu trennen. In den Erläuterungen zu den einzelnen APIs wird auch beschrieben, wie du eine Verbindung herstellen kannst, ohne eine Standarddatenbank auszuwählen. Das könnte der Fall sein, wenn du eine Anweisung ausführen willst, die keine Standarddatenbank erfordert, wie SHOW VARIABLES oder SELECT VERSION() . Oder du schreibst ein Programm, das es dem Benutzer ermöglicht, die Datenbank anzugeben, nachdem die Verbindung hergestellt wurde.

Tipp

Die hier gezeigten Skripte verwenden localhost als Hostname. Wenn sie einen Verbindungsfehler produzieren, der anzeigt, dass eine Socket-Datei nicht gefunden werden kann, versuche localhost in 127.0.0.1 zu ändern, die TCP/IP-Adresse des lokalen Hosts. Dieser Tipp gilt für das gesamte Buch.

Perl

Um MySQL-Skripte in Perl zu schreiben, muss das DBI-Modul sowie das MySQL-spezifische Treibermodul DBD::mysql installiert sein. Wie du diese Module erhältst, wenn sie noch nicht installiert sind, findest du im Vorwort.

Das folgende Perl-Skript, connect.pl, stellt eine Verbindung zum MySQL-Server her, wählt cookbook als Standarddatenbank aus und trennt die Verbindung:

#!/usr/bin/perl
# connect.pl: connect to the MySQL server

use strict;
use warnings;
use DBI;

my $dsn = "DBI:mysql:host=localhost;database=cookbook";
my $dbh = DBI->connect ($dsn, "cbuser", "cbpass")
            or die "Cannot connect to server\n";
print "Connected\n";
$dbh->disconnect ();
print "Disconnected\n";

Um connect.pl auszuprobieren, suche es im api-Verzeichnis der recipes Distribution und führe es von der Kommandozeile aus. Das Programm sollte zwei Zeilen ausgeben, die anzeigen, dass es sich erfolgreich verbunden und getrennt hat:

$ perl connect.pl
Connected
Disconnected

Im weiteren Verlauf des Abschnitts werden wir den Code durchgehen und erklären, wie er funktioniert.

Tipp

Wenn du einen Access Denied Fehler erhältst, wenn du dich mit MySQL 8.0 verbindest, stelle sicher, dass die Version von DBD::MySQL mit der MySQL 8.0 Client-Bibliothek gelinkt ist, oder verwende das Authentifizierungs-Plug-in mysql_native_password anstelle des Standard-Plug-ins caching_sha2_password. Wir besprechen Authentifizierungs-Plug-ins in Rezept 24.2.

Hintergrundinformationen zum Ausführen von Perl-Programmen findest du unter cmdline.pdf in der Rezepte-Distribution (siehe Vorwort).

Die Zeile use strict schaltet die strenge Variablenprüfung ein und veranlasst Perl,sich über alle Variablen zu beschweren, die verwendet werden, ohne vorher deklariert worden zu sein. Diese Vorsichtsmaßnahme hilft, Fehler zu finden, die sonst unentdeckt bleiben könnten.

Die Zeile use warnings schaltet den Warnmodus ein, so dassPerl bei fragwürdigen Konstruktionen Warnungen ausgibt. In unserem Beispielskript gibt es keine, aber es ist eine gute Idee, sich anzugewöhnen, Warnungen zu aktivieren, um Probleme abzufangen, die während des Skriptentwicklungsprozesses auftreten. use warnings ist ähnlich wie die Angabe der Perl -w Kommandozeilenoption, bietet aber mehr Kontrolle darüber, welche Warnungen angezeigt werden sollen. (Weitere Informationen erhältst du, wenn du den Befehl perldocwarnings ausführst).

Mit der Anweisung use DBI wird Perl angewiesen, das DBI-Modul zu laden. Es ist nicht nötig, das MySQL-Treibermodul (DBD::mysql) explizit zu laden. DBI macht das selbst, wenn sich das Skript mit dem Datenbankserver verbindet.

Die nächsten beiden Zeilen stellen die Verbindung zu MySQL her, indemeinen Datenquellennamen (DSN) einrichtet und die DBI-Methode connect() aufruft. Die Argumente für connect() sind der DSN, der MySQL-Benutzername und das Passwort sowie alle Verbindungsattribute, die du angeben möchtest. Der DSN ist erforderlich. Die anderen Argumente sind optional, obwohl es normalerweise notwendig ist, einen Benutzernamen und ein Passwort anzugeben.

Der DSN gibt an, welcher Datenbanktreiber verwendet werden soll und andere Optionen, die angeben, wo die Verbindung hergestellt werden soll. Für MySQL-Programme hat der DSN das Format DBI:mysql:optionsDer zweite Doppelpunkt im DSN ist auch dann erforderlich, wenn du keine weiteren Optionen angibst.

Verwende die DSN-Komponenten wie folgt:

  • Die erste Komponente ist immer DBI. Groß- und Kleinschreibung wird nicht beachtet.

  • Die zweite Komponente teilt DBI mit, welcher Datenbanktreiber verwendet werden soll, und unterscheidet zwischen Groß- und Kleinschreibung. Für MySQL muss der Name mysql lauten.

  • Die dritte Komponente, falls vorhanden, ist eine durch Semikolon getrennte Liste von name=value Paaren, die zusätzliche Verbindungsoptionen angeben, in beliebiger Reihenfolge. Für unsere Zwecke sind die beiden wichtigsten Optionen host und database, um den Hostnamen, auf dem der MySQL-Server läuft, und die Standarddatenbank anzugeben.

Mit diesen Informationen sieht der DSN für die Verbindung zur Datenbank cookbook auf dem lokalen Rechner localhost wie folgt aus:

DBI:mysql:host=localhost;database=cookbook

Wenn du die Option host weglässt, istder Standardwert localhost. Diese beiden DSNs sind gleichwertig:

DBI:mysql:host=localhost;database=cookbook
DBI:mysql:database=cookbook

Um keine Standarddatenbank auszuwählen, lass die Option database weg.

Das zweite und dritte Argument des Aufrufs connect() sind dein MySQL-Benutzername und dein Passwort. Nach dem Passwort kannst du auch ein viertes Argument angeben, um Attribute festzulegen, die das Verhalten von DBI beim Auftreten von Fehlern steuern. Wenn du keine Attribute angibst, gibt DBI standardmäßig Fehlermeldungen aus, wenn Fehler auftreten, beendet dein Skript aber nicht. Deshalb prüft connect.pl, ob connect() undef zurückgibt, was einen Fehler anzeigt:

my $dbh = DBI->connect ($dsn, "cbuser", "cbpass")
            or die "Cannot connect to server\n";

Es sind auch andere Strategien für die Fehlerbehandlung möglich. Wenn du zum Beispiel DBI anweisen willst, das Skript zu beenden, wenn bei einem DBI-Aufruf ein Fehler auftritt, deaktiviere das Attribut PrintErrorund aktiviere stattdessen RaiseError:

my $dbh = DBI->connect ($dsn, "cbuser", "cbpass",
                        {PrintError => 0, RaiseError => 1});

Dann musst du nicht selbst nach Fehlern suchen. Der Kompromiss ist, dass du auch die Möglichkeit verlierst, zu entscheiden, wie sich dein Programm von Fehlern erholt. In Rezept 4.2 wird die Fehlerbehandlung näher erläutert.

Ein weiteres gängiges Attribut ist AutoCommit, das den Auto-Commit-Modus der Verbindung für Transaktionen festlegt. MySQL aktiviert diesen Modus standardmäßig für neue Verbindungen, aber wir werden ihn ab jetzt setzen, um den anfänglichen Verbindungsstatus explizit zu machen:

my $dbh = DBI->connect ($dsn, "cbuser", "cbpass",
                        {PrintError => 0, RaiseError => 1, AutoCommit => 1});

Wie gezeigt, ist das vierte Argument von connect() ein Verweis auf einen Hash mit Attributnamen/Wertpaaren. Es folgt eine alternative Möglichkeit, diesen Code zu schreiben:

my $conn_attrs = {PrintError => 0, RaiseError => 1, AutoCommit => 1};
my $dbh = DBI->connect ($dsn, "cbuser", "cbpass", $conn_attrs);

Verwende den Stil, den du bevorzugst. Die Skripte in diesem Buch verwenden den $conn_attr hashref, damit die Aufrufe von connect() einfacher zu lesen sind.

Wenn connect()erfolgreich ist, wird ein Datenbank-Handle zurückgegeben, das Informationen über den Zustand der Verbindung enthält. (In der DBI-Sprache werden Verweise auf Objekte als Handles bezeichnet.) Später werden wir andere Handles kennenlernen, wie z. B. Statement-Handles, die mit bestimmten Statements verbunden sind. Die Perl-DBI-Skripte in diesem Buch verwenden üblicherweise $dbh und $sth, um Datenbank- und Statement-Handles zu bezeichnen.

Um den Pfad zu einer Socket-Datei für Localhost-Verbindungen unter Unix anzugeben, gibst du im DSN die Option mysql_socket an:

my $dsn = "DBI:mysql:host=localhost;database=cookbook"
          . ";mysql_socket=/var/tmp/mysql.sock";

Um die Portnummer für Nicht-Localhost-Verbindungen (TCP/IP) anzugeben, gibst du die Option portan:

my $dsn = "DBI:mysql:host=127.0.0.1;database=cookbook;port=3307";

Ruby

Um MySQL-Skripte in Ruby zu schreiben, muss das Gem Mysql2 installiert sein. Wie du dieses Gem erhältst, wenn es noch nicht installiert ist, findest du im Vorwort.

Das folgende Ruby-Skript, connect.rb, stellt eine Verbindung zum MySQL-Server her, wählt cookbook als Standarddatenbank aus und trennt die Verbindung:

#!/usr/bin/ruby -w
# connect.rb: connect to the MySQL server

require "mysql2"

begin
  client = Mysql2::Client.new(:host => "localhost", 
                              :username => "cbuser",
                              :password => "cbpass",
                              :database => "cookbook")
  puts "Connected"
rescue => e
  puts "Cannot connect to server"
  puts e.backtrace
  exit(1)
ensure
  client.close()
  puts "Disconnected"
end

Um connect.rb auszuprobieren, suche es im api-Verzeichnis der recipes Distribution und führe es von der Kommandozeile aus. Das Programm sollte zwei Zeilen ausgeben, die anzeigen, dass es sich erfolgreich verbunden und getrennt hat:

$ ruby connect.rb
Connected
Disconnected

Hintergrundinformationen zum Ausführen von Ruby-Programmen findest du unter cmdline.pdf in der Rezepte-Distribution (siehe Vorwort).

Die Option -w schaltet den Warnmodus ein, sodass Ruby bei fragwürdigen Konstruktionen Warnungen ausgibt. Unser Beispielskript hat keine solchen Konstrukte, aber es ist eine gute Idee, sich anzugewöhnen, -wzu verwenden, um Probleme zu erkennen, die während der Skriptentwicklung auftreten.

Die Anweisung require weist Ruby an, das Modul Mysql2 zu laden.

Um die Verbindung herzustellen, erstellst du ein Mysql2::Client Objekt. Übergeben Sie Verbindungsparameter als benannte Argumente für die Methode new.

Um keine Standarddatenbank auszuwählen, lass die Option database weg.

Wenn das Objekt Mysql2::Client erfolgreich erstellt wurde, fungiert als Datenbank-Handle, das Informationen über den Status der Verbindung enthält. In den Ruby-Skripten in diesem Buch wird üblicherweise client verwendet, um ein Datenbank-Handle-Objekt zu bezeichnen.

Wenn die Methode new() fehlschlägt, löst sie eine Ausnahme aus. Um Ausnahmen zu behandeln, fügst du die Anweisungen, die fehlschlagen könnten, in einen begin Block ein und verwendest eine rescue Klausel, die den Code für die Fehlerbehandlung enthält. Ausnahmen, die auf der obersten Ebene eines Skripts auftreten (d. h. außerhalb eines begin Blocks), werden vom Standard-Exception-Handler abgefangen, der einen Stack-Trace ausgibt und die Anwendung beendet. In Rezept 4.2 wird die Fehlerbehandlung näher erläutert.

Um den Pfad zu einer Socket-Datei für Localhost-Verbindungen unter Unix anzugeben, gibst du die Option socket für die Methode new an:

client = Mysql2::Client.new(:host => "localhost",
                            :socket => "/var/tmp/mysql.sock",
                            :username => "cbuser",
                            :password => "cbpass",
                            :database => "cookbook")

Um die Portnummer für Nicht-Localhost-Verbindungen (TCP/IP) anzugeben, gibst du die Option portan:

client = Mysql2::Client.new(:host => "127.0.0.1",
                            :port => 3307,
                            :username => "cbuser",
                            :password => "cbpass",
                            :database => "cookbook")

PHP

Um PHP-Skripte zu schreiben, die MySQL verwenden, muss dein PHP-Interpreter die MySQL-Unterstützung einkompiliert haben. Wenn deine Skripte keine Verbindung zu deinem MySQL-Server herstellen können, kannst du in der Anleitung deiner PHP-Distribution nachlesen, wie du die MySQL-Unterstützung aktivierst.

PHP verfügt über mehrere Erweiterungen, die die Nutzung von MySQL ermöglichen, z. B. mysql, die ursprüngliche (und inzwischen veraltete) MySQL-Erweiterung, mysqli, die verbesserte MySQL-Erweiterung, und seit kurzem auch den MySQL-Treiber für die PDO-Schnittstelle (PHP Data Objects). Die PHP-Skripte in diesem Buch verwenden PDO. Wie du PHP und PDO erhältst, wenn sie noch nicht installiert sind, findest du im Vorwort.

PHP-Skripte werden normalerweise für die Verwendung mit einem Webserver geschrieben. Ichgehe davon aus, dass du, wenn du PHP auf diese Weise verwendest, PHP-Skripte in den Dokumentenbaum deines Servers kopieren kannst und sie von deinem Browser aus anforderst, und sie werden ausgeführt. Wenn du zum Beispiel Apache als Webserver auf dem Host localhost laufen lässt und ein PHP-Skript namens myscript.php auf der obersten Ebene des Apache-Dokumentenbaums installierst, solltest du das Skript aufrufen können, indem du diese URL anforderst:

http://localhost/myscript.php

In diesem Buch wird die Endung .phpfür die Dateinamen von PHP-Skripten verwendet, daher muss dein Webserver so konfiguriert sein, dass er die Endung .php erkennt. Wenn du sonst ein PHP-Skript von deinem Browser abrufst, sendet der Server einfach den wörtlichen Text des Skripts und das erscheint dann in deinem Browserfenster. Das willst du nicht, vor allem nicht, wenn das Skript den Benutzernamen und das Passwort für die Verbindung zu MySQL enthält.

PHP-Skripte werden oft als eine Mischung aus HTML- und PHP-Code geschrieben, wobei der PHP-Code zwischen den speziellen Tags <?php und ?> eingebettet ist. Hier ist ein Beispiel:

<html>
<head><title>A simple page</title></head>
<body>
<p>
<?php
  print ("I am PHP code, hear me roar!");
?>
</p>
</body>
</html>

Der Kürze halber lasse ich bei Beispielen, die ausschließlich aus PHP-Code bestehen, normalerweise die umschließenden Tags <?php und ?> weg. Wenn du in einem PHP-Beispiel keine Tags siehst, gehst du davon aus, dass <?php und ?> den gesamten gezeigten Codeblock umschließen. Beispiele, in denen zwischen HTML- und PHP-Code gewechselt wird, enthalten die Tags, um deutlich zu machen, was PHP-Code ist und was nicht.

PHP kann so konfiguriert werden, dass es auch kurze Tags erkennt, die als <? und ?> geschrieben werden. Dieses Buch geht nicht davon aus, dass du kurze Tags aktiviert hast und verwendet sie auch nicht.

Das folgende PHP-Skript, connect.php, verbindet sich mit dem MySQL-Server, wählt cookbook als Standarddatenbank aus und trennt die Verbindung:

<?php
# connect.php: connect to the MySQL server

try
{
  $dsn = "mysql:host=localhost;dbname=cookbook";
  $dbh = new PDO ($dsn, "cbuser", "cbpass");
  print ("Connected\n");
}
catch (PDOException $e)
{
  die ("Cannot connect to server\n");
}
$dbh = NULL;
print ("Disconnected\n");
?>

Um connect.php auszuprobieren, suche es im api-Verzeichnis der recipes -Distribution, kopiere es in den Dokumentenbaum deines Webservers und rufe es mit deinem Browser auf. Alternativ kannst du das Skript auch direkt ausführen, wenn du eine eigenständige Version des PHP-Interpreters für die Verwendung über die Kommandozeile hast:

$ php connect.php
Connected
Disconnected

Hintergrundinformationen zum Ausführen von PHP-Programmen findest du unter cmdline.pdf in der Rezepte-Distribution (siehe Vorwort).

$dsn ist der Datenquellenname (DSN), der angibt, wie man sich mit dem Datenbankserver verbindet. Er hat diese allgemeine Syntax:

driver:name=value;name=value ...

Der driver Wert ist der PDO-Treiber-Typ. Für MySQL ist dies mysql.

Nach dem Treibernamen folgen durch Semikolon getrennte name=value Paare die Verbindungsparameter in beliebiger Reihenfolge an. Für unsere Zwecke sind die beiden wichtigsten Optionen host und dbname, um den Hostnamen, auf dem der MySQL-Server läuft, und die Standarddatenbank anzugeben. Um keine Standarddatenbank auszuwählen, lassen Sie die Option dbnameweg.

Um die Verbindung herzustellen, rufst du den Konstruktor der Klasse new PDO() auf und übergibst ihm die entsprechenden Argumente. Der DSN ist erforderlich. Die anderen Argumente sind optional, obwohl es normalerweise notwendig ist, einen Benutzernamen und ein Passwort anzugeben. Wenn der Verbindungsversuch erfolgreich war, gibt new PDO() ein Datenbank-Handle-Objekt zurück, das für den Zugriff auf andere MySQL-bezogene Methoden verwendet wird. Die PHP-Skripte in diesem Buch verwenden üblicherweise $dbh, um ein Datenbank-Handle zu bezeichnen.

Wenn der Verbindungsversuch fehlschlägt, löst PDO eine Ausnahme aus. Um damit umzugehen, fügst du den Verbindungsversuch in einen try Block ein und verwendest einen catch Block, der den Code für die Fehlerbehandlung enthält, oder du lässt die Ausnahme dein Skript einfach beenden. In Rezept 4.2 wird die Fehlerbehandlung näher erläutert.

Um die Verbindung zu trennen, setzt du den Datenbank-Handle auf NULL. Es gibt keinen expliziten Aufruf zum Trennen der Verbindung.

Um den Pfad zu einer Socket-Datei für Localhost-Verbindungen unter Unix anzugeben, gibst du im DSN die Option unix_socketan:

$dsn = "mysql:host=localhost;dbname=cookbook"
         . ";unix_socket=/var/tmp/mysql.sock";

Um die Portnummer für Nicht-Localhost-Verbindungen(TCP/IP) anzugeben, gibst du die Option port an:

$dsn = "mysql:host=127.0.0.1;database=cookbook;port=3307";

Python

Um MySQL-Programme in Python zu schreiben, muss ein Modul installiert sein, das die MySQLKonnektivität für die Python DB API, auch bekannt als Python Database API Specification v2.0 (PEP 249), bereitstellt. Dieses Buch verwendet den MySQL Connector/Python, den du im Vorwort findest, falls du ihn noch nicht installiert hast.

Um die DB-API zu nutzen, importiere das Datenbanktreibermodul, das du verwenden möchtest ( mysql.connector für MySQL-Programme, die Connector/Python verwenden). Erstelle dann ein Datenbankverbindungsobjekt, indem du die Methode connect()des Treibers aufrufst. Dieses Objekt ermöglicht den Zugriff auf andere DB-API-Methoden, wie z. B. die Methode close(), die die Verbindung zum Datenbankserver herstellt.

Das folgende Python-Skript, connect.py, verbindet sich mit dem MySQL-Server, wählt cookbook als Standarddatenbank aus und trennt die Verbindung:

#!/usr/bin/python3
# connect.py: connect to the MySQL server

import mysql.connector

try:
  conn = mysql.connector.connect(database="cookbook",
                                 host="localhost",
                                 user="cbuser",
                                 password="cbpass")
  print("Connected")
except:
  print("Cannot connect to server")
else:
  conn.close()
  print("Disconnected")

Um connect.py auszuprobieren, suche es im api-Verzeichnis der recipes Distribution und führe es von der Kommandozeile aus. Das Programm sollte zwei Zeilen ausgeben, die anzeigen, dass es sich erfolgreich verbunden und getrennt hat:

$ python3 connect.py
Connected
Disconnected

Hintergrundinformationen zum Ausführen von Python-Programmen findest du unter. cmdline.pdf in der Rezepte-Distribution (siehe Vorwort).

Die Zeile import weist Python an, das Modul mysql.connector zu laden. Dann versucht das Skript, eine Verbindung mit dem MySQL-Server herzustellen, indem es connect() aufruft, um ein Verbindungsobjekt zu erhalten. Die Python-Skripte in diesem Buch verwenden üblicherweise conn, um Verbindungsobjekte zu bezeichnen.

Wenn die Methode connect() fehlschlägt, löst Connector/Python eine Ausnahme aus. Um Ausnahmen zu behandeln, fügst du die Anweisungen, die fehlschlagen könnten, in eine try Anweisung ein und verwendest eine except Klausel, die den Code für die Fehlerbehandlung enthält. Ausnahmen, die auf der obersten Ebene eines Skripts auftreten (d. h. außerhalb einer tryAnweisung), werden vom Standard-Exception-Handler abgefangen, der einen Stack-Trace ausgibt und sich beendet. In Rezept 4.2 wird die Fehlerbehandlung näher erläutert.

Die else Klausel enthält Anweisungen, die ausgeführt werden, wenn die tryKlausel keine Ausnahme erzeugt. Sie wird hier verwendet, um die erfolgreich geöffnete Verbindung zu schließen.

Da der Aufruf connect() benannte Argumente verwendet, spielt ihre Reihenfolge keine Rolle. Wenn du das Argument host beim Aufruf von connect() weglässt, ist der Standardwert 127.0.0.1. Um keine Standarddatenbank auszuwählen, lässt du das Argument databaseweg oder übergibst einen database Wert von "" (die leere Zeichenkette) oder None.

Eine andere Möglichkeit, eine Verbindung herzustellen, besteht darin, die Parameter in einemPython-Wörterbuch anzugeben und das Wörterbuch an connect() zu übergeben:

conn_params = {
  "database": "cookbook",
  "host": "localhost",
  "user": "cbuser",
  "password": "cbpass",
}
conn = mysql.connector.connect(**conn_params)
print("Connected")

Dieses Buch verwendet von nun an generell diesen Stil.

Um den Pfad zu einer Socket-Datei für lokale Host-Verbindungen unter Unix anzugeben, lässt du den Parameter host weg und gibst den Parameter unix_socketan:

conn_params = {
  "database": "cookbook",
  "unix_socket": "/var/tmp/mysql.sock",
  "user": "cbuser",
  "password": "cbpass",
}
conn = mysql.connector.connect(**conn_params)
print("Connected")

Um die Portnummer für TCP/IP-Verbindungen anzugeben, füge den Parameter hosthinzu und gib einen ganzzahligen port Parameter an:

conn_params = {
  "database": "cookbook",
  "host": "127.0.0.1",
  "port": 3307,
  "user": "cbuser",
  "password": "cbpass",
}
conn = mysql.connector.connect(**conn_params)

Geh

Um MySQL-Programme in Go zu schreiben, muss ein Go-SQL-Treiber installiert sein.Dieses Buch verwendet den Go-MySQL-Treiber. Um ihn zu erhalten, wenn er noch nicht installiert ist, installiere Git und gib dann den folgenden Befehl ein:

$ go get -u github.com/go-sql-driver/mysql

Um die Go SQL-Schnittstelle zu verwenden, importiere das Paket database/sql und dein Treiberpaket. Erstelle dann ein Datenbankverbindungsobjekt, indem du die Funktion sql.Open()aufrufst. Dieses Objekt ermöglicht den Zugriff auf andere Funktionen des Pakets database/sql, z. B. die Funktion db.Close(), die die Verbindung zum Datenbankserver schließt. Wir verwenden auch eine deferAnweisung, um die db.Close() aufzurufen, damit der Funktionsaufruf später in der Programmausführung ausgeführt wird. Du wirst diese Verwendung im Laufe dieses Kapitels kennenlernen.

Tipp

Das Go database/sql Paket und der Go-MySQL-Treiber unterstützen Kontextabbrüche. Das bedeutet, dass du Datenbankoperationen, wie zum Beispiel das Ausführen einer Abfrage, abbrechen kannst, wenn du den Kontext aufhebst. Um diese Funktion zu nutzen, musst du die kontextabhängigen Funktionen der Schnittstelle sql aufrufen. Der Kürze halber haben wir Context in unseren Beispielen in diesem Kapitel nicht verwendet. Ein Beispiel für die Verwendung von Context findest du in Rezept 20.9, wenn wir die Transaktionsverarbeitung besprechen.

Das folgende Go-Skript, connect.go, verbindet sich mit dem MySQL-Server, wählt cookbook als Standarddatenbank aus und trennt die Verbindung:

// connect.go: connect to MySQL server
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

func main() {

    db, err := sql.Open("mysql", "cbuser:cbpass@tcp(127.0.0.1:3306)/cookbook")

    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    err = db.Ping()

    if err != nil {
        log.Fatal(err)
    }


    fmt.Println("Connected!")
}

Um connect.go auszuprobieren, suche das Programm im Verzeichnis api/01_connect der recipes Distribution und führe es über die Befehlszeile aus. Das Programm sollte eine einzelne Zeile ausgeben, die anzeigt, dass es eine Verbindung hergestellt hat:

$ go run connect.go
Connected!

Die Zeile import weist Go an, das Paket go-sql-driver/mysql zu laden. Dann validiert das Skript die Verbindungsparameter und erhält ein Verbindungsobjekt durch den Aufruf von sql.Open(). Es wurde noch keine MySQL-Verbindung hergestellt!

Wenn die Methode sql.Open() fehlschlägt, gibt go-sql-driver/mysql einen Fehler zurück. Um den Fehler zu behandeln, speicherst du ihn in einer Variablen (in unserem Beispiel err) und verwendest einen if Block, der den Fehlerbehandlungscode enthält. In Rezept 4.2 wird die Fehlerbehandlung näher erläutert.

Der Aufruf db.Ping() stellt die Datenbankverbindung her. Erst dann können wir sagen, dass wir uns erfolgreich mit dem MySQL-Server verbunden haben.

Um den Pfad zu einer Socket-Datei für lokale Host-Verbindungen unter Unix anzugeben, lässt du den Parameter tcp im DSN weg und gibst den Parameter unixan:

// connect_socket.go : Connect MySQL server using socket
package main

import (
    "database/sql"
    "fmt"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql","cbuser:cbpass@unix(/tmp/mysql.sock)/cookbook")
    defer db.Close()

    if err != nil {
        log.Fatal(err)
    }


    var user string
    err = db.QueryRow("SELECT USER()").Scan(&user)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Connected User:", user, "via MySQL socket")
}

Führe dieses Programm aus:

$ go run connect_socket.go
Connected User: cbuser@localhost via MySQL socket

Um die Portnummer für TCP/IP-Verbindungen anzugeben, füge den Parameter tcpin den DSN ein und gib eine ganzzahlige port Portnummer an:

// connect_tcpport.go : Connect MySQL server using tcp port number
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	db, err := sql.Open("mysql",
	"cbuser:cbpass@tcp(127.0.0.1:3306)/cookbook?charset=utf8mb4")

	if err != nil {
		log.Fatal(err)
	}

	var user string
	err2 := db.QueryRow("SELECT USER()").Scan(&user)

	if err2 != nil {
		log.Fatal(err2)
	}

	fmt.Println("Connected User:", user, "via MySQL TCP/IP localhost on port 3306")
}

Führe dieses Programm aus:

$  go run connect_tcpport.go
Connected User: cbuser@localhost via MySQL TCP/IP localhost on port 3306

Go akzeptiert einen DSN (Data Source Name) in diesem Formular:

[username[:password]@][protocol[(address)]]/dbname[?param1=value1&..&paramN=valueN]

Dabei kann protocol entweder tcp oder unix sein.

Ein DSN in seiner vollständigsten Form sieht folgendermaßen aus:

username:password@protocol(address)/dbname?param=value

Java

Datenbankprogramme in Java verwenden die JDBC-Schnittstelle zusammen mit einem Treiber für die jeweilige Datenbank-Engine, auf die du zugreifen möchtest. Das heißt, die JDBC-Architektur bietet eine allgemeine Schnittstelle, die in Verbindung mit einem datenbankspezifischen Treiber verwendet wird.

Für die Java-Programmierung brauchst du ein Java Development Kit (JDK), und du musst deine Umgebungsvariable JAVA_HOMEauf den Ort setzen, an dem dein JDK installiert ist. Um MySQL-basierte Java-Programme zu schreiben, brauchst du außerdem einen MySQL-spezifischen JDBC-Treiber. Die Programme in diesem Buch verwenden den MySQL Connector/J. Wie du ihn erhältst, wenn er noch nicht installiert ist, findest du im Vorwort. Informationen über die Beschaffung eines JDK und die Einstellung von JAVA_HOME findest du unter cmdline.pdf in der Rezeptverteilung (siehe Vorwort).

Das folgende Java-Programm, Connect.java, verbindet sich mit dem MySQL-Server, wählt cookbook als Standarddatenbank aus und trennt die Verbindung:

// Connect.java: connect to the MySQL server

import java.sql.*;

public class Connect {

  public static void main (String[] args) {
    Connection conn = null;
    String url = "jdbc:mysql://localhost/cookbook";
    String userName = "cbuser";
    String password = "cbpass";

    try {
      conn = DriverManager.getConnection (url, userName, password);
      System.out.println("Connected");
    } catch (Exception e) {
      System.err.println("Cannot connect to server");
      System.exit (1);
    }
    
    if (conn != null) {
      try {
        conn.close();
        System.out.println("Disconnected");
      } catch (Exception e) { /* ignore close errors */ }
    }
  }
}

Um Connect.java auszuprobieren, musst du es im api-Verzeichnis der recipes Distribution finden, kompilieren und ausführen. Die Anweisung class gibt den Namen des Programms an, der in diesem Fall Connect lautet. Der Name der Datei, die das Programm enthält, muss mit diesem Namen übereinstimmen und eine .java-Erweiterung enthalten, also lautet der Dateiname des Programms Connect.java. Kompiliere das Programm mit javac:

$ javac Connect.java

Wenn du einen anderen Java-Compiler bevorzugst, ersetze dessen Namen durch javac.

Der Java-Compiler erzeugt kompilierten Bytecode, um eine Klassendatei mit dem Namen Connect.class zu erstellen. Verwende das Programm java, um die Klassendatei (ohne die Erweiterung .class ) auszuführen. Das Programm sollte zwei Zeilen ausgeben, die anzeigen, dass es die Verbindung erfolgreich hergestellt und getrennt hat:

$ java Connect
Connected
Disconnected

Möglicherweise musst du deine Umgebungsvariable CLASSPATH setzen, bevor das Beispielprogramm kompiliert und ausgeführt werden kann. Der Wert von CLASSPATH sollte mindestens dein aktuelles Verzeichnis (.) und den Pfad zum Connector/J JDBC-Treiber enthalten. Hintergrundinformationen zum Ausführen von Java-Programmen oder zum Setzen von CLASSPATH findest du unter cmdline.pdf in der Rezeptdistribution (siehe Vorwort).

Tipp

Ab Java 11 kannst du den Aufruf javac für ein Einzeldateiprogramm überspringen und es als ausführen:

$ java Connect.java
Connected
Disconnected

Die Anweisung import java.sql.* verweist auf die Klassen und Schnittstellen, die den Zugriff auf die Datentypen ermöglichen, die zur Verwaltung der verschiedenen Aspekte deiner Interaktion mit dem Datenbankserver verwendet werden. Diese sind für alle JDBC-Programme erforderlich.

Um eine Verbindung zum Server herzustellen, rufst du DriverManager.getConnection() auf, um die Verbindung zu initiieren und ein Connection Objekt zu erhalten, das Informationen über den Status der Verbindung enthält. Java-Programme in diesem Buch verwenden üblicherweise conn, um Verbindungsobjekte zu bezeichnen.

DriverManager.getConnection() benötigt drei Argumente: eine URL, die den Ort der Verbindung und die zu verwendende Datenbank beschreibt, den MySQL-Benutzernamen und das Passwort. Die URL-Zeichenkette hat dieses Format:

jdbc:driver://host_name/db_name

Dieses Format folgt der Java-Konvention, dass die URL für die Verbindung zu einer Netzwerkressource mit einem Protokollbezeichner beginnt. Für JDBC-Programme lautet das Protokoll jdbc, und du brauchst außerdem einen Unterprotokollbezeichner, der den Treibernamen angibt (mysql, für MySQL-Programme). Viele Teile der Verbindungs-URL sind optional, aber die führenden Protokoll- und Unterprotokollkennungen sind es nicht. Wenn du auslässt host_nameauslässt, ist der Standardwert für den Host localhost. Um keine Standarddatenbank auszuwählen, lässt du den Datenbanknamen weg. Du solltest aber auf keinen Fall einen der Schrägstriche weglassen. Wenn du dich zum Beispiel mit dem lokalen Host verbinden willst, ohne eine Standarddatenbank auszuwählen, lautet die URL wie folgt:

jdbc:mysql:///

In JDBC testest du Methodenaufrufe nicht auf Rückgabewerte, die einen Fehler anzeigen. Stattdessen stellst du Handler bereit, die aufgerufen werden, wenn Ausnahmen ausgelöst werden. In Rezept 4.2 wird die Fehlerbehandlung näher erläutert.

Bei einigen JDBC-Treibern (z. B. Connector/J) kannst du den Benutzernamen und das Passwort als Parameter am Ende der URL angeben. In diesem Fall lässt du das zweite und dritte Argument beim Aufruf von getConnection() weg. Schreibe den Code, der die Verbindung im Beispielprogramm herstellt, mit diesem URL-Stil wie folgt:

// connect using username and password included in URL
Connection conn = null;
String url = "jdbc:mysql://localhost/cookbook?user=cbuser&password=cbpass";

try
{
  conn = DriverManager.getConnection (url);
  System.out.println ("Connected");
}

Das Zeichen, das die Parameter user und password trennt, sollte & und nicht ; sein.

Connector/J unterstützt von Haus aus keine Unix-Domain-Socket-Dateiverbindungen, so dass selbst Verbindungen, bei denen der Hostname localhost ist, über TCP/IP hergestellt werden. Um eine explizite Portnummer anzugeben, füge :port_num an den Hostnamen in der Verbindungs-URL an:

String url = "jdbc:mysql://127.0.0.1:3307/cookbook";

Du kannst aber auch Bibliotheken von Drittanbietern verwenden, die Verbindungen über einen Socket unterstützen. Weitere Informationen findest du unter "Verbindungen über Unix Domain Sockets" im Referenzhandbuch.

4.2 Prüfen auf Fehler

Problem

Irgendetwas ist bei deinem Programm schief gelaufen und du weißt nicht, was.

Lösung

Jeder hat Probleme damit, Programme richtig zum Laufen zu bringen. Aber wenn du Probleme nicht vorher erkennst, indem du nach Fehlern suchst, wird die Arbeit noch viel schwieriger. Füge einen Code zur Fehlerüberprüfung hinzu, damit deine Programme dir helfen können, herauszufinden, was schief gelaufen ist.

Diskussion

Nachdem du Rezept 4.1 durchgearbeitet hast, weißt du, wie du dich mit dem MySQL-Server verbinden kannst. Es ist auch gut zu wissen, wie man auf Fehler prüft und wie man bestimmte Fehlerinformationen von der API abruft, also behandeln wir das als Nächstes. Wahrscheinlich möchtest du noch mehr interessante Dinge tun (z. B. Anweisungen ausführen und die Ergebnisse zurückbekommen), aber die Fehlerprüfung ist von grundlegender Bedeutung. Programme schlagen manchmal fehl, vor allem während der Entwicklung, und wenn du nicht herausfinden kannst, warum sie fehlschlagen, fliegst du im Blindflug. Plane für das Scheitern, indem du auf Fehler prüfst, damit du entsprechende Maßnahmen ergreifen kannst.

Wenn ein Fehler auftritt, liefert MySQL drei Werte:

  • Eine MySQL-spezifische Fehlernummer

  • Eine MySQL-spezifische beschreibende Text-Fehlermeldung

  • Ein fünfstelliger SQLSTATE-Fehlercode, der gemäß den ANSI- und ODBC-Standards definiert ist

Dieses Rezept zeigt, wie man auf diese Informationen zugreift. Die Beispielprogramme sind absichtlich so konzipiert, dass sie fehlschlagen, damit der Fehlerbehandlungscode ausgeführt wird. Deshalb versuchen sie, sich mit einem Benutzernamen und einem Passwort von baduser und badpass zu verbinden.

Tipp

Eine allgemeine Debugging-Hilfe, die sich nicht auf eine bestimmte API bezieht, ist die Verwendung der verfügbaren Logs. Überprüfe das allgemeine Abfrageprotokoll des MySQL-Servers, um zu sehen, welche Anweisungen der Server empfängt. (Dazu muss dieses Protokoll aktiviert sein; siehe Rezept 22.3.) Das allgemeine Abfrageprotokoll könnte zeigen, dass dein Programm nicht den erwarteten SQL-Anweisungsstring aufbaut. Wenn du ein Skript auf einem Webserver ausführst und es fehlschlägt, solltest du auch das Fehlerprotokoll des Webservers überprüfen.

Perl

Das DBI-Modul bietet zwei Attribute, die steuern, was passiert, wenn DBI-Methodenaufrufe fehlschlagen:

  • PrintErrorWenn diese Option aktiviert ist, gibt DBI eine Fehlermeldung unter warn() aus.

  • RaiseErrorfalls aktiviert, veranlasst DBI, eine Fehlermeldung mit die() zu drucken. Dadurch wird dein Skript beendet.

Standardmäßig ist PrintError aktiviert und RaiseError deaktiviert, so dass ein Skript nach dem Drucken einer Meldung weiter ausgeführt wird, wenn ein Fehler auftritt. In dem Aufruf connect() können eines oder beide Attribute angegeben werden. Wenn du ein Attribut auf 1 oder 0 setzt, wird es aktiviert bzw. deaktiviert. Um eines oder beide Attribute anzugeben, übergibst du sie in einer Hash-Referenz als viertes Argument an den Aufruf connect().

Der folgende Code setzt nur das Attribut AutoCommitund verwendet die Standardeinstellungen für die Fehlerbehandlungsattribute. Wenn der Aufruf von connect() fehlschlägt, wird eine Warnmeldung ausgegeben, aber das Skript wird weiter ausgeführt:

my $conn_attrs = {AutoCommit => 1};
my $dbh = DBI->connect ($dsn, "baduser", "badpass", $conn_attrs);

Da du nicht viel tun kannst, wenn der Verbindungsversuch fehlschlägt, ist es oft ratsam, sich zu beenden, nachdem DBI eine Meldung ausgegeben hat:

my $conn_attrs = {AutoCommit => 1};
my $dbh = DBI->connect ($dsn, "baduser", "badpass", $conn_attrs)
            or exit;

Wenn du deine eigenen Fehlermeldungen ausgeben willst, lass RaiseError deaktiviert und deaktiviere auch PrintError. Teste dann die Ergebnisse der DBI-Methodenaufrufe selbst. Wenn eine Methode fehlschlägt, enthalten die Variablen $DBI::err, $DBI::errstr und $DBI::state die MySQL-Fehlernummer, einen beschreibenden Fehlerstring bzw. den SQLSTATE-Wert:

my $conn_attrs = {PrintError => 0, AutoCommit => 1};
my $dbh = DBI->connect ($dsn, "baduser", "badpass", $conn_attrs)
            or die "Connection error: "
                   . "$DBI::errstr ($DBI::err/$DBI::state)\n";

Wenn kein Fehler auftritt, ist $DBI::err0 oder undef; $DBI::errstr ist der leere String oder undef; und $DBI::state ist leer oder 00000.

Wenn du auf Fehler prüfst, greifst du auf diese Variablen sofort nach dem Aufruf der DBI-Methode zu, die sie setzt. Wenn du eine andere Methode aufrufst, bevor du sie benutzt, setzt DBI ihre Werte zurück.

Wenn du deine eigenen Nachrichten druckst, sind die Standardeinstellungen (PrintError aktiviert, RaiseError deaktiviert) nicht so nützlich. DBI druckt automatisch eine Nachricht und dein Skript druckt dann seine eigene Nachricht. Das ist nicht nur redundant, sondern auch verwirrend für die Person, die das Skript benutzt.

Wenn du RaiseError aktivierst, kannst du DBI-Methoden aufrufen, ohne auf Rückgabewerte zu prüfen, die auf Fehler hinweisen. Wenn eine Methode fehlschlägt, gibt DBI einen Fehler aus und beendet dein Skript. Wenn die Methode zurückkehrt, kannst du davon ausgehen, dass sie erfolgreich war. Das ist der einfachste Ansatz für Skriptschreiber: Lass DBI die Fehlerprüfung übernehmen! Wenn jedoch sowohl PrintError als auch RaiseError aktiviert sind, kann es passieren, dass DBI warn() und die() nacheinander aufruft, was dazu führt, dass Fehlermeldungen doppelt ausgegeben werden. Um dieses Problem zu vermeiden, deaktiviere PrintError, wenn du RaiseError aktivierst:

my $conn_attrs = {PrintError => 0, RaiseError => 1, AutoCommit => 1};
my $dbh = DBI->connect ($dsn, "baduser", "badpass", $conn_attrs);

In diesem Buch wird dieser Ansatz generell verwendet. Wenn du nicht das Alles-oder-Nichts-Verhalten haben willst, indem du RaiseError für die automatische Fehlerprüfung aktivierst, während du alle Prüfungen selbst durchführen musst, wähle einen gemischten Ansatz. Einzelne Handles haben PrintErrorund RaiseError Attribute, die selektiv aktiviert oder deaktiviert werden können. Du kannst zum Beispiel RaiseError global aktivieren, indem du es beim Aufruf von connect() einschaltest, und es dann selektiv für jeden Handle deaktivieren.

Angenommen, ein Skript liest den Benutzernamen und das Passwort aus den Kommandozeilenargumenten und führt dann eine Schleife aus, während der Benutzer Anweisungen eingibt, die ausgeführt werden sollen. In diesem Fall möchtest du wahrscheinlich, dass DBI automatisch stirbt und eine Fehlermeldung ausgibt, wenn die Verbindung fehlschlägt (in diesem Fall kannst du nicht mit der Schleife zur Ausführung der Anweisungen fortfahren). Nach der Verbindung möchtest du jedoch nicht, dass das Skript beendet wird, nur weil der Benutzer eine syntaktisch ungültige Anweisung eingibt. Stattdessen solltest du eine Fehlermeldung ausgeben und eine Schleife für die nächste Anweisung einrichten. Der folgende Code zeigt, wie das geht. Die im Beispiel verwendete Methode do() führt eine Anweisung aus und gibt undef zurück, um einen Fehler anzuzeigen:

my $user_name = shift (@ARGV);
my $password = shift (@ARGV);
my $conn_attrs = {PrintError => 0, RaiseError => 1, AutoCommit => 1};
my $dbh = DBI->connect ($dsn, $user_name, $password, $conn_attrs);
$dbh->{RaiseError} = 0; # disable automatic termination on error
print "Enter statements to execute, one per line; terminate with Control-D\n";
while (<>)              # read and execute queries
{
  $dbh->do ($_) or warn "Statement failed: $DBI::errstr ($DBI::err)\n";
}

Wenn RaiseError aktiviert ist, kannst du Code innerhalb eines evalBlocks ausführen, um Fehler abzufangen, ohne dein Programm zu beenden. Wenn ein Fehler auftritt, gibt eval eine Meldung in der Variable $@ zurück:

eval
{
  # statements that might fail go here...
};
if ($@)
{
  print "An error occurred: $@\n";
}

Diese eval Technik wird häufig verwendet, um Transaktionen durchzuführen (siehe Rezept 20.4).

Die Verwendung von RaiseError in Kombination mit eval unterscheidet sich von der Verwendung von RaiseError allein:

  • Bei Fehlern wird nur der eval Block beendet, nicht das gesamte Skript.

  • Jeder Fehler beendet den eval Block, während RaiseError nur für DBI-bezogene Fehler gilt.

Wenn du eval mit RaiseError verwendest, solltest du PrintError deaktivieren. Andernfalls kann ein Fehler in einigen Versionen von DBI dazu führen, dass warn() einfach aufgerufen wird, ohne dass der eval Block wie erwartet beendet wird.

Zusätzlich zu den Fehlerbehandlungsattributen PrintError und RaiseError stehen dir mit dem DBI-Tracing-Mechanismus viele Informationen über die Ausführung deines Skripts zur Verfügung. Rufe die Methode trace() mit einem Argument auf, das den Trace-Level angibt. Die Stufen 1 bis 9 aktivieren das Tracing mit zunehmend ausführlicherer Ausgabe, während Stufe 0 das Tracing deaktiviert:

DBI->trace (1); # enable tracing, minimal output
DBI->trace (3); # elevate trace level
DBI->trace (0); # disable tracing

Einzelne Datenbank- und Anweisungs-Handles haben auch trace() Methoden, so dass du das Tracing auf ein einzelnes Handle beschränken kannst, wenn du willst.

Die Trace-Ausgabe geht normalerweise in dein Terminal (oder, im Falle eines Webskripts, in das Fehlerprotokoll des Webservers). Um die Trace-Ausgabe in eine bestimmte Datei zu schreiben, gibst du ein zweites Argument an, das den Dateinamen angibt:

DBI->trace (1, "/tmp/trace.out");

Wenn die Trace-Datei bereits existiert, wird ihr Inhalt nicht zuerst gelöscht, sondern die Trace-Ausgabe wird an das Ende angehängt. Hüte dich davor, bei der Entwicklung eines Skripts einen Datei-Trace einzuschalten, aber zu vergessen, den Trace zu deaktivieren, wenn du das Skript in Produktion gibst. Zu deinem Leidwesen wirst du dann feststellen, dass die Trace-Datei ziemlich groß geworden ist. Oder schlimmer noch, ein Dateisystem füllt sich und du hast keine Ahnung, warum!

Ruby

Ruby signalisiert Fehler, indem es Ausnahmen auslöst, und Ruby-Programme behandelnFehler, indem sie Ausnahmen in einer rescue Klausel eines begin Blocks abfangen. Ruby-Mysql2-Methoden lösen Ausnahmen aus, wenn sie fehlschlagen, und liefern Fehlerinformationen über ein Mysql2::Error -Objekt. Um die MySQL-Fehlernummer, die Fehlermeldung und den SQLSTATE-Wert zu erhalten, greifst du auf die Methoden errno, message und sql_state dieses Objekts zu. Das folgende Beispiel zeigt, wie du in einem Ruby-Skript Ausnahmen abfangen und Fehlerinformationen abrufen kannst:

begin
  client = Mysql2::Client.new(:host => "localhost",
                              :username => "baduser",
                              :password => "badpass",
                              :database => "cookbook")
  puts "Connected"
rescue Mysql2::Error => e
  puts "Cannot connect to server"
  puts "Error code: #{e.errno}"
  puts "Error message: #{e.message}"
  puts "Error SQLSTATE: #{e.sql_state}"
  exit(1)
ensure
  client.close()s
end

PHP

Der Konstruktor new PDO() löst eine Ausnahme aus, wenn er fehlschlägt, aber andere PDO-Methodenzeigen den Erfolg oder Misserfolg standardmäßig durch ihren Rückgabewert an. Damit alle PDO-Methoden bei Fehlern eine Ausnahme auslösen, kannst du das Datenbank-Handle, das bei einem erfolgreichen Verbindungsversuch entsteht, verwenden, um den Modus für die Fehlerbehandlung festzulegen. Dies ermöglicht eine einheitliche Behandlung aller PDO-Fehler, ohne dass das Ergebnis jedes Aufrufs überprüft werden muss. Das folgende Beispiel zeigt, wie der Fehlermodus gesetzt wird, wenn der Verbindungsversuch erfolgreich ist, und wie Ausnahmen behandelt werden, wenn er fehlschlägt:

try
{
  $dsn = "mysql:host=localhost;dbname=cookbook";
  $dbh = new PDO ($dsn, "baduser", "badpass");
  $dbh->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  print ("Connected\n");
}
catch (PDOException $e)
{
  print ("Cannot connect to server\n");
  print ("Error code: " . $e->getCode () . "\n");
  print ("Error message: " . $e->getMessage () . "\n");
}

Wenn PDO eine Ausnahme auslöst, liefert das resultierende PDOException Objekt Fehlerinformationen. Die Methode getCode() gibt den SQLSTATE-Wert zurück. Die Methode getMessage() gibt einen String zurück, der den SQLSTATE-Wert, die MySQL-Fehlernummer und die Fehlermeldung enthält.

Datenbank- und Anweisungs-Handles liefern auch Informationen, wenn ein Fehler auftritt. Für beide Handle-Typen gibt errorCode() den SQLSTATE-Wert zurück, und errorInfo() liefert ein Array mit drei Elementen, das den SQLSTATE-Wert sowie einen treiberspezifischen Fehlercode und eine Meldung enthält. Bei MySQL sind die beiden letzteren Werte die Fehlernummer und der Meldungsstring. Das folgende Beispiel zeigt, wie man Informationen aus dem Ausnahmeobjekt und dem Datenbank-Handle erhält:

try
{
  $dbh->query ("SELECT"); # malformed query
}
catch (PDOException $e)
{
  print ("Cannot execute query\n");
  print ("Error information using exception object:\n");
  print ("SQLSTATE value: " . $e->getCode () . "\n");
  print ("Error message: " . $e->getMessage () . "\n");

  print ("Error information using database handle:\n");
  print ("Error code: " . $dbh->errorCode () . "\n");
  $errorInfo = $dbh->errorInfo ();
  print ("SQLSTATE value: " . $errorInfo[0] . "\n");
  print ("Error number: " . $errorInfo[1] . "\n");
  print ("Error message: " . $errorInfo[2] . "\n");
}

Python

Python signalisiert Fehler durch das Auslösen von Exceptions, und Python-Programme behandelnFehler, indem sie Exceptions in der except Klausel einer try Anweisung abfangen. Um MySQL-spezifische Fehlerinformationen zu erhalten, benenne eine Ausnahmeklasse und gib eine Variable an, die die Informationen erhält. Hier ist ein Beispiel:

conn_params = {
  "database": "cookbook",
  "host": "localhost",
  "user": "baduser",
  "password": "badpass"
}

try:
  conn = mysql.connector.connect(**conn_params)
  print("Connected")
except mysql.connector.Error as e:
  print("Cannot connect to server")
  print("Error code: %s" % e.errno)
  print("Error message: %s" % e.msg)
  print("Error SQLSTATE: %s" % e.sqlstate)

Wenn eine Ausnahme auftritt, enthalten die Mitglieder errno, msg und sqlstate des Ausnahmeobjekts die Fehlernummer, die Fehlermeldung bzw. die SQLSTATE-Werte. Beachte, dass der Zugriff auf die Klasse Error über den Namen des Treibermoduls erfolgt.

Geh

Go unterstützt keine Ausnahmen. Stattdessen machen es die mehrwertigen Rückgaben einfach, bei Bedarf einen Fehler zu übergeben. Um Fehler in Go zu behandeln, speicherst du den zurückgegebenen Wert vom Typ Error in einer Variablen (wir verwenden hier den Variablennamen err ) und behandelst ihn entsprechend. Zur Behandlung von Fehlern bietet Go die Anweisung defer sowie die eingebauten Funktionen Panic() und Recover(), die in Tabelle 4-2 dargestellt sind.

Tabelle 4-2. Fehlerbehandlung in Go
Funktion oder AnweisungBedeutung
deferVerschiebt die Ausführung der Anweisung, bis die aufrufende Funktion zurückkehrt.
Panic()Die normale Ausführung der aufrufenden Funktion wird angehalten, alle aufgeschobenen Funktionen werden ausgeführt, dann gibt die Funktion einen Aufruf zur Panik auf dem Stapel zurück. Der Prozess wird fortgesetzt. Schließlich stürzt das Programm ab.
Recover()Ermöglicht es dir, die Kontrolle über die in Panik geratene Goroutine zurückzugewinnen, damit das Programm nicht abstürzt und weiter ausgeführt wird. Dies funktioniert nur in den aufgeschobenen Funktionen. Wenn sie in einer Funktion aufgerufen wird, die nicht aufgeschoben ist, tut sie nichts und gibt nil zurück.
// mysql_error.go : MySQL error handling
package main

import (
    "database/sql"
    "log"
    "fmt"

    _ "github.com/go-sql-driver/mysql"
)

var actor string

func main() {

    db, err := sql.Open("mysql", "cbuser:cbpass!@tcp(127.0.0.1:3306)/cookbook")
    defer db.Close()

    if err != nil {
        log.Fatal(err)
    }

    err = db.QueryRow("SELECT actor FROM actors where actor='Dwayne Johnson'").
          Scan(&actor)
    if err != nil {
	    if err == sql.ErrNoRows {
		    fmt.Print("There were no rows, but otherwise no error occurred")
	    } else {
		    fmt.Println(err.Error())
	    }
    }
    fmt.Println(actor)
}

Wenn ein Fehler auftritt, gibt die Funktion ein Objekt vom Typ error zurück. Die Funktion Error() gibt einen MySQL-Fehlercode und eine Nachricht für die Fehler zurück, die von Go-MySQL-Driver ausgelöst wurden.

Es gibt einen Ausnahmefall für die Funktion QueryRow() mit dem nachfolgenden Aufruf Scan(). Standardmäßig gibt Scan() nil zurück, wenn kein Fehler aufgetreten ist, und error, wenn ein Fehler vorliegt. Wenn die Abfrage jedoch erfolgreich ausgeführt wurde, aber keine Zeilen zurückgegeben hat, gibt diese Funktion sql.ErrNoRows zurück.

Java

Java-Programme behandeln Fehler, indem sie Ausnahmen abfangen. Um das Minimuman Arbeit zu erledigen, gibst du einen Stack-Trace aus, um den Benutzer zu informieren, wo das Problem liegt:

try
{
  /* ... some database operation ... */
}
catch (Exception e)
{
  e.printStackTrace ();
}

Der Stack-Trace zeigt, wo das Problem liegt, aber nicht unbedingt, was das Problem war. Außerdem ist er möglicherweise nur für dich, den Entwickler des Programms, aussagekräftig. Um genauer zu sein, solltest du die Fehlermeldung und den Code ausgeben, die mit einer Ausnahme verbunden sind:

  • Alle Exception Objekte unterstützen die Methode getMessage(). JDBC-Methoden können Ausnahmen mit Hilfe von SQLException -Objekten auslösen; diese sind wie Exception -Objekte, unterstützen aber auch die Methoden getErrorCode() und getSQLState(). getErrorCode() und getMessage() geben die MySQL-spezifische Fehlernummer und den Meldungsstring zurück, und getSQLState()gibt einen String zurück, der den SQLSTATE-Wert enthält.

  • Einige Methoden erzeugen SQLWarningObjekte, um Informationen über nicht tödliche Warnungen bereitzustellen. SQLWarning ist eine Unterklasse von SQLException, aber Warnungen werden in einer Liste gesammelt und nicht sofort ausgelöst. Sie unterbrechen dein Programm nicht und du kannst sie in aller Ruhe ausdrucken.

Das folgende Beispielprogramm Error.java zeigt, wie man auf Fehlermeldungen zugreifen kann, indem es alle verfügbaren Fehlerinformationen ausgibt. Es versucht, eine Verbindung zum MySQL-Server herzustellen und gibt Ausnahmeinformationen aus, wenn der Versuch fehlschlägt. Dann führt es eine Anweisung aus und gibt Ausnahme- und Warninformationen aus, wenn die Anweisung fehlschlägt:

// Error.java: demonstrate MySQL error handling

import java.sql.*;

public class Error {
  public static void main(String[] args) {
    Connection conn = null;
    String url = "jdbc:mysql://localhost/cookbook";
    String userName = "baduser";
    String password = "badpass";

    try {
      conn = DriverManager.getConnection(url, userName, password);
      System.out.println("Connected");
      tryQuery(conn);    // issue a query
    } catch (Exception e) {
      System.err.println("Cannot connect to server");
      System.err.println(e);
      if (e instanceof SQLException)  // JDBC-specific exception?
      {
        // e must be cast from Exception to SQLException to
        // access the SQLException-specific methods
        printException((SQLException) e);
      }
    } finally {
      if (conn != null) {
        try {
          conn.close ();
          System.out.println("Disconnected");
        } catch (SQLException e) {
          printException (e);
        }
      }
    }
  }

  public static void tryQuery(Connection conn) {
    try {
      // issue a simple query
      Statement s = conn.createStatement();
      s.execute("USE cookbook");
      s.close();

      // print any accumulated warnings
      SQLWarning w = conn.getWarnings();
      while (w != null) {
        System.err.println("SQLWarning: " + w.getMessage());
        System.err.println("SQLState: " + w.getSQLState());
        System.err.println("Vendor code: " + w.getErrorCode());
        w = w.getNextWarning();
      }
    } catch (SQLException e) {
      printException(e);
    }
  }

  public static void printException(SQLException e) {
    // print general message, plus any database-specific message
    System.err.println("SQLException: " + e.getMessage ());
    System.err.println("SQLState: " + e.getSQLState ());
    System.err.println("Vendor code: " + e.getErrorCode ());
  }
}

4.3 Bibliotheksdateien schreiben

Problem

Du bemerkst, dass du Code wiederholst, um gemeinsame Operationen in mehrerenProgrammen durchzuführen.

Lösung

Schreibe Routinen, die diese Operationen ausführen, lege sie in einer Bibliotheksdatei ab und sorge dafür, dass deine Programme auf die Bibliothek zugreifen können. So musst du den Code nur einmal schreiben. Möglicherweise musst du eine Umgebungsvariable setzen, damit deine Skripte die Bibliothek finden können.

Diskussion

In diesem Abschnitt wird beschrieben, wie man Code für gängige Operationen in Bibliotheksdateien ablegt. Die Kapselung (oder Modularisierung) ist nicht wirklich ein Rezept, sondern eine Programmiertechnik. Ihr Hauptvorteil ist, dass du den Code nicht in jedem Programm, das du schreibst, wiederholen musst. Stattdessen rufst du einfach eine Routine auf, die in der Bibliothek enthalten ist. Wenn du zum Beispiel den Code für die Verbindung zur Datenbank cookbook in eine Bibliotheksroutine einbaust, musst du nicht alle Parameter für die Verbindung eingeben. Du rufst die Routine einfach aus deinem Programm heraus auf, und schon ist die Verbindung hergestellt.

Der Verbindungsaufbau ist natürlich nicht der einzige Vorgang, den du kapseln kannst. In späteren Abschnitten dieses Buches werden weitere Funktionen entwickelt, die in Bibliotheksdateien untergebracht werden können. Alle diese Dateien, auch die in diesem Abschnitt gezeigten, befinden sich im lib-Verzeichnis der recipes Distribution. Wenn du deine eigenen Programme schreibst, halte Ausschau nach Operationen, die du häufig ausführst und die sich gut für die Aufnahme in eine Bibliothek eignen. Nutze die Techniken in diesem Abschnitt, um deine eigenen Bibliotheksdateien zu schreiben.

Bibliotheksdateien haben nicht nur den Vorteil, dass sie das Schreiben von Programmen erleichtern, sondern auch die Portabilität fördern. Wenn du die Verbindungsparameter direkt in jedes Programm schreibst, das sich mit dem MySQL-Server verbindet, musst du alle diese Programme ändern, wenn du sie auf einen anderen Rechner verschiebst, der andere Parameter verwendet. Wenn du stattdessen deine Programme so schreibst, dass sie sich durch den Aufruf einer Bibliotheksroutine mit der Datenbank verbinden, musst du nur die betroffene Bibliotheksroutine ändern, nicht aber alle Programme, die sie verwenden.

Codekapselung kann auch die Sicherheit verbessern. Wenn du eine private Bibliotheksdatei nur für dich selbst lesbar machst, können nur von dir ausgeführte Skripte Routinen in der Datei ausführen. Oder nimm an, du hast einige Skripte im Dokumentenbaum deines Webservers gespeichert. Ein richtig konfigurierter Server führt die Skripte aus und sendet ihre Ausgabe an entfernte Clients. Wenn der Server aber irgendwie falsch konfiguriert ist, kann das dazu führen, dass er deine Skripte im Klartext an die Clients sendet und so deinen MySQL-Benutzernamen und dein Passwort anzeigt. Wenn du den Code für den Verbindungsaufbau mit dem MySQL-Server in einer Bibliotheksdatei außerhalb des Dokumentenbaums platzierst, sind diese Parameter für die Clients nicht sichtbar.

Warnung

Wenn du eine Bibliotheksdatei so installierst, dass sie von deinem Webserver gelesen werden kann, hast du keine große Sicherheit, wenn andere Entwickler denselben Server verwenden. Jeder dieser Entwickler kann ein Webskript schreiben, das deine Bibliotheksdatei liest und anzeigt, weil das Skript standardmäßig mit den Rechten des Webservers läuft und somit Zugriff auf die Bibliothek hat.

Die folgenden Rezepte zeigen, wie man für jede API eine Bibliotheksdatei schreibt, die eine Routine für die Verbindung zur Datenbank cookbook auf dem MySQL-Server enthält. Das aufrufende Programm kann die in Rezept 4.2 besprochenen Techniken zur Fehlerprüfung verwenden, um festzustellen, ob ein Verbindungsversuch fehlschlägt. Die Verbindungsroutine für jede Sprache gibt ein Datenbank-Handle oder ein Verbindungsobjekt zurück, wenn sie erfolgreich ist, oder löst eine Ausnahme aus, wenn die Verbindung nicht hergestellt werden kann.

Die Bibliotheken sind für sich genommen nicht nützlich, daher wird auf die Verwendung jeder Bibliothek anhand eines kurzen Testprogramms erläutert. Wenn du eines dieser Harness-Programme als Grundlage für die Erstellung neuer Programme verwenden willst, kopiere die Datei und füge deinen eigenen Code zwischen den Aufrufen zum Verbinden und Trennen ein.

Beim Schreiben von Bibliotheksdateien geht es nicht nur um die Frage, was in die Datei geschrieben werden soll, sondern auch darum, wo die Datei installiert wird, damit deine Programme auf sie zugreifen können, und (auf Mehrbenutzersystemen wie Unix) wie die Zugriffsrechte gesetzt werden, damit der Inhalt nicht von Personen eingesehen werden kann, die ihn nicht sehen sollen.

Auswahl eines Installationsortes für die Bibliotheksdateien

Wenn du eine Bibliotheksdatei in einem Verzeichnis installierst, das ein Sprachprozessor standardmäßig durchsucht, müssen Programme, die in dieser Sprache geschrieben sind, nichts Besonderes tun, um auf die Bibliothek zuzugreifen. Wenn du jedoch eine Bibliotheksdatei in einem Verzeichnis installierst, das der Sprachprozessor nicht standardmäßig durchsucht, musst du deinen Skripten mitteilen, wie sie zu finden ist. Es gibt zwei gängige Möglichkeiten, dies zu tun:

  • Die meisten Sprachen bieten eine Anweisung, die in einem Skript verwendet werden kann, um Verzeichnisse zum Suchpfad des Sprachprozessors hinzuzufügen. Dazu musst du jedes Skript ändern, das die Bibliothek benötigt.

  • Du kannst eine Umgebungs- oder Konfigurationsvariable setzen, die den Suchpfad des Sprachprozessors ändert. Bei diesem Ansatz muss jeder Benutzer, der Skripte ausführt, die die Bibliothek benötigen, die entsprechende Variable setzen. Wenn der Sprachprozessor über eine Konfigurationsdatei verfügt, kannst du alternativ auch einen Parameter in der Datei setzen, der sich global auf alle Skripte auswirkt.

Wir werden den zweiten Ansatz verwenden. Für unsere API-Sprachen Tabelle 4-3 zeigt die entsprechenden Variablen. In jedem Fall ist der Wert der Variablen ein Verzeichnis oder eine Liste von Verzeichnissen.

Tabelle 4-3. Standardmäßige Bibliothekspfade
SpracheName der VariableVariable Art
PerlPERL5LIB Umgebungsvariable
RubyRUBYLIB Umgebungsvariable
PHPinclude_path Konfigurationsvariable
PythonPYTHONPATH Umgebungsvariable
GehGOPATH Umgebungsvariable
JavaCLASSPATH Umgebungsvariable

Allgemeine Informationen zum Setzen von Umgebungsvariablen findest du untercmdline.pdf in der Rezepte-Distribution (siehe Vorwort). Du kannst diese Anweisungen nutzen, um die Umgebungsvariablen auf die Werte in der folgenden Diskussion zu setzen.

Angenommen, du möchtest Bibliotheksdateien in einem Verzeichnisinstallieren, das Sprachprozessoren standardmäßig nicht durchsuchen. Zur Veranschaulichung nehmen wir /usr/local/lib/mcb unter Unix und C:\lib\mcb unter Windows. (Wenn du die Dateien woanders ablegen willst, musst du die Pfadnamen in den Variableneinstellungen entsprechend anpassen. Du könntest zum Beispiel ein anderes Verzeichnis verwenden oder die Bibliotheken für jede Sprache in einem anderen Verzeichnis ablegen).

Wenn du unter Unix Perl-Bibliotheksdateien in das Verzeichnis /usr/local/lib/mcb legst, setze die UmgebungsvariablePERL5LIB entsprechend. Für eine Shell aus der Bourne-Shell-Familie (sh, bash, ksh) setzt du die Variable in der entsprechenden Startdatei wie folgt:

export PERL5LIB=/usr/local/lib/mcb
Hinweis

Für die ursprüngliche Bourne-Shell, sh, musst du diesen Befehl eventuell in zwei aufteilen:

PERL5LIB=/usr/local/lib/mcb
export PERL5LIB

Für eine Shell in der C-Shell-Familie (csh, tcsh), setze PERL5LIB wie folgt in deiner .login-Datei:

setenv PERL5LIB /usr/local/lib/mcb

Wenn du unter Windows Perl-Bibliotheksdateien in C:\lib\mcb ablegst, setze PERL5LIB wie folgt:

PERL5LIB=C:\lib\mcb

In jedem Fall weist der Variablenwert Perl an, in dem angegebenen Verzeichnis nach Bibliotheksdateien zu suchen, zusätzlich zu allen anderen Verzeichnissen, die es standardmäßig durchsucht. Wenn du PERL5LIB so einstellst, dass mehrere Verzeichnisse benannt werden, ist das Trennzeichen zwischen den Verzeichnispfadnamen ein Doppelpunkt (:) unter Unix oder ein Semikolon (;) unter Windows.

Gib die anderen Umgebungsvariablen (RUBYLIB, PYTHONPATH und CLASSPATH) mit der gleichen Syntax an.

Hinweis

Für Skripte, die du von der Kommandozeile aus ausführst, sollte es ausreichen, diese Umgebungsvariablen wie eben beschrieben zu setzen. Für Skripte, die von einem Webserver ausgeführt werden sollen, musst du wahrscheinlich auch den Server so konfigurieren, dass er die Bibliotheksdateien finden kann.

Bei PHP wird der Suchpfad durch den Wert der Variable include_path in der PHP-Initialisierungsdatei php.ini festgelegt. Unter Unix lautet der Pfadname der Datei wahrscheinlich /usr/lib/php.ini oder /usr/local/lib/php.ini. Unter Windows befindet sich die Datei wahrscheinlich im Windows-Verzeichnis oder im Hauptverzeichnis der PHP-Installation. Um den Speicherort zu ermitteln, führe diesen Befehl aus:

$ php --ini

Definiere den Wert von include_path in der php.ini mit einer Zeile wie dieser:

include_path = "value"

Gib an. value mit der gleichen Syntax wie für Umgebungsvariablen, die Verzeichnisse benennen. Das heißt, es handelt sich um eine Liste von Verzeichnisnamen, wobei die Namen unter Unix durch Doppelpunkte und unter Windows durch Semikolons getrennt sind. Wenn du unter Unix möchtest, dass PHP im aktuellen Verzeichnis und in /usr/local/lib/mcb nach eingebundenen Dateien sucht, setze include_path wie folgt:

include_path = ".:/usr/local/lib/mcb"

Unter Windows kannst du das aktuelle Verzeichnis und C:\lib\mcb durchsuchen, indem du include_path wie folgt einstellst:

include_path = ".;C:\lib\mcb"

Wenn PHP als Apache-Modul ausgeführt wird, starte Apache neu, damit die Änderungen in der php.ini wirksam werden.

Zugriffsrechte für Bibliotheksdateien festlegen

Wenn du ein Mehrbenutzersystem wie Unix verwendest, musst du Entscheidungen über den Besitz und den Zugriffsmodus von Bibliotheksdateien treffen:

  • Wenn eine Bibliotheksdatei privat ist und Code enthält, der nur von dir verwendet werden soll, platziere die Datei unter deinem eigenen Konto und mache sie nur für dich zugänglich. Angenommen, eine Bibliotheksdatei namens mylib gehört dir bereits, dann kannst du sie wie folgt privat machen:

    $ chmod 600 mylib
  • Wenn die Bibliotheksdatei nur von deinem Webserver verwendet werden soll, installiere sie in einem Bibliotheksverzeichnis des Servers und stelle sicher, dass sie nur der Benutzer-ID des Servers gehört und nur diese darauf zugreifen kann. Möglicherweise musst du root sein, um das zu tun. Wenn der Webserver z. B. als wwwusr läuft, machen die folgenden Befehle die Datei für diesen Benutzer privat:

    # chown wwwusr mylib
    # chmod 600 mylib
  • Wenn die Bibliotheksdatei öffentlich ist, kannst du sie an einem Ort ablegen, den deine Programmiersprache automatisch durchsucht, wenn sie nach Bibliotheken sucht. (Die meisten Sprachprozessoren suchen in einer Reihe von Standardverzeichnissen nach Bibliotheken, obwohl diese durch das Setzen von Umgebungsvariablen, wie oben beschrieben, beeinflusst werden können). Möglicherweise musst du root aufrufen, um Dateien in einem dieser Verzeichnisse zu installieren. Dann kannst du die Datei für die Welt lesbar machen:

    # chmod 444 mylib

Nun wollen wir für jede API eine Bibliothek erstellen. In jedem Abschnitt wird gezeigt, wie man die Bibliotheksdatei selbst schreibt und wie man die Bibliothek in den Programmen verwendet.

Perl

In Perl werden Bibliotheksdateien als Module bezeichnet und haben in der Regel die Endung .pm(Perl-Modul). Üblicherweise ist der Basisname einer Moduldatei derselbe wie der Bezeichner in der Zeile package in der Datei. Die folgende Datei, Cookbook.pm, implementiert ein Modul namens Cookbook:

package Cookbook;
# Cookbook.pm: library file with utility method for connecting to MySQL
# using the Perl DBI module

use strict;
use warnings;
use DBI;

my $db_name = "cookbook";
my $host_name = "localhost";
my $user_name = "cbuser";
my $password = "cbpass";
my $port_num = undef;
my $socket_file = undef;

# Establish a connection to the cookbook database, returning a database
# handle.  Raise an exception if the connection cannot be established.

sub connect
{
my $dsn = "DBI:mysql:host=$host_name";
my $conn_attrs = {PrintError => 0, RaiseError => 1, AutoCommit => 1};

  $dsn .= ";database=$db_name" if defined ($db_name);
  $dsn .= ";mysql_socket=$socket_file" if defined ($socket_file);
  $dsn .= ";port=$port_num" if defined ($port_num);

  return DBI->connect ($dsn, $user_name, $password, $conn_attrs);
}

1;  # return true

Das Modul kapselt den Code zum Herstellen einer Verbindung zum MySQL-Server in einer connect() Methode, und der Bezeichner package legt einen Cookbook Namensraum für das Modul fest. Um die Methode connect() aufzurufen, verwendest du den Modulnamen:

$dbh = Cookbook::connect ();

Die letzte Zeile der Moduldatei ist eine Anweisung, die trivialerweise als wahr ausgewertet wird. (Wenn das Modul keinen wahren Wert zurückgibt, nimmt Perl an, dass etwas nicht stimmt und beendet sich).

Perl sucht nach Bibliotheksdateien, indem es die Liste der Verzeichnissein seinem @INC Array durchsucht. Um den Standardwert dieser Variable auf deinem System zu überprüfen, rufst du Perl wie folgt in der Befehlszeile auf:

$ perl -V

Der letzte Teil der Ausgabe des Befehls zeigt die Verzeichnisse von, die in @INC aufgelistet sind. Wenn du eine Bibliotheksdatei in einem dieser Verzeichnisse installierst, werden deine Skripte sie automatisch finden. Wenn du das Modul an einem anderen Ort installierst, musst du deinen Skripten mitteilen, wo es zu finden ist, indem du die Umgebungsvariable PERL5LIBsetzt, wie im einleitenden Teil dieses Rezepts beschrieben.

Nachdem du das Modul Cookbook.pm installiert hast, kannst du es in einem Test-Harness-Skript ausprobieren: harness.pl:

#!/usr/bin/perl
# harness.pl: test harness for Cookbook.pm library

use strict;
use warnings;
use Cookbook;

my $dbh;
eval
{
  $dbh = Cookbook::connect ();
  print "Connected\n";
};
die "$@" if $@;
$dbh->disconnect ();
print "Disconnected\n";

harness.pl hat keine use DBI Anweisung. Sie ist unnötig, weil das Modul Cookbook selbst DBI importiert; jedes Skript, das Cookbook verwendet, hat auch Zugriff auf DBI.

Wenn du Verbindungsfehler nicht explizit mit eval abfängst, kannst du den Skriptkörper einfacher schreiben:

my $dbh = Cookbook::connect ();
print "Connected\n";
$dbh->disconnect ();
print "Disconnected\n";

In diesem Fall fängt Perl jede Verbindungsausnahme ab und beendet das Skript, nachdem es die von der Methode connect()erzeugte Fehlermeldung ausgegeben hat.

Ruby

Die folgende Datei der Ruby-Bibliothek, Cookbook.rb, definiert eine Klasse Cookbook, die eine Methode connect implementiert:

# Cookbook.rb: library file with utility method for connecting to MySQL
# using the Ruby Mysql2 module

require "mysql2"

# Establish a connection to the cookbook database, returning a database
# handle.  Raise an exception if the connection cannot be established.

class Cookbook
  @@host_name = "localhost"
  @@db_name = "cookbook"
  @@user_name = "cbuser"
  @@password = "cbpass"

  # Class method for connecting to server to access the
  # cookbook database; returns a database handle object.

  def Cookbook.connect
    return Mysql2::Client.new(:host => @@host_name,
                              :database => @@db_name,
                              :username => @@user_name, 
                              :password => @@password)
  end
end

Die Methode connect ist in der Bibliothek als Cookbook.connectdefiniert, weil die Methoden der Ruby-Klassen wie folgt definiert sind class_name.method_name.

Ruby sucht nach Bibliotheksdateien, indem es die Liste der Verzeichnissedurchsucht, die in der Variable $LOAD_PATH(auch bekannt als $:), die ein Array ist, genannt werden. Um den Standardwert dieser Variable auf deinem System zu überprüfen, führe diese Anweisung mit dem interaktiven Ruby aus:

$ irb
>> puts $LOAD_PATH

Wenn du eine Bibliotheksdatei in einem dieser Verzeichnisse installierst, werden deineSkripte sie automatisch finden. Wenn du die Datei an einem anderen Ort installierst, musst du deinen Skripten mitteilen, wo sie zu finden ist, indem du die Umgebungsvariable RUBYLIBsetzt, wie im einleitenden Teil dieses Rezepts beschrieben.

Nachdem du die Cookbook.rb Bibliotheksdatei installiert hast, kannst du sie in einem Test-Harness-Skript ausprobieren, harness.rb:

#!/usr/bin/ruby -w
# harness.rb: test harness for Cookbook.rb library

require "Cookbook"

begin
  client = Cookbook.connect
  print "Connected\n"
rescue Mysql2::Error => e
  puts "Cannot connect to server"
  puts "Error code: #{e.errno}"
  puts "Error message: #{e.message}"
  exit(1)
ensure
  client.close()
  print "Disconnected\n"
end

harness.rb hat keine require Anweisung für das Modul Mysql2. Das ist unnötig, weil das Modul Cookbookselbst Mysql2 importiert; jedes Skript, das Cookbook importiert, hat auch Zugriff auf Mysql2.

Wenn du möchtest, dass ein Skript bei einem Fehler stirbt, ohne selbst nach einer Ausnahme zu suchen, schreibe den Skriptkörper so:

client = Cookbook.connect
print "Connected\n"
client.close
print "Disconnected\n"

PHP

PHP-Bibliotheksdateien werden wie normale PHP-Skripte geschrieben. Eine Cookbook.php Datei, die eine Cookbook Klasse mit einer connect()Methode implementiert, sieht wie folgt aus:

<?php
# Cookbook.php: library file with utility method for connecting to MySQL
# using the PDO module

class Cookbook
{
  public static $host_name = "localhost";
  public static $db_name = "cookbook";
  public static $user_name = "cbuser";
  public static $password = "cbpass";

  # Establish a connection to the cookbook database, returning a database
  # handle.  Raise an exception if the connection cannot be established.
  # In addition, cause exceptions to be raised for errors.

  public static function connect ()
  {
    $dsn = "mysql:host=" . self::$host_name . ";dbname=" . self::$db_name;
    $dbh = new PDO ($dsn, self::$user_name, self::$password);
    $dbh->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    return ($dbh);
  }

} # end Cookbook
?>

Die Routine connect() innerhalb der Klasse wird mit dem Schlüsselwort static deklariert, um sie zu einer Klassenmethode und nicht zu einer Instanzmethode zu machen. Dadurch ist sie direkt aufrufbar, ohne dass ein Objekt instanziiert werden muss, über das sie aufgerufen wird.

Der Konstruktor new PDO() löst eine Ausnahme aus, wenn der Verbindungsversuch fehlschlägt. Nach einem erfolgreichen Versuch stellt connect() den Fehlerbehandlungsmodus so ein, dass andere PDO-Aufrufe ebenfalls Ausnahmen bei Fehlschlägen auslösen. Auf diese Weise müssen die einzelnen Aufrufe nicht auf einen Fehlerrückgabewert getestet werden.

Obwohl die meisten PHP-Beispiele in diesem Buch die Tags <?php und ?> nicht zeigen, haben wir sie hier als Teil von Cookbook.php gezeigt, um zu verdeutlichen, dass Bibliotheksdateien den gesamten PHP-Code innerhalb dieser Tags einschließen müssen. Der PHP-Interpreter macht keine Annahmen über den Inhalt einer Bibliotheksdatei, wenn er mit dem Parsen beginnt, denn es könnte sein, dass du eine Datei einfügst, die nichts anderes als HTML enthält. Deshalb musst du <?php und ?> verwenden, um explizit anzugeben, welche Teile der Bibliotheksdatei als PHP-Code und nicht als HTML betrachtet werden sollen, genau wie im Hauptskript.

PHP sucht nach Bibliotheken, indem es die Verzeichnisse durchsucht, die ingenannt werden. Dazu wird die Variable include_path in der PHP-Initialisierungsdatei verwendet, wie im einleitenden Teil dieses Rezeptes beschrieben.

Hinweis

PHP-Skripte werden oft im Dokumentenbaum deinesServers abgelegt und können von den Kunden direkt angefordert werden. Für PHP-Bibliotheksdateien empfehlen wir, sie außerhalb des Dokumentenbaums zu platzieren, insbesondere wenn sie (wie Cookbook.php) einen Benutzernamen und ein Passwort enthalten.

Nachdem du Cookbook.php in einem der Verzeichnisse von include_path installiert hast, probiere es mit einem Test-Harness-Skript aus: harness.php:

<?php
# harness.php: test harness for Cookbook.php library

require_once "Cookbook.php";

try
{
  $dbh = Cookbook::connect ();
  print ("Connected\n");
}
catch (PDOException $e)
{
  print ("Cannot connect to server\n");
  print ("Error code: " . $e->getCode () . "\n");
  print ("Error message: " . $e->getMessage () . "\n");
  exit (1);
}
$dbh = NULL;
print ("Disconnected\n");
?>

Die Anweisung require_once greift auf die Datei Cookbook.php zu, die benötigt wird, um die Klasse Cookbook zu verwenden. require_once ist eine von mehreren PHP-Dateieinbindungsanweisungen:

  • require und include weisen PHP an, die genannte Datei zu lesen. Sie sind ähnlich, aber require bricht das Skript ab, wenn die Datei nicht gefunden wird; includegibt nur eine Warnung aus.

  • require_once und include_once sind wie require und include, mit dem Unterschied, dass der Inhalt der Datei nicht erneut verarbeitet wird, wenn sie bereits gelesen wurde. Dies ist nützlich, um Probleme mit Mehrfachdeklarationen zu vermeiden, die leicht auftreten können, wenn Bibliotheksdateien andere Bibliotheksdateien enthalten.

Python

Python-Bibliotheken werden als Module geschrieben und von Skripten mit import Anweisungen referenziert. Um eine Methode zur Verbindung mit MySQL zu erstellen, schreibst du eine Moduldatei, cookbook.py (Python-Modulnamen sollten klein geschrieben werden):

# cookbook.py: library file with utility method for connecting to MySQL
# using the Connector/Python module

import mysql.connector

conn_params = {
  "database": "cookbook",
  "host": "localhost",
  "user": "cbuser",
  "password": "cbpass",
}

# Establish a connection to the cookbook database, returning a connection
# object.  Raise an exception if the connection cannot be established.

def connect():
  return mysql.connector.connect(**conn_params)

Der Dateiname basename bestimmt den Modulnamen, also heißt das Modul cookbook. Auf die Methoden des Moduls wird über den Modulnamen zugegriffen; importiere also das Modul cookbook und rufe seine Methode connect()wie folgt auf:

import cookbook

conn = cookbook.connect();

Der Python-Interpreter sucht in der Variable sys.path nach Modulen in Verzeichnissen mit dem Namen. Um den Standardwert von sys.path auf deinem System zu überprüfen, starte Python interaktiv und gib ein paar Befehle ein:

$ python
>>> import sys
>>> sys.path

Wenn du cookbook.pyin einem der von sys.path benannten Verzeichnisse installierst, werden deine Skripte es ohne besondere Behandlung finden. Wenn du cookbook.py woanders installierst, musst dudie Umgebungsvariable PYTHONPATHsetzen, wie im einleitenden Teil dieses Rezepts beschrieben.

Nachdem du die cookbook.py Bibliotheksdatei installiert hast, kannst du sie in einem Test-Harness-Skript ausprobieren, harness.py:

#!/usr/bin/python
# harness.py: test harness for cookbook.py library

import mysql.connector
import cookbook

try:
  conn = cookbook.connect()
  print("Connected")
except mysql.connector.Error as e:
  print("Cannot connect to server")
  print("Error code: %s" % e.errno)
  print("Error message: %s" % e.msg)
else:
  conn.close()
  print("Disconnected")

Die Datei cookbook.py importiert das Modul mysql.connector, aber ein Skript, das cookbookimportiert, erhält dadurch keinen Zugriff auf mysql.connector. Wenn das Skript Connector/Python-spezifische Informationen benötigt (z.B. mysql.connector.Error), muss das Skript selbst mysql.connector importieren.

Wenn du möchtest, dass ein Skript bei einem Fehler stirbt, ohne selbst nach einer Ausnahme zu suchen, schreibe den Skriptkörper so:

conn = cookbook.connect()
print("Connected")
conn.close()
print("Disconnected")

Geh

Go-Programme sind in Paketen organisiert, die eine Sammlung von Quelldateien sind, die sich im gleichen Verzeichnis befinden. Pakete wiederum sind in Modulen organisiert, die Sammlungen von Go-Paketen sind, die gemeinsam veröffentlicht werden. Module gehören zu einem Go-Repository. Ein typisches Go-Repository enthält nur ein Modul, aber du kannst auch mehrere Module in einem Repository haben.

Der Go-Interpreter sucht in der Variable $GOPATH/src/{domain}/{project} nach Paketen in Verzeichnissen mit dem Namen . Bei der Verwendung von Modulen verwendet Go jedoch nicht mehr GOPATH. Du brauchst diese Variable nicht zu ändern, egal, wo dein Modul installiert ist. Wir werden für unsere Beispiele Module verwenden.

Um eine Methode für die Verbindung zu MySQL zu erstellen, schreibst du ein Paket Datei, cookbook.go:

package cookbook

import (
  "database/sql"
  _"github.com/go-sql-driver/mysql"
)

func Connect() (*sql.DB, error) {
  db, err := sql.Open("mysql","cbuser:cbpass@tcp(127.0.0.1:3306)/cookbook")

  if err != nil {
    panic(err.Error())
  }

  err = db.Ping()

  return db, err
}

Der Basisdateiname bestimmt nicht den Paketnamen: Go durchsucht alle Dateien im Importpfad, bis es diejenige mit der erforderlichen Paketdeklaration findet. Auf die Paketmethoden wird über den Paketnamen zugegriffen.

Um das Paket zu testen, kannst du einen relativen Pfad zu dem Verzeichnis angeben, in dem sich die Paketdatei befindet:

import "../../lib"

Das ist eine sehr einfache Methode, um deine Bibliotheken schnell zu testen, aber solche Befehle wie go install funktionieren nicht für Pakete, die auf diese Weise importiert wurden. Das hat zur Folge, dass dein Programm jedes Mal von Grund auf neu erstellt wird, wenn du es aufrufen willst.

Eine bessere Möglichkeit, mit Paketen zu arbeiten, ist, sie als Teil von Modulen zu veröffentlichen. Dazu führst du in dem Verzeichnis, in dem du cookbook.go speicherst, Folgendes aus:

go mod init cookbook

Dadurch wird eine Datei go.mod erstellt, die den Namen deines Moduls und die Version von Go enthält. Du kannst das Modul benennen, wie du willst.

Du kannst dein Modul im Internet veröffentlichen und vom lokalen Programm aus darauf zugreifen, wie du es mit jedem anderen Modul tun würdest. Während der Entwicklung wäre es jedoch nützlich, das Modul nur lokal zu haben. In diesem Fall musst du ein paar Anpassungen in dem Programmverzeichnis vornehmen, das das Modul verwenden soll.

Erstelle zunächst ein Programm, das das Paket aufruft: harness.go:

package main

import (
  "fmt"
  "github.com/svetasmirnova/mysqlcookbook/recipes/lib"
)

func main() {
  db, err := cookbook.Connect()

  if err != nil {
    fmt.Println("Cannot connect to server")
    fmt.Printf("Error message: %s\n", err.Error())
  } else {
    fmt.Println("Connected")
  }
  defer db.Close()
}

Nachdem das Paket installiert ist, initialisiere das Modul in dem Verzeichnis:

go mod init harness

Sobald das Modul initialisiert und go.mod erstellt ist, bearbeite es wie folgt:

go mod edit -replace ↩
github.com/svetasmirnova/mysqlcookbook/recipes/lib=↩
/home/sveta/src/mysqlcookbook/recipes/lib

Ersetze die URL und den lokalen Pfad mit denen, die in deiner Umgebung gültig sind.

Mit diesem Befehl wird Go angewiesen, den Pfad des Fernmoduls durch das lokale Verzeichniszu ersetzen .

Danach kannst du deine Verbindung testen:

$ go run harness.go
Connected

Java

Java-Bibliotheksdateien sind den Java-Programmen in den meisten Punkten ähnlich:

  • Die Zeile class in der Quelldatei gibt einen Klassennamen an.

  • Die Datei sollte den gleichen Namen wie die Klasse haben (mit der Erweiterung .java ).

  • Kompiliere die .java-Datei, um eine .class-Dateizu erstellen.

Java-Bibliotheksdateien unterscheiden sich auch in einigen Punkten von Java-Programmen:

  • Im Gegensatz zu normalen Programmdateien haben Java-Bibliotheksdateien keine main() Funktion.

  • Eine Bibliotheksdatei sollte mit einem package Bezeichner beginnen, der die Position der Klasse innerhalb des Java-Namensraums angibt.

Eine gängige Konvention für Java-Paketbezeichner ist es, die Domaindes Codeautors als Präfix zu verwenden; dies hilft, Bezeichner eindeutig zu machen und Konflikte mit Klassen zu vermeiden, die von anderen Autoren geschrieben wurden. Domainnamen verlaufen innerhalb des Domain-Namensraums von rechts nach links, von allgemeiner zu spezifischer, während der Java-Klassennamensraum von links nach rechts verläuft, von allgemein zu spezifisch. Um eine Domain als Präfix für einen Paketnamen im Java-Klassennamensraum zu verwenden, muss man sie also umkehren. Pauls Domain ist zum Beispiel kitebird.com. Wenn er also eine Bibliotheksdatei schreibt und sie unter mcb im Namensraum seiner Domain ablegt, beginnt die Bibliothek mit einer package Anweisung wie dieser:

package com.kitebird.mcb;

Java-Pakete, die für dieses Buch entwickelt wurden, befinden sich imcom.kitebird.mcb Namensraum, um ihre Einzigartigkeit im Paketnamensraum zu gewährleisten.

Die folgende Bibliotheksdatei, Cookbook.java, definiert eine Cookbook Klasse, die eine connect() Methode zur Verbindung mit der cookbook Datenbank implementiert. connect() gibt ein Connection Objekt zurück, wenn es erfolgreich war und wirft andernfalls eine Ausnahme. Um dem Aufrufer zu helfen, mit Fehlern umzugehen, definiert die Klasse Cookbook auch die Hilfsmethoden getErrorMessage() und printErrorMessage(), die die Fehlermeldung als String zurückgeben bzw. auf System.err ausgeben:

// Cookbook.java: library file with utility methods for connecting to MySQL
// using MySQL Connector/J and for handling exceptions

package com.kitebird.mcb;

import java.sql.*;

public class Cookbook {
  // Establish a connection to the cookbook database, returning
  // a connection object.  Throw an exception if the connection
  // cannot be established.

  public static Connection connect() throws Exception {
    String url = "jdbc:mysql://localhost/cookbook";
    String user = "cbuser";
    String password = "cbpass";

    return (DriverManager.getConnection(url, user, password));
  }

  // Return an error message as a string

  public static String getErrorMessage(Exception e) {
    StringBuffer s = new StringBuffer ();
    if (e instanceof SQLException) { // JDBC-specific exception?
      // print general message, plus any database-specific message
      s.append("Error message: " + e.getMessage () + "\n");
      s.append("Error code: " + ((SQLException) e).getErrorCode() + "\n");
    } else {
      s.append (e + "\n");
    }
    return (s.toString());
  }

  // Get the error message and print it to System.err

  public static void printErrorMessage(Exception e) {
    System.err.println(Cookbook.getErrorMessage(e));
  }
}

Die Routinen innerhalb der Klasse werden mit dem Schlüsselwort static deklariert, was sie zu Klassenmethoden und nicht zu Instanzmethoden macht. Das wird hier gemacht, weil die Klasse direkt verwendet wird, anstatt ein Objekt aus ihr zu erstellen und die Methoden über das Objekt aufzurufen.

Um die Datei Cookbook.javazu verwenden, kompiliere sie, um Cookbook.class zu erzeugen, und installiere dann die Klassendatei in einem Verzeichnis, das dem Paketbezeichner entspricht.

Das bedeutet, dass Cookbook.class unter in einem Verzeichnis mit dem Namen com/kitebird/mcb (Unix) oder com\kitebird\mcb (Windows) installiert werden sollte, das sich unter einem Verzeichnis befindet, das in deiner Einstellung CLASSPATH genannt wird. Wenn CLASSPATH unter Unix zum Beispiel /usr/local/lib/mcb enthält, kannst du Cookbook.class in das Verzeichnis /usr/local/lib/mcb/com/kitebird/mcbinstallieren. (Weitere Informationen über die Variable CLASSPATH findest du in der Java-Diskussion in Rezept 4.1).

Um die Klasse Cookbook in einem Java-Programm zu verwenden, importiere sie und rufe die Methode Cookbook.connect() auf. Das folgende Test-Harness-Programm Harness.java zeigt, wie das geht:

// Harness.java: test harness for Cookbook library class

import java.sql.*;
import com.kitebird.mcb.Cookbook;

public class Harness {
  public static void main(String[] args) {
    Connection conn = null;
    try {
      conn = Cookbook.connect ();
      System.out.println("Connected");
    } catch (Exception e) {
      Cookbook.printErrorMessage (e);
      System.exit (1);
    } finally  {
      if (conn != null) {
        try {
          conn.close();
          System.out.println("Disconnected");
        } catch (Exception e) {
          String err = Cookbook.getErrorMessage(e);
          System.out.println(err);
        }
      }
    }
  }
}

Harness.java zeigt auch, wie die Fehlermeldungs-Utility-Methoden aus der Klasse Cookbook verwendet, wenn eine MySQL-bezogene Ausnahme auftritt:

  • printErrorMessage() nimmt das Exception-Objekt und verwendet es, um eine Fehlermeldung auf System.err zu drucken.

  • getErrorMessage() gibt die Fehlermeldung als String zurück. Du kannst die Meldung selbst anzeigen, sie in eine Logdatei schreiben oder was auch immer.

4.4 Ausführen von Anweisungen und Abrufen von Ergebnissen

Problem

Du möchtest, dass ein Programm eine SQL-Anweisung an den MySQL-Server sendet und das Ergebnis abruft.

Lösung

Einige Anweisungen geben nur einen Statuscode zurück, andere geben eine Ergebnismenge (eine Reihe von Zeilen) zurück. Einige APIs bieten unterschiedliche Methoden für die Ausführung der einzelnen Anweisungstypen. In diesem Fall musst du die entsprechende Methode für die auszuführende Anweisung verwenden.

Diskussion

Du kannst zwei allgemeine Kategorien von SQL-Anweisungen ausführen. Einige rufen Informationen aus der Datenbank ab, andere ändern diese Informationen oder die Datenbank selbst. Die Anweisungen in den beiden Kategorien werden unterschiedlich behandelt. Darüber hinaus bieten einige APIs mehrere Routinen für die Ausführung von Anweisungen, was die Sache noch komplizierter macht. Bevor wir zu den Beispielen kommen, die zeigen, wie die Anweisungen in den einzelnen APIs ausgeführt werden, beschreiben wir die Datenbanktabelle, die in den Beispielen verwendet wird, und erläutern dann die beiden Anweisungskategorien sowie eine allgemeine Strategie für die Verarbeitung von Anweisungen in jeder Kategorie.

In Kapitel 1 haben wir eine Tabelle namens limbs erstellt, um einige Beispielanweisungen auszuprobieren. In diesem Kapitel werden wir eine andere Tabelle namens profile verwenden. Sie basiert auf der Idee einer Freundesliste, d.h . einer Gruppe von Personen, mit denen wir gerne in Kontakt bleiben, während wir online sind. Die Tabellendefinition sieht wie folgt aus:

CREATE TABLE profile
(
  id    INT UNSIGNED NOT NULL AUTO_INCREMENT,
  name  VARCHAR(20) NOT NULL,
  birth DATE,
  color ENUM('blue','red','green','brown','black','white'),
  foods SET('lutefisk','burrito','curry','eggroll','fadge','pizza'),
  cats  INT,
  PRIMARY KEY (id)
);

Die Tabelle profile zeigt die Dinge, die uns bei jedem Kumpel wichtig sind: Name, Alter, Lieblingsfarbe, Lieblingsessen und die Anzahl der Katzen. Außerdem verwendet die Tabelle verschiedene Datentypen für ihre Spalten, die uns zeigen, wie wir Probleme lösen können, die sich auf bestimmte Datentypen beziehen.

Die Tabelle enthält auch eine Spalte id mit eindeutigen Werten, damit wir eine Zeile von einer anderen unterscheiden können, auch wenn zwei Freunde denselben Namen haben. id und namesind als NOT NULL deklariert, weil sie jeweils einen Wert haben müssen. Die anderen Spalten dürfen implizit als NULL deklariert werden (und das ist auch ihr Standardwert), weil wir vielleicht nicht wissen, welchen Wert wir ihnen für eine bestimmte Person zuweisen sollen. Das heißt, NULL steht für unbekannt.

Beachte, dass es in der Tabelle keine Spalte age gibt, obwohl wir das Alter festhalten wollen. Stattdessen gibt es eine Spalte birth vom Typ DATE. Das Alter ändert sich. Wenn wir also Alterswerte speichern, müssen wir sie ständig aktualisieren. Die Speicherung von Geburtsdaten ist besser: Sie ändern sich nicht und können jederzeit zur Berechnung des Alters verwendet werden (siehe Rezept 8.14). color ist eine ENUM Spalte; Farbwerte können beliebige der aufgelisteten Werte sein. foods ist eine SET, die es erlaubt, dass der Wert eine beliebige Kombination der einzelnen Set-Mitglieder ist. Auf diese Weise können wir für jeden Kumpel mehrere Lieblingsspeisen aufzeichnen.

Um die Tabelle zu erstellen, verwende das Skript profile.sql, das sich im Verzeichnis tables der Distribution recipes befindet. Wechsle in dieses Verzeichnis und führe dann diesen Befehl aus:

$ mysql cookbook < profile.sql

Das Skript lädt auch Beispieldaten in die Tabelle. Du kannst mit der Tabelle experimentieren und sie dann wiederherstellen, wenn du ihren Inhalt änderst, indem du das Skript erneut ausführst. (Siehe Rezept 4.9 über die Bedeutung der Wiederherstellung der Tabelle profilenach einer Änderung).

Der Inhalt der Tabelle profile, die durch das Skript profile.sqlgeladen wird, sieht folgendermaßen aus:

mysql> SELECT * FROM profile;
+----+---------+------------+-------+-----------------------+------+
| id | name    | birth      | color | foods                 | cats |
+----+---------+------------+-------+-----------------------+------+
|  1 | Sybil   | 1970-04-13 | black | lutefisk,fadge,pizza  |    0 |
|  2 | Nancy   | 1969-09-30 | white | burrito,curry,eggroll |    3 |
|  3 | Ralph   | 1973-11-02 | red   | eggroll,pizza         |    4 |
|  4 | Lothair | 1963-07-04 | blue  | burrito,curry         |    5 |
|  5 | Henry   | 1965-02-14 | red   | curry,fadge           |    1 |
|  6 | Aaron   | 1968-09-17 | green | lutefisk,fadge        |    1 |
|  7 | Joanna  | 1952-08-20 | green | lutefisk,fadge        |    0 |
|  8 | Stephen | 1960-05-01 | white | burrito,pizza         |    0 |
+----+---------+------------+-------+-----------------------+------+

Obwohl die meisten Spalten in der Tabelle profile NULL Werte zulassen, enthält keine der Zeilen im Beispieldatensatz tatsächlich NULL. (Wir wollen die Komplikationen bei der Verarbeitung von NULL Werten auf die Rezepte 4.5 und 4.7 verschieben ).

SQL-Anweisungskategorien

SQL-Anweisungen können in zwei große Kategorien eingeteilt werden, je nachdem, ob sie eine Ergebnismenge (eine Reihe von Zeilen) zurückgeben:

INSERT, DELETE, oder UPDATE

Anweisungen, die keine Ergebnismenge zurückgeben, wie z.B. INSERT , DELETE oder UPDATE. In der Regel verändern Anweisungen dieser Art die Datenbank in irgendeiner Weise. Es gibt einige Ausnahmen, wie z. B. USEdb_name, die die Standarddatenbank (die aktuelle) für deine Sitzung ändert, ohne die Datenbank selbst zu verändern. Das Beispiel für eine datenverändernde Anweisung in diesem Abschnitt ist UPDATE:

UPDATE profile SET cats = cats+1 WHERE name = 'Sybil';

Wir erklären dir, wie du diese Anweisung ausführst und die Anzahl der Zeilen bestimmst, die sie betrifft.

SELECT, SHOW, EXPLAIN, oder DESCRIBE

Anweisungen, die eine Ergebnismenge zurückgeben, wie SELECT, SHOW, EXPLAIN oder DESCRIBE. Wir bezeichnen solche Anweisungen allgemein als SELECTAnweisungen, aber du solltest diese Kategorie so verstehen, dass sie alle Anweisungen umfasst, die Zeilen zurückgeben. Die Beispielanweisung zum Abrufen von Zeilen, die in diesem Abschnitt verwendet wird, ist eine SELECT:

SELECT id, name, cats FROM profile;

Wir zeigen dir, wie du diese Anweisung ausführst, die Zeilen der Ergebnismenge abrufst und die Anzahl der Zeilen und Spalten in der Ergebnismenge bestimmst. (Um Informationen wie die Spaltennamen oder Datentypen zu erhalten, musst du auf die Metadaten der Ergebnismenge zugreifen. Das ist Rezept 12.2.)

Der erste Schritt bei der Verarbeitung einer SQL-Anweisung besteht darin, sie zur Ausführung an den MySQL-Server zu senden. Einige APIs (z. B. die für Perl und Java) unterscheiden zwischen den beiden Kategorien von Anweisungen und bieten separate Aufrufe für deren Ausführung. Andere APIs (wie die für Python oder Ruby) verwenden einen einzigen Aufruf für alle Anweisungen. Eine Gemeinsamkeit aller APIs ist jedoch, dass kein Sonderzeichen das Ende der Anweisung kennzeichnet. Es ist kein Terminator notwendig, weil das Ende der Anweisungszeichenfolge diese beendet. Dies unterscheidet sich von der Ausführung von Anweisungen im Programm mysql, wo du Anweisungen mit einem Semikolon (;) oder \g beendest. (Es unterscheidet sich auch davon, wie dieses Buch normalerweise Semikolons in Beispiele einfügt, um deutlich zu machen, wo Anweisungen enden).

Wenn du eine Anweisung an den Server sendest, musst du mit Fehlern rechnen, wenn sie nicht erfolgreich ausgeführt wurde . Wenn eine Anweisung fehlschlägt und du davon ausgehst, dass sie erfolgreich war, wird dein Programm nicht funktionieren. In diesem Abschnitt wird größtenteils kein Code zur Fehlerprüfung gezeigt, aber das ist nur der Kürze halber. Produktionscode sollte immer eine Fehlerbehandlung enthalten. Die Beispielskripte in der recipes Distribution, denen die Beispiele entnommen sind, enthalten eine Fehlerbehandlung, die auf den in Rezept 4.2 beschriebenen Techniken basiert.

Wenn eine Anweisung ohne Fehler ausgeführt wird, hängt dein nächster Schritt von der Art der Anweisung ab. Wenn es sich um eine Anweisung handelt, die keine Ergebnismenge zurückgibt, gibt es nichts weiter zu tun, es sei denn, du möchtest überprüfen, wie viele Zeilen betroffen waren. Wenn die Anweisung eine Ergebnismenge zurückgibt, holst du die Zeilen ab und schließt die Ergebnismenge. Wenn du nicht weißt, ob eine Anweisung eine Ergebnismenge liefert, erfährst du in Rezept 12.2, wie du das feststellen kannst.

Perl

Das Perl DBI-Modul bietet zwei grundlegende Ansätze für die Ausführung von SQL-Anweisungen, je nachdem, ob du eine Ergebnismenge zurückbekommst. Für eine Anweisung wie INSERT oder UPDATE, die keine Ergebnismenge zurückgibt, verwendest du die Methode Database Handle do(). Sie führt die Anweisung aus und gibt die Anzahl der von ihr betroffenen Zeilen zurück oder undef, wenn ein Fehler auftritt. Wenn Sybil eine neue Katze bekommt, erhöht die folgende Anweisung die Anzahl ihrer cats um eins:

my $count = $dbh->do ("UPDATE profile SET cats = cats+1
                       WHERE name = 'Sybil'");
if ($count)   # print row count if no error occurred
{
  $count += 0;
  print "Number of rows updated: $count\n";
}

Wenn die Anweisung erfolgreich ausgeführt wurde, aber keine Zeilen betroffen sind, gibtdo() einen speziellen Wert zurück, "0E0" (den Wert Null in wissenschaftlicher Notation, ausgedrückt als String). "0E0" kann zum Testen des Ausführungsstatus einer Anweisung verwendet werden, da er in booleschen Kontexten wahr ist (im Gegensatz zu undef). Bei erfolgreichen Anweisungen kann er auch verwendet werden, um zu zählen, wie viele Zeilen betroffen waren, da er in numerischen Kontexten als die Zahl Null behandelt wird. Wenn du diesen Wert so ausgibst, wie er ist, druckst du natürlich "0E0" aus, was für die Benutzer deines Programms seltsam aussehen könnte. Das vorangegangene Beispiel stellt sicher, dass das nicht passiert, indem es eine Null zum Wert hinzufügt, um ihn in die numerische Form zu zwingen, so dass er als 0 angezeigt wird. Alternativ kannst du printf mit dem Formatierungsparameter %d verwenden, um eine implizite numerische Umwandlung zu bewirken:

if ($count)   # print row count if no error occurred
{
  printf "Number of rows updated: %d\n", $count;
}

Wenn RaiseError aktiviert ist,wird dein Skript bei DBI-bezogenen Fehlern automatisch beendet. Du musst also nicht $count überprüfen, um herauszufinden, ob do() fehlgeschlagen ist, und kannst so den Code vereinfachen:

my $count = $dbh->do ("UPDATE profile SET cats = cats+1
                       WHERE name = 'Sybil'");
printf "Number of rows updated: %d\n", $count;

Um eine Anweisung wie SELECT zu verarbeiten, die eine Ergebnismenge zurückgibt, wendest du einen anderen Ansatz an, der diese Schritte umfasst:

  1. Gib die auszuführende Anweisung an, indem du prepare() mit dem Datenbankhandle aufrufst. prepare() gibt ein Anweisungshandle zurück, das bei allen nachfolgenden Operationen mit der Anweisung verwendet wird. (Wenn ein Fehler auftritt, wird das Skript beendet, wenn RaiseError aktiviert ist; andernfalls gibt prepare() undef zurück).

  2. Rufe execute() auf, um die Anweisungauszuführen und die Ergebnismenge zu erzeugen.

  3. Schleife, um die von der Anweisung zurückgegebenen Zeilen zu holen. DBI bietet dafür mehrere Methoden, die wir kurz erläutern.

  4. Wenn du nicht die gesamte Ergebnismenge abrufst, gibst du die damit verbundenen Ressourcen frei, indem du finish() aufrufst.

Das folgende Beispiel veranschaulicht diese Schritte, wobei fetchrow_array() als Zeilenabrufmethode verwendet wird und angenommen wird, dass RaiseError aktiviert ist, damit Fehler das Skript beenden:

my $sth = $dbh->prepare ("SELECT id, name, cats FROM profile");
$sth->execute ();
my $count = 0;
while (my @val = $sth->fetchrow_array ())
{
  print "id: $val[0], name: $val[1], cats: $val[2]\n";
  ++$count;
}
$sth->finish ();
print "Number of rows returned: $count\n";

Die Größe des Zeilenarrays gibt die Anzahl der Spalten in der Ergebnismenge an.

Auf die soeben gezeigte Schleife zum Abrufen von Zeilen folgt ein Aufruf von finish(), der die Ergebnismenge schließt und dem Server mitteilt, dass er alle damit verbundenen Ressourcen freigeben soll. Wenn du jede Zeile der Menge abrufst, merkt DBI, wenn du das Ende erreicht hast und gibt die Ressourcen für dich frei. Das Beispiel könnte also den Aufruf von finish() weglassen, ohne dass dies negative Auswirkungen hätte.

Um festzustellen, wie viele Zeilen eine Ergebnismenge enthält, zählst du sie, während du sie abrufst, wie das Beispiel zeigt. Verwende nicht die DBI-Methode rows() für diesen Zweck. Die DBI-Dokumentation rät von dieser Methode ab, da rows() für SELECTAnweisungen nicht unbedingt zuverlässig ist - aufgrund der Unterschiede im Verhalten der verschiedenen Datenbank-Engines und -Treiber.

DBI hat mehrere Methoden, die jeweils eine Zeile abrufen. Die im vorangegangenen Beispiel verwendete Methode fetchrow_array() gibt ein Array zurück, das die nächste Zeile enthält, oder eine leere Liste, wenn es keine weiteren Zeilen gibt. Die Array-Elemente sind in der Reihenfolge vorhanden, die in der Anweisung SELECT genannt wird. Greife auf sie als $val[0], $val[1], usw. zu.

Die Methode fetchrow_array() ist besonders nützlich für Anweisungen, die die auszuwählenden Spalten explizit benennen. (Bei SELECT * gibt es keine Garantien für die Position der Spalten innerhalb des Arrays.)

fetchrow_arrayref() ist wie fetchrow_array(), mit dem Unterschied, dass es eine Referenz auf das Array zurückgibt, oder undef, wenn es keine weiteren Zeilen gibt. Wie bei fetchrow_array() sind die Array-Elemente in der Reihenfolge vorhanden, die in der Anweisung genannt wird. Greife auf sie als $ref->[0], $ref->[1], usw. zu:

while (my $ref = $sth->fetchrow_arrayref ())
{
  print "id: $ref->[0], name: $ref->[1], cats: $ref->[2]\n";
}

fetchrow_hashref() gibt einen Verweis auf eine Hash-Struktur zurück, oder undef, wenn es keine weiteren Zeilen gibt:

while (my $ref = $sth->fetchrow_hashref ())
{
  print "id: $ref->{id}, name: $ref->{name}, cats: $ref->{cats}\n";
}

Um auf die Elemente des Hashes zuzugreifen, verwendest du die Namen der Spalten, die von der Anweisung ausgewählt wurden ($ref->{id}, $ref->{name}, usw.). fetchrow_hashref() ist besonders nützlich für SELECT * Anweisungen, weil du auf die Elemente der Zeilen zugreifen kannst, ohne etwas über die Reihenfolge zu wissen, in der die Spalten zurückgegeben werden. Du brauchst nur ihre Namen zu kennen. Andererseits ist es teurer, einen Hash einzurichten als ein Array, so dass fetchrow_hashref() langsamer ist als fetchrow_array() oder fetchrow_arrayref(). Es ist auch möglich, dass Zeilenelemente verloren gehen, wenn sie denselben Namen haben, weil Spaltennamen eindeutig sein müssen. Gleichnamige Spalten sind bei Joins zwischen Tabellen keine Seltenheit. Lösungen für dieses Problem findest du in Rezept 16.11.

Zusätzlich zu den gerade beschriebenen Methoden zur Ausführung von Anweisungen bietet DBI mehrere Abfragemethoden auf hoher Ebene, die eine Anweisung ausführen und die Ergebnismenge in einer einzigen Operation zurückgeben. Alle sind Datenbank-Handle-Methoden, die das Statement-Handle intern erstellen und entsorgen, bevor sie die Ergebnismenge zurückgeben. Die Methoden unterscheiden sich in der Form, in der sie das Ergebnis zurückgeben. Einige geben die gesamte Ergebnismenge zurück, andere geben eine einzelne Zeile oder Spalte der Menge zurück, wie in Tabelle 4-4 zusammengefasst.

Tabelle 4-4. Perl-Methoden zum Abrufen von Ergebnissen
MethodeRückgabewert
selectrow_array() Erste Zeile der Ergebnismenge als Array
selectrow_arrayref() Erste Zeile der Ergebnismenge als Verweis auf ein Array
selectrow_hashref() Erste Zeile der Ergebnismenge als Verweis auf einen Hash
selectcol_arrayref() Erste Spalte der Ergebnismenge als Verweis auf ein Array
selectall_arrayref() Gesamte Ergebnismenge als Verweis auf ein Array von Array-Referenzen
selectall_hashref() Gesamte Ergebnismenge als Verweis auf einen Hash von Hash-Referenzen

Die meisten dieser Methoden geben eine Referenz zurück. Die Ausnahme ist selectrow_array(), die die erste Zeile der Ergebnismenge auswählt und ein Array oder einen Skalar zurückgibt, je nachdem, wie du sie aufrufst. Im Array-Kontext gibt selectrow_array() die gesamte Zeile als Array zurück (oder die leere Liste, wenn keine Zeile ausgewählt wurde). Das ist nützlich für Anweisungen, von denen du nur eine einzige Zeile erwartest. Der Rückgabewert kann verwendet werden, um die Größe der Ergebnismenge zu bestimmen. Die Spaltenzahl ist die Anzahl der Elemente im Array und die Zeilenzahl ist 1 oder 0:

my @val = $dbh->selectrow_array ("SELECT name, birth, foods FROM profile
                                  WHERE id = 3");
my $ncols = @val;
my $nrows = $ncols ? 1 : 0;

selectrow_arrayref() und selectrow_hashref() wählen die erste Zeile der Ergebnismenge aus und geben eine Referenz darauf zurück, oder undef, wenn keine Zeile ausgewählt wurde. Um auf die Spaltenwerte zuzugreifen, behandelst du den Verweis auf die gleiche Weise wie den Rückgabewert von fetchrow_arrayref() oder fetchrow_hashref(). Der Verweis liefert auch die Anzahl der Zeilen und Spalten:

my $ref = $dbh->selectrow_arrayref ($stmt);
my $ncols = defined ($ref) ? @{$ref} : 0;
my $nrows = $ncols ? 1 : 0;

my $ref = $dbh->selectrow_hashref ($stmt);
my $ncols = defined ($ref) ? keys (%{$ref}) : 0;
my $nrows = $ncols ? 1 : 0;

selectcol_arrayref() gibt einen Verweis auf ein einspaltiges Array zurück, das die erste Spalte der Ergebnismenge darstellt. Unter der Annahme, dass der Rückgabewert nichtundef ist, kannst du auf die Elemente des Arrays wie folgt zugreifen $ref->[i] für den Wert aus Zeile i. Die Anzahl der Zeilen ist die Anzahl der Elemente im Array, und die Spaltenanzahl ist 1 oder 0:

my $ref = $dbh->selectcol_arrayref ($stmt);
my $nrows = defined ($ref) ? @{$ref} : 0;
my $ncols = $nrows ? 1 : 0;

selectall_arrayref() gibt einen Verweis auf ein Array zurück, das ein Element für jede Zeile des Ergebnisses enthält. Jedes Element ist ein Verweis auf ein Array. Um auf eine Zeile i der Ergebnismenge zuzugreifen, verwende $ref->[i] um einen Verweis auf die Zeile zu erhalten. Behandle die Zeilenreferenz dann genauso wie einen Rückgabewert von fetchrow_arrayref(), um auf die einzelnen Spaltenwerte in der Zeile zuzugreifen. Die Zeilen- und Spaltenwerte der Ergebnismenge sind wie folgt verfügbar:

my $ref = $dbh->selectall_arrayref ($stmt);
my $nrows = defined ($ref) ? @{$ref} : 0;
my $ncols = $nrows ? @{$ref->[0]} : 0;

selectall_hashref() gibt einen Verweis auf einen Hash zurück, bei dem jedes Element ein Hash-Verweis auf eine Zeile des Ergebnisses ist. Um diese Funktion aufzurufen, gibst du ein Argument an, das angibt, welche Spalte als Hash-Schlüssel verwendet werden soll. Wenn du zum Beispiel Zeilen aus der Tabelle profile abrufst, ist der Primärschlüssel die Spalte id:

my $ref = $dbh->selectall_hashref ("SELECT * FROM profile", "id");

Der Zugriff auf die Zeilen erfolgt über die Schlüssel des Hashes. Bei einer Zeile mit dem Schlüsselspaltenwert 12 lautet die Hash-Referenz für die Zeile $ref->{12}. Dieser Zeilenwert ist mit Spaltennamen verschlüsselt, die du verwenden kannst, um auf einzelne Spaltenelemente zuzugreifen (z. B. $ref->{12}->{name}). Die Zeilen- und Spaltenanzahl der Ergebnismenge ist wie folgt verfügbar:

my @keys = defined ($ref) ? keys (%{$ref}) : ();
my $nrows = scalar (@keys);
my $ncols = $nrows ? keys (%{$ref->{$keys[0]}}) : 0;

Die selectall_XXX() Methoden sind nützlich, wenn du eine Ergebnismenge mehr als einmal verarbeiten musst, denn Perl DBI bietet keine Möglichkeit, eine Ergebnismenge zurückzuspulen. Indem du die gesamte Ergebnismenge einer Variablen zuweist, kannst du ihre Elemente mehrfach durchlaufen.

Sei vorsichtig bei der Verwendung der High-Level-Methoden, wenn du RaiseError deaktiviert hast. In diesem Fall kann es sein, dass der Rückgabewert einer Methode es dir nicht ermöglicht, einen Fehler von einer leeren Ergebnismenge zu unterscheiden. Wenn du zum Beispiel selectrow_array() im skalaren Kontext aufrufst, um einen einzelnen Wert abzurufen, ist der Rückgabewert von undefzweideutig, denn er kann drei Dinge anzeigen: einen Fehler, eine leere Ergebnismenge oder eine Ergebnismenge, die aus einem einzelnen NULL Wert besteht. Um auf einen Fehler zu testen, prüftden Wert von $DBI::errstr, $DBI::err oder $DBI::state.

Ruby

Die Ruby Mysql2-API verwendet dieselben Aufrufe für SQL-Anweisungen, die kein Ergebnis zurückgebenset und solche, die es tun. Um eine Anweisung in Ruby zu verarbeiten, verwendest du die Methode query. Wenn die Anweisung mit einem Fehler fehlschlägt, löst query eine Ausnahme aus. Andernfalls gibt die Methode affected_rows die Anzahl der geänderten Zeilen für die letzte Anweisung zurück, die Daten geändert hat:

client.query("UPDATE profile SET cats = cats+1 WHERE name = 'Sybil'")
puts "Number of rows updated: #{client.affected_rows}"

Bei Anweisungen wie SELECT, die eine Ergebnismenge liefern, gibt die Methode query die Ergebnismenge als Instanz der Klasse Mysql2::Result zurück. Die Methode affected_rows gibt für solche Anweisungen die Anzahl der Zeilen in der Ergebnismenge zurück. Du kannst die Anzahl der Zeilen in der Ergebnismenge auch mit der Methode count des Objekts Mysql2::Result ermitteln:

result = client.query("SELECT id, name, cats FROM profile")
puts "Number of rows returned: #{client.affected_rows}"
puts "Number of rows returned: #{result.count}"
result.each do |row|
  printf "id: %s, name: %s, cats: %s\n", row["id"], row["name"], row["cats"]
end

result.fields enthält die Namen der Spalten in der Ergebnismenge.

PHP

PDO hat zwei Verbindungsobjekt-Methoden, um SQL-Anweisungen auszuführen:exec() für Anweisungen, die keine Ergebnismenge zurückgeben, und query() für solche, die eine Ergebnismenge zurückgeben. Wenn du PDO-Ausnahmen aktiviert hast, lösen beide Methodeneine Ausnahme aus, wenn die Ausführung der Anweisung fehlschlägt. (Ein anderer Ansatz kombiniert die Methoden prepare() und execute(); siehe Rezept 4.5.)

Um Anweisungen wie INSERT oder UPDATE auszuführen, die keine Zeilen zurückgeben, verwende exec(). Sie gibt einen Zähler zurück, der angibt, wie viele Zeilen geändert wurden:

$count = $dbh->exec ("UPDATE profile SET cats = cats+1 WHERE name = 'Sybil'");
printf ("Number of rows updated: %d\n", $count);

Bei Anweisungen wie SELECT, die eine Ergebnismenge zurückgeben, gibt die Methode query() ein Anweisungshandle zurück. In der Regel verwendest du dieses Objekt, um eine Methode zum Abrufen von Zeilen in einer Schleife aufzurufen und die Zeilen zu zählen, wenn du wissen musst, wie viele es sind:

$sth = $dbh->query ("SELECT id, name, cats FROM profile");
$count = 0;
while ($row = $sth->fetch (PDO::FETCH_NUM))
{
  printf ("id: %s, name: %s, cats: %s\n", $row[0], $row[1], $row[2]);
  $count++;
}
printf ("Number of rows returned: %d\n", $count);

Um die Anzahl der Spalten in der Ergebnismenge zu ermitteln, rufst du die Methode statement handle columnCount()auf.

Das Beispiel demonstriert die Methode fetch(), die die nächste Zeile der Ergebnismenge oder FALSE zurückgibt, wenn keine weiteren Zeilen vorhanden sind. fetch() nimmt ein optionales Argument an, das angibt, welchen Typ von Wert es zurückgeben soll. Wie gezeigt, gibt fetch() mit dem Argument PDO::FETCH_NUM ein Array mit Elementen zurück, auf die über numerische Indizes zugegriffen wird, beginnend mit 0. Die Arraygröße gibt die Anzahl der Spalten der Ergebnismenge an.

Mit dem Argument PDO::FETCH_ASSOC gibt fetch() ein assoziatives Array zurück, das Werte enthält, auf die über Spaltennamen zugegriffen wird ($row["id"], $row["name"], $row["cats"]).

Mit dem Argument PDO::FETCH_OBJ gibt fetch() ein Objekt zurück, dessen Mitglieder über die Spaltennamen ($row->id, $row->name, $row->cats) angesprochen werden.

fetch() verwendet den Standard-Fetch-Modus, wenn du ihn ohne Argument aufrufst. Sofern du den Modus nicht geändert hast, ist es PDO::FETCH_BOTH, eine Kombination aus PDO::FETCH_NUM und PDO::FETCH_ASSOC. Um den Standard-Fetch-Modus für alle Anweisungen, die innerhalb einer Verbindung ausgeführt werden, festzulegen, verwendest du die Methode setAttributedatabase-handle:

$dbh->setAttribute (PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);

Um den Modus für eine bestimmte Anweisung festzulegen, rufst du die Methode setFetchMode()auf, nachdem du die Anweisung ausgeführt hast und bevor du die Ergebnisse abrufst:

$sth->setFetchMode (PDO::FETCH_OBJ);

Es ist auch möglich, ein Statement-Handle als Iterator zu verwenden. Das Handle verwendet den aktuellen Standard-Fetch-Modus:

$sth->setFetchMode (PDO::FETCH_NUM);
foreach ($sth as $row)
  printf ("id: %s, name: %s, cats: %s\n", $row[0], $row[1], $row[2]);

Die Methode fetchAll() holt die gesamte Ergebnismenge ab und gibt sie als Array von Zeilen zurück. Sie erlaubt ein optionales fetch-mode Argument:

$rows = $sth->fetchAll (PDO::FETCH_NUM);
foreach ($rows as $row)
  printf ("id: %s, name: %s, cats: %s\n", $row[0], $row[1], $row[2]);

In diesem Fall ist die Zeilenzahl die Anzahl der Elemente in $rows.

Python

Die Python-DB-API verwendet dieselben Aufrufe für SQL-Anweisungen, die keineErgebnismenge zurückgeben, und für solche, die dies tun. Um eine Anweisung in Python zu verarbeiten, verwendest du dein Datenbankverbindungsobjekt, um ein Cursor-Objekt zu erhalten.Verwende dann die Methode execute()des Cursors, um die Anweisung an den Server zu senden. Wenn die Anweisung mit einem Fehler fehlschlägt, löst execute()eine Ausnahme aus. Andernfalls, wenn es keine Ergebnismenge gibt, ist die Ausführung der Anweisung abgeschlossen und das Attribut rowcount des Cursors zeigt an, wie viele Zeilen geändert wurden:

cursor = conn.cursor()
cursor.execute("UPDATE profile SET cats = cats+1 WHERE name = 'Sybil'")
print("Number of rows updated: %d" % cursor.rowcount)
conn.commit()
cursor.close()
Hinweis

Die Python-DB-API-Spezifikation gibt an, dass Datenbankverbindungen mit deaktiviertemAuto-Commit-Modus beginnen sollten, sodass Connector/Python die Auto-Commit-Funktion deaktiviert, wenn er sich mit dem MySQL-Server verbindet. Wenn du transaktionale Tabellen verwendest, werden Änderungen an ihnen zurückgesetzt, wenn du die Verbindung beendest, es sei denn, du hast die Änderungen vorher festgeschrieben. Deshalb wird im vorangegangenen Beispiel die Methode commit() aufgerufen. Weitere Informationen zum Auto-Commit-Modus findest du in Kapitel 20, insbesondere in Rezept 20.7.

Wenn die Anweisung eine Ergebnismenge zurückgibt, holst du deren Zeilen und schließt dann den Cursor. Die Methode fetchone() gibt die nächste Zeile als Folge zurück, oder None, wenn es keine weiteren Zeilen gibt:

cursor = conn.cursor()
cursor.execute("SELECT id, name, cats FROM profile")
while True:
  row = cursor.fetchone()
  if row is None:
    break
  print("id: %s, name: %s, cats: %s" % (row[0], row[1], row[2]))
print("Number of rows returned: %d" % cursor.rowcount)
cursor.close()

Wie du im vorangegangenen Beispiel sehen kannst, ist das Attribut rowcount auch für SELECTAnweisungen nützlich; es gibt die Anzahl der Zeilen in der Ergebnismenge an.

len(row) gibt dir die Anzahl der Spalten in der Ergebnismenge an.

Alternativ kannst du auch den Cursor selbst als Iterator verwenden, der jede Zeile nacheinander zurückgibt:

cursor = conn.cursor()
cursor.execute("SELECT id, name, cats FROM profile")
for (id, name, cats) in cursor:
  print("id: %s, name: %s, cats: %s" % (id, name, cats))
print("Number of rows returned: %d" % cursor.rowcount)
cursor.close()

Die Methode fetchall() gibt die gesamte Ergebnismenge als eine Liste von Tupeln zurück. Iteriere durch die Liste, um auf die Zeilen zuzugreifen:

cursor = conn.cursor()
cursor.execute("SELECT id, name, cats FROM profile")
rows = cursor.fetchall()
for row in rows:
  print("id: %s, name: %s, cats: %s" % (row[0], row[1], row[2]))
print("Number of rows returned: %d" % cursor.rowcount)
cursor.close()

Die DB-API bietet keine Möglichkeit, eine Ergebnismenge zurückzuspulen. Daher kann fetchall() praktisch sein, wenn du mehr als einmal durch die Zeilen der Ergebnismenge iterieren oder direkt auf einzelne Werte zugreifen musst. Wenn zum Beispiel rows die Ergebnismenge enthält, kannst du auf den Wert der dritten Spalte in der zweiten Zeile mit rows[1][2] zugreifen (die Indizes beginnen bei 0, nicht bei 1).

Geh

Die Go sql Schnittstelle verfügt über zwei Connection-Object-Funktionen, um SQL-Anweisungen auszuführen: Exec() für Anweisungen, die keine Ergebnismenge zurückgeben, und Query() für Anweisungen, die eine Ergebnismenge zurückgeben. Beide geben error zurück, wenn die Anweisung fehlschlägt.

Um eine Anweisung auszuführen, die keine Zeile zurückgibt, wie z. B. INSERT, UPDATE oder DELETE, verwendest du die Funktion Exec(). Ihre Rückgabewerte können den Typ Result oder error haben. Die Schnittstelle Result hat eine Funktion RowsAffected(), die angibt, wie viele Zeilen geändert wurden:

sql := "UPDATE profile SET cats = cats+1 WHERE name = 'Sybil'"
res, err := db.Exec(sql)

if err != nil {
	panic(err.Error())
}

affectedRows, err := res.RowsAffected()

if err != nil {
	log.Fatal(err)
}

fmt.Printf("The statement affected %d rows\n", affectedRows)

Für die Anweisungen, die eine Ergebnismenge zurückgeben, typischerweise SELECT, verwendest du die Funktion Query(). Diese Funktion gibt den Cursor an das Objekt vom Typ Rows zurück, das das Ergebnis der Abfrage enthält. Rufe die Funktion Next() auf, um durch das Ergebnis zu iterieren, und speichere die zurückgegebenen Werte mit der Funktion Scan() in den Variablen. Wenn Next() false zurückgibt, bedeutet dies, dass es kein Ergebnis gibt:

res, err := db.Query("SELECT id, name, cats FROM profile")

defer res.Close()

if err != nil {
	log.Fatal(err)
}

for res.Next() {

	var profile Profile
	err := res.Scan(&profile.id, &profile.name, &profile.cats)

	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%+v\n", profile)
}

Wenn Next() aufgerufen wird und false zurückgibt, werden die Rows automatisch geschlossen. Andernfalls musst du sie mit der Funktion Close() schließen.

Für Abfragen, die höchstens eine Zeile zurückgeben sollen, gibt es eine spezielle Funktion, QueryRow(), die ein Row Objekt zurückgibt, das sofort gescannt werden kann. QueryRow() gibt nie einen Fehler zurück, bis Scan() aufgerufen wird. Wenn die Abfrage keine Zeile liefert, gibt Scan() ErrNoRows zurück:

row := db.QueryRow("SELECT id, name, cats FROM profile where id=3")

var profile Profile
err = row.Scan(&profile.id, &profile.name, &profile.cats)

if err == sql.ErrNoRows {
	fmt.Println("No row matched!")
} else if err != nil {
	log.Fatal(err)
} else {
	fmt.Printf("%v\n", profile)
}

Java

Die JDBC-Schnittstelle bietet spezifische Objekttypen für die verschiedenen Phasen derSQL-Anweisungsverarbeitung. Die Anweisungen werden in JDBC mit Java-Objekten eines Typs ausgeführt. Die Ergebnisse werden, falls vorhanden, als Objekte eines anderen Typs zurückgegeben.

Um eine Anweisung auszuführen, holst du dir zunächst ein Statement Objekt, indem du die createStatement() Methode deines Connection Objekts aufrufst:

Statement s = conn.createStatement ();

Verwende dann das Statement Objekt, um die Anweisung an den Server zu senden. JDBC bietet dafür mehrere Methoden. Wähle die Methode, die für die Art der Anweisung geeignet ist: executeUpdate() für Anweisungen, die keine Ergebnismenge zurückgeben, executeQuery() für Anweisungen, die eine Ergebnismenge zurückgeben, undexecute() , wenn du es nicht weißt. Jede Methode löst eine Ausnahme aus, wenn die Anweisung fehlschlägt.

Die Methode executeUpdate() sendet eine Anweisung, die keine Ergebnismenge erzeugt, an den Server und gibt einen Zähler zurück, der die Anzahl der betroffenen Zeilen angibt. Wenn du mit dem Statement-Objekt fertig bist, schließe es:

Statement s = conn.createStatement ();
int count = s.executeUpdate(
               "UPDATE profile SET cats = cats+1 WHERE name = 'Sybil'");
s.close();   // close statement
System.out.println("Number of rows updated: " + count);

Für Anweisungen, die eine Ergebnismenge zurückgeben, verwendest du executeQuery(). Hol dir dann ein Ergebnismengenobjekt und benutze es, um die Zeilenwerte abzurufen. Wenn du fertig bist, schließe die Objekte "Ergebnismenge" und "Anweisung":

Statement s = conn.createStatement ();
s.executeQuery("SELECT id, name, cats FROM profile");
ResultSet rs = s.getResultSet();
int count = 0;
while (rs.next ()) { // loop through rows of result set\
  int id = rs.getInt(1);   // extract columns 1, 2, and 3
  String name = rs.getString(2);
  int cats = rs.getInt(3);
  System.out.println("id: " + id
                     + ", name: " + name
                     + ", cats: " + cats);
  ++count;
}
rs.close ();  // close result set
s.close ();   // close statement
System.out.println ("Number of rows returned: " + count);

Das ResultSet Objekt, das von der getResultSet() Methode deines Statement Objekts zurückgegeben wird, hat seine eigenen Methoden, wie z.B. next(), um Zeilen zu holen und verschiedene getXXX() Methoden, die auf die Spalten der aktuellen Zeile zugreifen. Zu Beginn steht die Ergebnismenge direkt vor der ersten Zeile der Menge. Rufe next()auf, um jede Zeile nacheinander zu holen, bis sie false zurückgibt. Um die Anzahl der Zeilen in einer Ergebnismenge zu ermitteln, zählst du sie selbst, wie im vorangegangenen Beispiel gezeigt.

Tipp

Bei Abfragen, die eine einzige Ergebnismenge zurückgeben, ist es nicht notwendig, getResultSet aufzurufen. Der obige Code könnte wie folgt geschrieben werden:

ResultSet rs = s.executeQuery("SELECT id, name, cats FROM profile");

Ein separater Aufruf ist erforderlich, wenn deine Abfrage mehrere Ergebnissätze zurückgeben kann, zum Beispiel, wenn du eine gespeicherte Routine aufrufst.

Um auf Spaltenwerte zuzugreifen, verwendest du die Methoden getInt(), getString(), getFloat() oder getDate(). Um den Spaltenwert als generisches Objekt zu erhalten, verwende getObject(). Das Argument für einen getXXX() Aufruf kann entweder die Spaltenposition (beginnend bei 1, nicht bei 0) oder den Spaltennamen angeben. Das vorherige Beispiel zeigt, wie man die Spalten id, name und cats über die Position abruft. Um stattdessen auf die Spalten nach Namen zuzugreifen, schreibst du die Zeilenabrufschleife wie folgt:

while (rs.next ()) { // loop through rows of result set
  int id = rs.getInt("id");
  String name = rs.getString("name");
  int cats = rs.getInt("cats");
  System.out.println("id: " + id
                     + ", name: " + name
                     + ", cats: " + cats);
  ++count;
}

Um einen bestimmten Spaltenwert abzurufen, verwendest du einen beliebigen getXXX() Aufruf, der für den Datentyp sinnvoll ist. getString() ruft zum Beispiel jeden Spaltenwert als String ab:

String id = rs.getString("id");
String name = rs.getString("name");
String cats = rs.getString("cats");
System.out.println("id: " + id
                   + ", name: " + name
                   + ", cats: " + cats);

Oder verwende getObject(), um Werte als generische Objekte abzurufen und die Werte bei Bedarf zu konvertieren. Das folgende Beispiel verwendet toString(), um Objektwerte in eine druckbare Form umzuwandeln:

Object id = rs.getObject("id");
Object name = rs.getObject("name");
Object cats = rs.getObject("cats");
System.out.println("id: " + id.toString()
                   + ", name: " + name.toString()
                   + ", cats: " + cats.toString());

Um die Anzahl der Spalten in der Ergebnismenge zu ermitteln, greifst du auf ihre Metadaten zu:

ResultSet rs = s.getResultSet();
ResultSetMetaData md = rs.getMetaData(); // get result set metadata
int ncols = md.getColumnCount();         // get column count from metadata

Die dritte Methode zur Ausführung von JDBC-Anweisungen, execute(), funktioniert für beide Arten von Anweisungen. Sie ist besonders nützlich, wenn du einen Anweisungsstring von einer externen Quelle erhältst und nicht weißt, ob er eine Ergebnismenge erzeugt oder mehrere Ergebnismengen zurückgibt. Der Rückgabewert von execute() gibt den Anweisungstyp an, sodass du ihn entsprechend verarbeiten kannst: Wenn execute() true zurückgibt, gibt es eine Ergebnismenge, andernfalls nicht. Normalerweise würdest du die Anweisung so verwenden, wobei stmtStr eine beliebige SQL-Anweisung darstellt:

Statement s = conn.createStatement();
if (s.execute(stmtStr)) {
  // there is a result set
  ResultSet rs = s.getResultSe();

  // ... process result set here ...

  rs.close();  // close result set
} else {
  // there is no result set, just print the row count
  System.out.println("Number of rows affected: " + s.getUpdateCount ());
}
s.close();   // close statement

4.5 Umgang mit Sonderzeichen und NULL-Werten in Anweisungen

Problem

Du musst SQL-Anweisungen konstruieren, die sich auf Datenwerte beziehen, die Sonderzeichen wie Anführungszeichen oder Backslashes oder Sonderwerte wie NULL enthalten. Oder du erstellst Anweisungen, die Daten aus externen Quellen verwenden, und möchtest SQL-Injection-Angriffe verhindern.

Lösung

Verwende den Platzhalter-Mechanismus deiner API oder die Quoting-Funktion, um die Daten für das Einfügen zu sichern.

Diskussion

Bis zu diesem Punkt in diesem Kapitel haben unsere Anweisungen sichere Datenwerte verwendet, die keine besondere Behandlung erfordern. Wir können zum Beispiel die folgenden SQL-Anweisungen einfach aus einem Programm heraus konstruieren, indem wir die Datenwerte wörtlich in die Anweisungsstrings schreiben:

SELECT * FROM profile WHERE age > 40 AND color = 'green';

INSERT INTO profile (name,color) VALUES('Gary','blue');

Manche Datenwerte sind jedoch nicht so einfach zu handhaben und verursachen Probleme, wenn du nicht aufpasst. In Anweisungen können Werte verwendet werden, die Sonderzeichen wie Anführungszeichen, Backslashes, binäre Daten oder Werte, die NULL sind, enthalten. Im Folgenden werden die Schwierigkeiten beschrieben, die diese Werte verursachen, und die richtigen Techniken für den Umgang mit ihnen.

Angenommen, du möchtest diese INSERTAnweisung ausführen:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('Alison','1973-01-12','blue','eggroll',4);

Daran ist nichts Ungewöhnliches. Wenn du aber den Wert der Spalte in etwas wie änderst, das ein einfaches Anführungszeichen enthält, wird die Anweisung syntaktisch ungültig: name De'Mont

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De'Mont','1973-01-12','blue','eggroll',4);

Das Problem ist das einfache Anführungszeichen innerhalb eines Strings mit einfachen Anführungszeichen. Um die Anweisung legal zu machen, indem du das Anführungszeichen ausklammerst, stellst du ihm entweder ein einfaches Anführungszeichen oder einen Backslash voran:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De''Mont','1973-01-12','blue','eggroll',4);
INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De\'Mont','1973-01-12','blue','eggroll',4);

Alternativ kannst du den Wert nameselbst in Anführungszeichen setzen (vorausgesetzt, der SQL-Modus ANSI_QUOTES ist nicht aktiviert):

INSERT INTO profile (name,birth,color,foods,cats)
VALUES("De'Mont",'1973-01-12','blue','eggroll',4);

Wenn du eine Anweisung wörtlich in dein Programm schreibst, kannst du den Wert von name von Hand auslassen oder in Anführungszeichen setzen, weil du weißt, wie der Wert lautet. Aber wenn der Name in einer Variablen gespeichert ist, weißt du nicht unbedingt, wie der Wert der Variablen lautet. Schlimmer noch: Ein einfaches Anführungszeichen ist nicht das einzige Zeichen, mit dem du rechnen musst; auch doppelte Anführungszeichen und Backslashes verursachen Probleme. Und wenn die Datenbank Binärdaten wie Bilder oder Soundclips speichert, kann ein Wert alles Mögliche enthalten - nicht nur Anführungszeichen oder Backslashes, sondern auch andere Zeichen wie Nullen (Bytes mit Nullwerten). Die Notwendigkeit, mit Sonderzeichen richtig umzugehen, ist besonders akut in einer Webumgebung, in der Anweisungen mit Hilfe von Formulareingaben erstellt werden (z. B. wenn du nach Zeilen suchst, die mit den vom Benutzer eingegebenen Suchbegriffen übereinstimmen). Du musst in der Lage sein, mit jeder Art von Eingabe umzugehen, da du nicht vorhersehen kannst, welche Art von Informationen ein Benutzer eingeben wird. Es ist nicht ungewöhnlich, dass böswillige Benutzer Müllwerte mit problematischen Zeichen eingeben, um die Sicherheit deines Servers zu gefährden und sogar fatale Befehle wie DROP TABLE auszuführen. Das ist eine Standardtechnik zum Ausnutzen unsicherer Skripte, die sogenannte SQL-Injection.

Der SQL-Wert NULL ist keinSonderzeichen, aber auch er erfordert eine besondere Behandlung. In SQL bedeutet NULL keinen Wert. Das kann je nach Kontext verschiedene Bedeutungen haben, z. B. unbekannt, fehlend, außerhalb des Bereichs und so weiter. Bislang haben wir in unseren Anweisungen keine NULL Werte verwendet, um die damit verbundenen Komplikationen zu vermeiden. Wenn du zum Beispiel die Lieblingsfarbe von De'Mont nicht kennst, kannst du die Spalte color auf NULLsetzen - aber nicht, indem du die Anweisung wie folgt schreibst:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De''Mont','1973-01-12','NULL','eggroll',4);

Stattdessen darf der Wert NULL keine einschließenden Anführungszeichen haben:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De''Mont','1973-01-12',NULL,'eggroll',4);

Würdest du die Anweisung wörtlich in dein Programm schreiben, würdest du einfach das Wort NULL schreiben. Aber wenn der Wert color aus einer Variablen stammt, ist die richtige Vorgehensweise nicht so offensichtlich. Du musst wissen, ob der Wert der Variable NULL ist, um zu entscheiden, ob du ihn in Anführungszeichen setzen musst, wenn du die Anweisung konstruierst.

Für den Umgang mit Sonderzeichen wie Anführungszeichen und Backslashes und mit Sonderwerten wie NULL stehen dir zwei Möglichkeiten zur Verfügung:

  • Verwende Platzhalter im Anweisungsstring, um symbolisch auf die Werte vonzu verweisen, und verbinde dann die Datenwerte mit den Platzhaltern, wenn du die Anweisung ausführst. Dies ist die bevorzugte Methode, da die API selbst alle oder die meiste Arbeit für dich übernimmt, indem sie die Werte bei Bedarf in Anführungszeichen setzt, Sonderzeichen im Datenwert in Anführungszeichen setzt oder escapet und möglicherweise einen Sonderwert so interpretiert, dass er auf NULLohne Anführungszeichen abgebildet wird.

  • Verwende eine Quoting-Funktion (wenn deine API eine anbietet), umDatenwerte in eine sichere Form zu konvertieren, die für die Verwendung in Anweisungsstrings geeignet ist.

In diesem Abschnitt wird gezeigt, wie du diese Techniken verwendest, um Sonderzeichen und NULL Werte für jede API zu behandeln. Eines der hier gezeigten Beispiele zeigt, wie man eine Tabellenzeile profile einfügt, die De'Mont für den Wert name und NULL für den Wert color enthält. Die hier gezeigten Prinzipien sind jedoch allgemein anwendbar und können für alle Sonderzeichen verwendet werden, auch für solche, die in binären Daten vorkommen. Außerdem sind die Prinzipien nicht auf INSERT Anweisungen beschränkt. Sie funktionieren auch für andere Arten von Anweisungen, wie z. B. SELECT. Eines der anderen hier gezeigten Beispiele zeigt, wie eine SELECTAnweisung mit Platzhaltern ausgeführt werden kann.

Die Verarbeitung von Sonderzeichen und NULL Werten kommt in anderen Zusammenhängen vor, die an anderer Stelle behandelt werden:

  • Die hier beschriebenen Platzhalter- und Quoting-Techniken gelten nur für Datenwerte und nicht für Bezeichner wie Datenbank- oder Tabellennamen. Eine Diskussion über das Quoting von Bezeichnern findest du in Rezept 4.6.

  • Vergleiche von NULL Werten erfordern andere Operatoren als nichtNULL Werte. In Rezept 5.6 wird beschrieben, wie man SQL-Anweisungen konstruiert, die NULL Vergleiche innerhalb von Programmen durchführen.

  • In diesem Abschnitt geht es darum, wie du Sonderzeichen in deine Datenbank bekommst. Ein verwandtes Thema ist der umgekehrte Vorgang der Umwandlung von Sonderzeichen in Werten, die von deiner Datenbank zurückgegeben werden, um sie in verschiedenen Kontexten anzuzeigen. Wenn du zum Beispiel HTML-Seiten erstellst, die Werte aus deiner Datenbank enthalten, musst du die Zeichen < und > in diesen Werten in die HTML-Entitäten &lt; und &gt; umwandeln, um sicherzustellen, dass sie richtig angezeigt werden.

Platzhalter verwenden

Mit Platzhaltern kannst du vermeiden, Datenwerte wörtlich in SQL-Anweisungen zu schreiben. Bei diesem Ansatz schreibst du Anweisungen mit Platzhaltern - speziellen Markierungen, die angeben, wohin die Werte gehören. Zwei gängige Parametermarkierungen sind ? und %s. Je nach Markierung schreibst du die Anweisung INSERT so um, dass Platzhalter wie folgt verwendet werden:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES(?,?,?,?,?);

INSERT INTO profile (name,birth,color,foods,cats)
VALUES(%s,%s,%s,%s,%s);

Dann übergibst du den Anweisungsstring an den Datenbankserver und lieferst die Datenwerte separat. Die API bindet die Werte an die Platzhalter, um sie zu ersetzen, so dass eine Anweisung entsteht, die die Datenwerte enthält.

Ein Vorteil von Platzhaltern ist, dass bei der Bindung von Parametern automatisch Zeichen wie Anführungszeichen und Backslashes umgangen werden. Dies ist besonders nützlich, wenn du binäre Daten wie Bilder in deine Datenbank einfügst oder Datenwerte mit unbekanntem Inhalt verwendest, z. B. Eingaben, die von einem Benutzer über ein Formular auf einer Webseite übermittelt werden. Außerdem gibt es in der Regel einen speziellen Wert, den du an einen Platzhalter bindest, um anzugeben, dass du einen SQL NULL Wert in der resultierenden Anweisung haben möchtest.

Ein zweiter Vorteil von Platzhaltern besteht darin, dass du eine Anweisung im Voraus vorbereiten und sie dann wiederverwenden kannst, indem du jedes Mal, wenn sie ausgeführt wird, andere Werte an sie bindest. Vorbereitete Anweisungen fördern also die Wiederverwendung von Anweisungen. Anweisungen werden allgemeiner, weil sie Platzhalter und keine spezifischen Datenwerte enthalten. Wenn du eine Operation immer wieder durchführst, kannst du eine vorbereitete Anweisung wiederverwenden, indem du einfach jedes Mal andere Datenwerte an sie bindest, wenn du sie ausführst. Einige Datenbanksysteme (MySQL gehört nicht dazu) haben die Möglichkeit, vor der Ausführung einer vorbereiteten Anweisung ein Preparing oder sogar eine Ausführungsplanung durchzuführen. Bei einer Anweisung, die später mehrmals ausgeführt wird, reduziert dies den Overhead, weil alles, was vor der Ausführung getan werden kann, nur einmal getan werden muss, nicht einmal pro Ausführung. Wenn ein Programm zum Beispiel eine bestimmte Art von SELECT Anweisung mehrmals ausführt, während es läuft, kann ein solches Datenbanksystem einen Plan für die Anweisung erstellen und ihn dann jedes Mal wieder verwenden, anstatt den Plan immer wieder neu zu erstellen. MySQL erstellt keine Abfragepläne im Voraus, so dass du durch die Verwendung von Prepared Statements keinen Leistungsschub erhältst. Wenn du jedoch ein Programm auf eine Datenbank portierst, die Abfragepläne wiederverwendet, und du dein Programm so geschrieben hast, dass es Prepared Statements verwendet, kannst du diesen Vorteil der Prepared Statements automatisch nutzen. Du musst nicht von nicht vorbereiteten Anweisungen konvertieren, um diesen Vorteil zu nutzen.

Ein dritter (zugegebenermaßen subjektiver) Vorteil ist, dass Code, der platzhalterbasierte Anweisungen verwendet, leichter zu lesen sein kann. Wenn du diesen Abschnitt durcharbeitest, vergleiche die hier verwendeten Anweisungen mit denen aus Rezept 4.4, in denen keine Platzhalter verwendet wurden, um zu sehen, welche du bevorzugst.

Verwenden einer Quoting-Funktion

Einige APIs stellen eine Quoting-Funktion zur Verfügung, die einen Datenwert alsArgument annimmt und einen korrekt quotierten und escapeten Wert zurückgibt, der sich zum sicheren Einfügen in eine SQL-Anweisung eignet. Dies ist weniger üblich als die Verwendung von Platzhaltern, kann aber nützlich sein, wenn du Anweisungen erstellst, die du nicht sofort ausführen willst. Allerdings musst du eine Verbindung zum Datenbankserver haben, wenn du eine solche Quoting-Funktion verwendest, da die API die richtigen Quoting-Regeln erst dann auswählen kann, wenn der Datenbanktreiber bekannt ist. (Die Regeln unterscheiden sich je nach Datenbanksystem.)

Hinweis

Wie wir später noch zeigen werden, geben einige APIs allenichtNULL Werte, auch Zahlen, als Zeichenketten an, wenn sie an Parametermarker gebunden werden. Dies kann in Kontexten, die Zahlen erfordern, ein Problem darstellen, wie in Rezept 5.11 beschrieben.

Perl

Um Platzhalter mit Perl DBI zu verwenden, fügst du an jeder Stelle des Datenwerts eine ? in deinen SQL-Anweisungsstring ein. Dann bindest du die Werte an die Anweisung, indem du sie an do() oder execute() übergibst oder indem du eine DBI-Methode aufrufst, die speziell für die Ersetzung von Platzhaltern vorgesehen ist. Verwende undef, um einen NULL Wert an einen Platzhalter zu binden.

Mit do() fügst du die Zeile profile für De'Mont hinzu, indem du den Anweisungsstring und die Datenwerte im selben Aufruf übergibst:

my $count = $dbh->do ("INSERT INTO profile (name,birth,color,foods,cats)
                       VALUES(?,?,?,?,?)",
                      undef,
                      "De'Mont", "1973-01-12", undef, "eggroll", 4);

Die Argumente nach der Anweisungszeichenfolge sind undef, dann ein Datenwert für jeden Platzhalter. Das Argument undef ist ein historisches Artefakt, muss aber vorhanden sein.

Alternativ kannst du den Anweisungsstring an prepare() übergeben, um ein Anweisungshandle zu erhalten, und dann dieses Handle verwenden, um die Datenwerte an execute() zu übergeben:

my $sth = $dbh->prepare ("INSERT INTO profile (name,birth,color,foods,cats)
                          VALUES(?,?,?,?,?)");
my $count = $sth->execute ("De'Mont", "1973-01-12", undef, "eggroll", 4);

In beiden Fällen erzeugt DBI diese Anweisung:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De\'Mont','1973-01-12',NULL,'eggroll','4');

Der DBI-Mechanismus von Perl setzt Datenwerte in Anführungszeichen, wenn sie an die Anweisungszeichenfolge gebunden werden. Deshalb solltest du die ? Zeichen in der Zeichenfolge nicht in Anführungszeichen setzen.

Beachte, dass der Platzhalter-Mechanismus Anführungszeichen um numerische Werte hinzufügt. DBI verlässt sich darauf, dass der MySQL-Server bei Bedarf eine Typkonvertierung durchführt, um Strings in Zahlen umzuwandeln. Wenn du undef an einen Platzhalter bindest, fügt DBI ein NULL in die Anweisung ein und verzichtet korrekterweise auf das Hinzufügen von Anführungszeichen.

Um dieselbe Anweisung immer wieder auszuführen, verwende prepare() einmal und rufe dann jedes Mal execute() mit den entsprechenden Datenwerten auf.

Du kannst diese Methoden auch für andere Arten von Anweisungen verwenden. Die folgende SELECTAnweisung verwendet zum Beispiel einen Platzhalter, um nach Zeilen zu suchen, die einen cats Wert größer als 2 haben:

my $sth = $dbh->prepare ("SELECT * FROM profile WHERE cats > ?");
$sth->execute (2);
while (my $ref = $sth->fetchrow_hashref ())
{
  print "id: $ref->{id}, name: $ref->{name}, cats: $ref->{cats}\n";
}

High-Level-Abrufmethoden wie selectrow_array() und selectall_arrayref() können auch mit Platzhaltern verwendet werden. Wie bei der Methode do()sind die Argumente der Anweisungsstring undef und die Datenwerte, die an die Platzhalter gebunden werden sollen. Hier ist ein Beispiel:

my $ref = $dbh->selectall_arrayref (
  "SELECT name, birth, foods FROM profile WHERE id > ? AND color = ?",
  undef, 3, "green"
);

Die Perl DBI quote()Datenbank-Handle-Methode ist eine Alternative zur Verwendung von Platzhaltern. Hier erfährst du, wie du mit quote() einen Anweisungsstring erstellst, der eine neue Zeile in die Tabelle profile einfügt. Schreibe die %s Formatangaben ohne einschließende Anführungszeichen, da quote() sie bei Bedarf automatisch einfügt. Werte, die nichtundef sind, werden mit Anführungszeichen eingefügt, und undef Werte werden als NULL ohne Anführungszeichen eingefügt:

my $stmt = sprintf ("INSERT INTO profile (name,birth,color,foods,cats)
                     VALUES(%s,%s,%s,%s,%s)",
                    $dbh->quote ("De'Mont"),
                    $dbh->quote ("1973-01-12"),
                    $dbh->quote (undef),
                    $dbh->quote ("eggroll"),
                    $dbh->quote (4));
my $count = $dbh->do ($stmt);

Der Anweisungsstring, der durch diesen Code erzeugt wird, ist der gleiche wie bei der Verwendung von Platzhaltern.

Ruby

Ruby DBI verwendet ? alsPlatzhalterzeichen in SQL-Anweisungen und nil als Wert für die Bindung eines SQL NULL Wertes an einen Platzhalter.

Um ? zu verwenden, übergibst du die Anweisungszeichenfolge an prepare, um ein Anweisungshandle zu erhalten, und verwendest dieses Handle, um execute mit den Datenwerten aufzurufen:

sth = client.prepare("INSERT INTO profile (name,birth,color,foods,cats)
                   VALUES(?,?,?,?,?)")
sth.execute("De'Mont", "1973-01-12", nil, "eggroll", 4)

Mysql2 fügt ordnungsgemäß maskierte Anführungszeichen und einen ordnungsgemäß unquotierten NULL Wert in die resultierende Anweisung ein:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De\'Mont','1973-01-12',NULL,'eggroll',4);

Der Ruby Mysql2-Platzhaltermechanismus setzt Datenwerte bei Bedarf in Anführungszeichen, wenn sie an den Anweisungsstring gebunden werden. Deshalb solltest du die ?Zeichen im String nicht in Anführungszeichen setzen.

PHP

Um Platzhalter mit der PDO-Erweiterung zu verwenden, übergibst du einen Statement-String an prepare(), um ein Statement-Objekt zu erhalten. Der String kann ? Zeichen als Platzhaltermarkierungen enthalten. Mit diesem Objekt rufst du execute() auf und übergibst ihm das Array der Datenwerte, die mit den Platzhaltern verbunden werden sollen. Verwende den PHP-Wert NULL, um einen SQL-Wert NULL an einen Platzhalter zu binden. Der Code zum Hinzufügen der Tabellenzeile profile für De'Mont sieht wie folgt aus:

$sth = $dbh->prepare ("INSERT INTO profile (name,birth,color,foods,cats)
                          VALUES(?,?,?,?,?)");
$sth->execute (array ("De'Mont","1973-01-12",NULL,"eggroll",4));

Die resultierende Anweisung enthält ein ordnungsgemäßes Anführungszeichen und einen ordnungsgemäß nicht in Anführungszeichen gesetzten Wert NULL:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De\'Mont','1973-01-12',NULL,'eggroll','4');

Der PDO-Platzhalter-Mechanismus setzt Datenwerte in Anführungszeichen, wenn sie an den Anweisungsstring gebunden werden. Daher sollten Sie die ? Zeichen im String nicht in Anführungszeichen setzen. (Beachten Sie, dass auch der numerische Wert 4 in Anführungszeichen gesetzt wird; PDO verlässt sich darauf, dass MySQL bei der Ausführung der Anweisung die erforderliche Typumwandlung vornimmt).

Python

Das Connector/Python-Modul implementiert Platzhalter mit %s Formatangaben im SQL-Anweisungsstring. (Umein % Zeichen in der Anweisung zu platzieren, musst du %% im Anweisungsstring verwenden.) Um Platzhalter zu verwenden, rufst du die Methode execute() mit zwei Argumenten auf: einem Anweisungsstring, der die Formatspezifikationen enthält, und einer Sequenz, die die Werte enthält, die an den Anweisungsstring gebunden werden sollen. Mit None kannst du einen NULL Wert an einen Platzhalter binden. Der Code zum Hinzufügen der Tabellenzeile profile für De'Mont sieht wie folgt aus:

cursor = conn.cursor()
cursor.execute('''
               INSERT INTO profile (name,birth,color,foods,cats)
               VALUES(%s,%s,%s,%s,%s)
               ''', ("De'Mont", "1973-01-12", None, "eggroll", 4))
cursor.close()
conn.commit()

Die Anweisung, die durch den vorangegangenen execute() -Aufruf an den Server gesendet wird, sieht wie folgt aus:

INSERT INTO profile (name,birth,color,foods,cats)
VALUES('De\'Mont','1973-01-12',NULL,'eggroll',4);

Der Connector/Python-Platzhalter-Mechanismus setzt Datenwerte bei Bedarf in Anführungszeichen, wenn sie an den Anweisungsstring gebunden sind. Deshalb solltest du die %s Formatangaben im String nicht in Anführungszeichen setzen.

Wenn du nur einen einzigen Wert hast, val, an einen Platzhalter binden willst, schreibst du ihn als Sequenz mit der Syntax (val,):

cursor = conn.cursor()
cursor.execute("SELECT id, name, cats FROM profile WHERE cats = %s", (2,))
for (id, name, cats) in cursor:
  print("id: %s, name: %s, cats: %s" % (id, name, cats))
cursor.close()

Alternativ kannst du den Wert als Liste schreiben, indem du die Syntax [val].

Geh

Das Go sql Paket verwendet Fragezeichen (?) als Platzhalter. Du kannst Platzhalter mit einzelnen Aufrufen von Exec() oder Query() verwenden, aber auch die Anweisung im Voraus vorbereiten und später ausführen. Letztere Methode ist gut, wenn du die Anweisung mehrmals ausführen musst. Der Code zum Hinzufügen der Tabellenzeile profile für De'Mont sieht so aus:

stmt := `INSERT INTO profile (name,birth,color,foods,cats)
           VALUES(?,?,?,?,?)`
_, err = db.Exec(stmt, "De'Mont", "1973-01-12", nil, "eggroll", 4)

Der gleiche Code mit dem Aufruf Prepare() sieht wie folgt aus:

pstmt, err := db.Prepare(`INSERT INTO profile (name,birth,color,foods,cats)
                 VALUES(?,?,?,?,?)`)
if err != nil {
  log.Fatal(err)
}
defer pstmt.Close()

_, err = pstmt.Exec("De'Mont", "1973-01-12", nil, "eggroll", 4)

Java

JDBC bietet Unterstützung für Platzhalter, wenn du vorbereitete Anweisungen verwendest. Erinnere dich daran, dassdas Verfahren zur Ausführung von nicht vorbereiteten Anweisungen in JDBC darin besteht, ein Statement Objekt zu erstellen und dann die Anweisungszeichenfolge an die Funktionen executeUpdate(), executeQuery(), oder execute() zu übergeben. Um stattdessen eine vorbereitete Anweisung zu verwenden, erstellst du ein PreparedStatement Objekt, indem du einen Anweisungsstring mit ?Platzhalterzeichen an die prepareStatement() Methode deines Verbindungsobjekts übergibst. Verbinde dann die Datenwerte mit der Anweisung mithilfe von setXXX() Methoden. Zum Schluss führst du die Anweisung aus, indem du executeUpdate(), executeQuery() oder execute() mit einer leeren Argumentliste aufrufst.

Hier ist ein Beispiel, das executeUpdate() verwendet, um eine Anweisung INSERT auszuführen, die die Tabellenzeile profile für De'Mont hinzufügt:

PreparedStatement s;
s = conn.prepareStatement(
            "INSERT INTO profile (name,birth,color,foods,cats)"
            + " VALUES(?,?,?,?,?)");
s.setString(1, "De'Mont");         // bind values to placeholders
s.setString(2, "1973-01-12");
s.setNull(3, java.sql.Types.CHAR);
s.setString(4, "eggroll");
s.setInt(5, 4);
s.close();   // close statement

Die setXXX() Methoden, die Datenwerte an Anweisungen binden, benötigen zwei Argumente: eine Platzhalterposition (beginnend mit 1, nicht 0) und den Wert, der an den Platzhalter gebunden werden soll. Wähle jeden Wertbindungsaufruf so, dass er dem Datentyp der Spalte entspricht, an die der Wert gebunden wird: setString(), um eine Zeichenkette an die Spalte name zu binden, setInt(), um eine ganze Zahl an die Spalte cats zu binden, und so weiter. (Wir haben ein wenig geschummelt, indem wir setString()verwendet haben, um den Datumswert für birthals String zu behandeln).

Ein Unterschied zwischen JDBC und den anderen APIs besteht darin, dass du NULL nicht durch die Angabe eines speziellen Werts (wie undef in Perl oder nil in Ruby) an einen Platzhalter bindest. Stattdessen rufst du setNull() mit einem zweiten Argument auf, das den Typ der Spalte angibt: java.sql.Types.CHAR für eine Zeichenkette, java.sql.Types.INTEGER für eine Ganzzahl und so weiter.

Die setXXX() Aufrufe fügen bei Bedarf Anführungszeichen um die Datenwerte hinzu, also setze keine Anführungszeichen um die ? Platzhalterzeichen im Anweisungsstring.

Um eine Anweisung zu behandeln, die eine Ergebnismenge zurückgibt, ist der Prozess ähnlich, aber führe die vorbereitete Anweisung mit executeQuery() statt mit executeUpdate() aus:

PreparedStatement s;
s = conn.prepareStatement("SELECT * FROM profile WHERE cats > ?");
s.setInt(1, 2);  // bind 2 to first placeholder
s.executeQuery();
// ... process result set here ...
s.close();     // close statement

4.6 Umgang mit Sonderzeichen in Bezeichnern

Problem

Du musst SQL-Anweisungen konstruieren, die sich auf Bezeichner beziehen, die spezielle Zeichen enthalten.

Lösung

Zitiere jeden Bezeichner, damit er sicher in Anweisungsstrings eingefügt werden kann.

Diskussion

InRezept 4.5 wird erläutert, wie man Sonderzeichen in Datenwerten mit Hilfe von Platzhaltern oder Quoting-Methoden behandelt. Sonderzeichen können auch in Bezeichnern wie Datenbank-, Tabellen- und Spaltennamen vorkommen. Der Tabellenname some table enthält zum Beispiel ein Leerzeichen, das standardmäßig nicht erlaubt ist:

mysql> CREATE TABLE some table (i INT);
ERROR 1064 (42000): You have an error in your SQL syntax near 'table (i INT)'

Sonderzeichen werden in Bezeichnern anders behandelt als in Datenwerten. Damit ein Bezeichner sicher in eine SQL-Anweisung eingefügt werden kann, musst du ihn in Anführungszeichen setzen, indem du ihn in Backticks einschließt:

mysql> CREATE TABLE `some table` (i INT);
Query OK, 0 rows affected (0.04 sec)

In MySQL sind Backticks für die Anführungszeichen von Bezeichnern immer erlaubt. Das doppelte Anführungszeichen ist auch erlaubt, wenn der ANSI_QUOTES SQL-Modus aktiviert ist. Wenn ANSI_QUOTESaktiviert ist, sind diese beiden Anweisungen also gleichwertig:

CREATE TABLE `some table` (i INT);
CREATE TABLE "some table" (i INT);

Wenn du wissen willst, welche Bezeichner-Zeichenerlaubt sind, führe eine SELECT@@sql_mode Anweisung aus, um den SQL-Modus für deine Sitzung abzurufen und prüfe, ob der Wert ANSI_QUOTES enthält.

Wenn ein Anführungszeichen im Bezeichner selbst vorkommt, verdopple es, wenn du den Bezeichner zitierst. Zum Beispiel: abc`def als `abc``def`.

Beachte, dass die Werte von Zeichenketten in MySQL normalerweise sowohl in einfachen als auch in doppelten Anführungszeichen angegeben werden können ('abc', "abc"). Das gilt jedoch nicht, wenn ANSI_QUOTES aktiviert ist. In diesem Fall interpretiert MySQL 'abc' als String und "abc" als Bezeichner, sodass du für Strings nur einfache Anführungszeichen verwenden darfst.

Innerhalb eines Programms kannst du eine Bezeichner-Quotierungsroutine verwenden, wenn deine API eine bereitstellt, oder selbst eine schreiben, wenn dies nicht der Fall ist. Perl DBI verfügt über eine Methode quote_identifier(), die einen korrekt quotierten Bezeichner zurückgibt. Bei einer API, die keine solche Methode hat, kannst du einen Bezeichner in Anführungszeichen setzen, indem du ihn in Backticks einschließt und alle Backticks, die innerhalb des Bezeichners vorkommen, verdoppelst. Hier ist eine PHP-Routine, die dies tut:

function quote_identifier ($ident)
{
  return ('`' . str_replace('`', '``', $ident) . '`');
}

Hinweis zur Portabilität: Wenn du deine eigenen Routinen für die Quotierung von Bezeichnern schreibst, erinnere dich daran, dass andere Datenbankmanagementsysteme (DBMS) möglicherweise andere Quotierungskonventionen verlangen.

In Kontexten, in denen Bezeichner als Datenwerte verwendet werden, solltest du sie auch als solche behandeln. Wenn du Informationen aus der INFORMATION_SCHEMA Metadaten-Datenbank auswählst, ist es üblich, die zurückzugebenden Zeilen durch die Angabe von Datenbankobjektnamen in der WHERE Klausel anzugeben. Diese Anweisung ruft zum Beispiel die Spaltennamen für die Tabelle profile in der Datenbank cookbook ab:

SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'cookbook' AND TABLE_NAME = 'profile';

Die Datenbank- und Tabellennamen werden hier als Datenwerte und nicht als Bezeichner verwendet. Wenn du diese Anweisung in einem Programm konstruieren willst, musst du sie mit Platzhaltern parametrisieren, nicht mit Bezeichnern in Anführungszeichen. In Ruby kannst du zum Beispiel so vorgehen:

sth = client.prepare("SELECT COLUMN_NAME
                        FROM INFORMATION_SCHEMA.COLUMNS
                        WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?")
names = sth.execute(db_name, tbl_name)

4.7 Erkennen von NULL-Werten in Ergebnismengen

Problem

Ein Abfrageergebnis enthält die Werte NULL, , aber du bist dir nicht sicher, wie du sie identifizieren sollst.

Lösung

Deine API hat wahrscheinlich einen speziellen Wert, der NULL per Konvention darstellt. Du musst nur wissen, welcher Wert das ist und wie du ihn testen kannst.

Diskussion

InRezept 4.5 wird beschrieben, wie man sich auf NULL Werte bezieht, wenn man Anweisungen an den Datenbankserver sendet. In diesem Abschnitt befassen wir uns stattdessen mit der Frage, wie man die vomDatenbankserver zurückgegebenen NULL Werte erkennt und verarbeitet. Im Allgemeinen geht es darum, zu wissen, auf welchen speziellen Wert die API die NULLWerte abbildet, oder welche Methode aufgerufen werden muss. Tabelle 4-5 zeigt diese Werte.

Tabelle 4-5. Erkannte NULL-Werte
SpracheWert oder Methode zur NULL-Erkennung
Perl DBIundef Wert
Ruby Mysql2 gemnil Wert
PHP PDONULL Wert
Python DB APINone Wert
Go sql SchnittstelleGo Null-Typ-Implementierung für die nullbaren Datentypen.
Java JDBCwasNull() Methode

Die folgenden Abschnitte zeigen eine sehr einfache Anwendung der NULL Werteerkennung. Die Beispiele rufen eine Ergebnismenge ab und drucken alle darin enthaltenen Werte aus, wobei die NULL Werte auf die druckbare Zeichenkette "NULL" abgebildet werden.

Um sicherzustellen, dass die Tabelle profileeine Zeile enthält, die einige NULL Werte enthält, verwende mysql, um die folgende INSERT Anweisung auszuführen, und führe dann die SELECT Anweisung aus, um zu überprüfen, ob die resultierende Zeile die erwarteten Werte enthält:

mysql> INSERT INTO profile (name) VALUES('Amabel');
mysql> SELECT * FROM profile WHERE name = 'Amabel';
+----+--------+-------+-------+-------+------+
| id | name   | birth | color | foods | cats |
+----+--------+-------+-------+-------+------+
|  9 | Amabel | NULL  | NULL  | NULL  | NULL |
+----+--------+-------+-------+-------+------+

Die Spalte id könnte eine andere Zahl enthalten, aber die anderen Spalten sollten wie abgebildet erscheinen, mit Werten von NULL.

Perl

Perl DBI stellt NULLWerte mit undef dar. Um solche Werte zu erkennen, verwendest du die Funktion defined(). Das ist besonders wichtig, wenn du Warnungen mit der Perl-Option -w aktivierst oder eine usewarnings Zeile in dein Skript einfügst. Ansonsten führt der Zugriff auf undef Werte dazu, dass Perl Use of uninitialized value Warnungen ausgibt.

Um diese Warnungen zu vermeiden, solltest du Spaltenwerte, die undef sein könnten, mit defined() testen, bevor du sie verwendest. Der folgende Code wählt einige Spalten aus der Tabelle profile aus und gibt "NULL" für alle undefinierten Werte in jeder Zeile aus. Dadurch werden die Werte von NULL in der Ausgabe explizit gemacht, ohne dass eine Warnmeldung ausgelöst wird:

my $sth = $dbh->prepare ("SELECT name, birth, foods FROM profile");
$sth->execute ();
while (my $ref = $sth->fetchrow_hashref ())
{
  printf "name: %s, birth: %s, foods: %s\n",
         defined ($ref->{name}) ? $ref->{name} : "NULL",
         defined ($ref->{birth}) ? $ref->{birth} : "NULL",
         defined ($ref->{foods}) ? $ref->{foods} : "NULL";
}

Leider ist das Testen mehrerer Spaltenwerte sehr schwerfällig und wird umso schwieriger, je mehr Spalten es gibt. Um dies zu vermeiden, solltest du undefinierte Werte mit einer Schleife oder map testen und setzen, bevor du sie ausdruckst. Das folgende Beispiel verwendet map:

my $sth = $dbh->prepare ("SELECT name, birth, foods FROM profile");
$sth->execute ();
while (my $ref = $sth->fetchrow_hashref ())
{
  map { $ref->{$_} = "NULL" unless defined ($ref->{$_}); } keys (%{$ref});
  printf "name: %s, birth: %s, foods: %s\n",
         $ref->{name}, $ref->{birth}, $ref->{foods};
}

Bei dieser Technik ist der Umfang des Codes zur Durchführung der Tests konstant und nicht proportional zur Anzahl der zu prüfenden Spalten. Außerdem gibt es keinen Bezug zu bestimmten Spaltennamen, so dass die Methode leichter in anderen Programmen oder als Grundlage für eine Dienstprogrammroutine verwendet werden kann.

Wenn du Zeilen in ein Array statt in einen Hash holst, verwende map, um undef Werte umzuwandeln:

my $sth = $dbh->prepare ("SELECT name, birth, foods FROM profile");
$sth->execute ();
while (my @val = $sth->fetchrow_array ())
{
  @val = map { defined ($_) ? $_ : "NULL" } @val;
  printf "name: %s, birth: %s, foods: %s\n",
         $val[0], $val[1], $val[2];
}

Ruby

Das Ruby Mysql2 Modul stellt NULL Werte mit nil dar, die durch Anwendung der nil?Methode auf einen Wert identifiziert werden können. Das folgende Beispiel verwendet die Methode nil? und den ternären Operator, um zu bestimmen, ob die Werte der Ergebnismenge so wie sie sind oder als String "NULL" für die Werte von NULL gedruckt werden sollen:

result = client.query("SELECT name, birth, foods FROM profile")
result.each do |row|
  printf "name %s, birth: %s, foods: %s\n", 
         row["name"].nil? ? "NULL" : row["name"], 
         row["birth"].nil? ? "NULL" : row["birth"], 
         row["foods"].nil? ? "NULL" : row["foods"]
end

PHP

PHP stellt SQL NULL Werte inErgebnismengen als PHP NULL Wert dar. Um festzustellen, ob ein Wert aus einer Ergebnismenge einen NULL Wert darstellt, vergleichst du ihn mit dem PHP NULL Wert unter Verwendung des === Triple Equal Operators:

if ($val === NULL)
{
  # $val is a NULL value
}

In PHP bedeutet der dreifache Gleichheitsoperator genau gleich. Der übliche ==, equal to, Vergleichsoperator ist hier nicht geeignet: Bei == betrachtet PHP den Wert NULL, den leeren String und 0 als gleich.

Der folgende Code verwendet den === Operator, um NULL Werte in einer Ergebnismenge zu identifizieren und sie als die Zeichenkette "NULL" auszugeben:

$sth = $dbh->query ("SELECT name, birth, foods FROM profile");
while ($row = $sth->fetch (PDO::FETCH_NUM))
{
  foreach (array_keys ($row) as $key)
  {
    if ($row[$key] === NULL)
      $row[$key] = "NULL";
  }
  print ("name: $row[0], birth: $row[1], foods: $row[2]\n");
}

Eine Alternative zu === für NULL Wertetests ist is_null().

Python

Python DB API-Programme stellen NULL inErgebnismengen mit None dar. Das folgende Beispiel zeigt, wie man NULL Werte erkennt:

cursor = conn.cursor()
cursor.execute("SELECT name, birth, foods FROM profile")

for row in cursor:
  row = list(row)  # convert nonmutable tuple to mutable list
  
  for i, value in enumerate(row):
    if value is None:  # is the column value NULL?
      row[i] = "NULL"
      
  print("name: %s, birth: %s, foods: %s" % (row[0], row[1], row[2]))
  
cursor.close()

Die innere Schleife prüft auf NULL Spaltenwerte, indem sie nach None sucht und diese in die Zeichenkette "NULL" umwandelt. Im Beispiel wird row vor der Schleife in ein veränderbares Objekt (Liste) umgewandelt, da fetchall()Zeilen als Sequenzwerte zurückgibt, die unveränderlich sind (nur lesbar).

Geh

Die Go sql Schnittstelle bietet spezielle Datentypen, um Werte in der Ergebnismenge zu behandeln, die NULL Werte enthalten können. Sie sind für die Standard-Go-Typen definiert. Tabelle 4-6 enthält eine Liste der Standarddatentypen und ihrer nullbaren Äquivalente.

Tabelle 4-6. Umgang mit NULL Werten in Go
Standard Go TypTyp, der NULL-Werte enthalten kann
boolNullBool
float64NullFloat64
int32NullInt32
int64NullInt64
stringNullString
time.TimeNullTime

Um eine Variable zu definieren, die sowohl Werte von NULL als auch vonNULL annehmen kann, wenn sie als Argument an die Funktion Scan() übergeben wird, verwende den entsprechenden nullbaren Typ.

Alle nullbaren Typen enthalten zwei Funktionen: Valid(), die true zurückgibt, wenn der Wert nicht NULL ist, und false, wenn der Wert NULL ist. Die zweite Funktion ist der Typname, der mit einem Großbuchstaben beginnt, z.B. String() für string Werte und Time() für time.Time Werte. Diese Methode gibt den aktuellen Wert zurück, wenn er nicht NULL ist.

Das folgende Beispiel zeigt dir, wie du NULL Werte in Go handhaben kannst:

// null-in-result.go : Selecting NULL values in Go
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

type Profile struct {
    name     string
    birth    sql.NullString
    foods    sql.NullString
}

func main() {

	db, err := sql.Open("mysql", "cbuser:cbpass@tcp(127.0.0.1:3306)/cookbook")

	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	sql := "SELECT name, birth, foods FROM profile"
	res, err := db.Query(sql)

	if err != nil {
		log.Fatal(err)
	}
	defer res.Close()

	for res.Next() {
		var profile Profile
		err = res.Scan(&profile.name, &profile.birth, &profile.foods)
		if err != nil {
			log.Fatal(err)
		}

        if (profile.birth.Valid && profile.foods.Valid) {
          fmt.Printf("name: %s, birth: %s, foods: %s\n", 
                      profile.name, profile.birth.String, profile.foods.String)
        } else if profile.birth.Valid {
          fmt.Printf("name: %s, birth: %s, foods: NULL\n",
                      profile.name, profile.birth.String)
        } else if profile.foods.Valid {
          fmt.Printf("name: %s, birth: NULL, foods: %s\n",
                      profile.name, profile.foods.String)
        } else {
          fmt.Printf("name: %s, birth: NULL, foods: NULL\n",
                      profile.name)
        }
	}
}
Warnung

Wir haben der Einfachheit halber den Typ NullString für die Spalte birth verwendet. Wenn du den Typ NullTime verwenden möchtest, musst du den Parameter parseTime=true zu deinem Verbindungsstring hinzufügen.

Tipp

Alternativ kannst du die Funktion COALESCE() von MySQL verwenden, um den Wert NULL während der Ausführung der Abfrage in einen String zu konvertieren:

sql := `SELECT name, 
               COALESCE(birth, '') as birthday
        FROM profile WHERE id = 9`
res, err := db.Query(sql)
defer res.Close()

Java

Wenn es bei JDBC-Programmen möglich ist, dass eine Spalte in einer ErgebnismengeNULL Werte enthält, ist es am besten, wenn du explizit nach ihnen suchst. Dazu holst du den Wert und rufst dann wasNull() auf, das true zurückgibt, wenn die Spalte NULL ist, und false, wenn nicht:

Object obj = rs.getObject (index);
if (rs.wasNull ())
{ /* the value's a NULL */ }

Das vorangegangene Beispiel verwendet getObject(), aber das Prinzip gilt auch für andere getXXX() Aufrufen ebenso.

Hier ist ein Beispiel, das jede Zeile einer Ergebnismenge als kommagetrennte Liste von Werten ausgibt, wobei "NULL" für jeden NULLWert ausgegeben wird:

Statement s = conn.createStatement();
s.executeQuery("SELECT name, birth, foods FROM profile");
ResultSet rs = s.getResultSet();
ResultSetMetaData md = rs.getMetaData();
int ncols = md.getColumnCount();
while (rs.next ()) { // loop through rows of result set
  for (int i = 0; i < ncols; i++) { // loop through columns
    String val = rs.getString(i+1);
    if (i > 0)
      System.out.print(", ");
    if (rs.wasNull())
      System.out.print("NULL");
    else
      System.out.print(val);
  }
  System.out.println();
}
rs.close();  // close result set
s.close();   // close statement

4.8 Abrufen der Verbindungsparameter

Problem

Du musst Verbindungsparameter für ein Skript erhalten, damit es sich mit einem MySQL-Server verbinden kann.

Lösung

Es gibt verschiedene Möglichkeiten, dies zu tun. Wähle aus den hier beschriebenen Alternativen.

Diskussion

Jedes Programm, das sich mit MySQL verbindet, gibt Verbindungsparameter wie den Benutzernamen, das Passwort und den Hostnamen an. Die bisher gezeigten Rezepte haben die Verbindungsparameter direkt in den Code eingefügt, der versucht, die Verbindung herzustellen, aber das ist nicht die einzige Möglichkeit für deine Programme, die Parameter zu erhalten. Diese Diskussion gibt einen kurzen Überblick über einige der verfügbaren Techniken:

Die Parameter in das Programm fest einbinden

Die Parameter können entweder in der Hauptquelldatei oder in einer vom Programm verwendeten Bibliotheksdatei angegeben werden. Diese Technik ist praktisch, weil die Benutzer die Werte nicht selbst eingeben müssen, aber sie ist auch unflexibel. Um Parameter zu ändern, musst du dein Programm modifizieren. Außerdem ist sie unsicher, weil jeder, der auf die Bibliothek zugreift, deine Datenbankzugangsdaten lesen kann.

Frag interaktiv nach den Parametern

In einer Befehlszeilenumgebung kannst du dem Benutzer eine Reihe von Fragen stellen. In einer Web- oder GUI-Umgebung kannst du dies über ein Formular oder einen Dialog tun. In beiden Fällen wird es für Personen, die die Anwendung häufig nutzen, mühsam, weil sie die Parameter jedes Mal neu eingeben müssen.

Hol dir die Parameter aus der Befehlszeile

Du kannst diese Methode entweder für Befehle verwenden, die interaktiv oder innerhalb eines Skripts ausgeführt werden. Wie bei der interaktiven Methode musst du auch hier bei jedem Befehlsaufruf Parameter angeben. (Ein Faktor, der diesen Aufwand mindert, ist, dass viele Shells es dir ermöglichen, Befehle aus deiner Verlaufsliste abzurufen und erneut auszuführen.) Diese Methode könnte unsicher sein, wenn du deine Anmeldedaten auf diese Weise angibst.

Abrufen der Parameter aus der Ausführungsumgebung

Die gängigste Methode, dies zu tun, ist, die entsprechenden Umgebungsvariablen in einer der Startdateien deiner Shell zu setzen (z. B. .profile für sh, bash oder ksh; oder .login für csh oder tcsh). Programme, die du während deiner Anmeldesitzung ausführst, können dann Parameterwerte erhalten, indem sie ihre Umgebung untersuchen.

Hol die Parameter aus einer separaten Datei

Mit dieser Methode speicherst du Informationen wie den Benutzernamen und das Passwort in einer Datei, die Programme lesen können, bevor sie sich mit dem MySQL-Server verbinden. Das Lesen von Parametern aus einer Datei, die von deinem Programm getrennt ist, hat den Vorteil, dass du sie nicht jedes Mal eingeben musst, wenn du das Programm benutzt, ohne die Werte fest zu verdrahten. Wenn du die Werte in einer Datei speicherst, kannst du die Parameter für mehrere Programme zentralisieren und aus Sicherheitsgründen den Zugriffsmodus der Datei so einstellen, dass andere Benutzer die Datei nicht lesen können.

Die MySQL-Client-Bibliothek selbst unterstützt einen Mechanismus für Optionsdateien, aber nicht alle APIs bieten Zugriff darauf. Für diejenigen, die das nicht tun, gibt es möglicherweise Umgehungslösungen. (Java unterstützt zum Beispiel die Verwendung von Eigenschaftsdateien und stellt Dienstprogramme zum Lesen dieser Dateien zur Verfügung).

Verwende eine Kombination von Methoden

Oft ist es sinnvoll, Methoden zu kombinieren, um den Nutzern die Flexibilität zu geben, Parameter auf verschiedene Arten zu übergeben. MySQL-Clients wie mysql und mysqladmin suchen zum Beispiel an verschiedenen Stellen nach Optionsdateien und lesen alle vorhandenen. Anschließend überprüfen sie die Befehlszeilenargumente auf weitere Parameter. So können die Benutzer die Verbindungsparameter in einer Optionsdatei oder auf der Befehlszeile angeben.

Diese Methoden zur Erlangung von Verbindungsparametern sind mit Sicherheitsproblemen verbunden:

  • Jede Methode, bei der Verbindungsparameter in einer Datei gespeichert werden, kann die Sicherheit deines Systems gefährden, wenn die Datei nicht vor dem Zugriff unbefugter Benutzer geschützt ist. Dies gilt unabhängig davon, ob die Parameter in einer Quelldatei, einer Optionsdatei oder in einem Skript gespeichert werden, das einen Befehl aufruft und die Parameter in der Befehlszeile angibt. (Webskripte, die nur vom Webserver gelesen werden können, gelten nicht als sicher, wenn andere Benutzer/innen administrativen Zugriff auf den Server haben).

  • Parameter, die in der Befehlszeile oder in Umgebungsvariablen angegeben werden, sind nicht besonders sicher. Während ein Programm ausgeführt wird, können seine Befehlszeilenargumente und seine Umgebung für andere Benutzer sichtbar sein, die Prozessstatusbefehle wie ps -e ausführen. Insbesondere das Speichern des Passworts in einer Umgebungsvariablen ist vielleicht am besten auf Situationen beschränkt, in denen du der einzige Benutzer auf dem Rechner bist oder allen anderen Benutzern vertraust.

Im restlichen Teil dieses Abschnitts wird erläutert, wie man Kommandozeilenargumente verarbeitet, um Verbindungsparameter zu erhalten, und wie man Parameter aus Optionsdateien liest.

Parameter von der Kommandozeile holen

Die Konvention, die von Standard-Clients wie mysql und mysqladmin für Befehlszeilenargumente verwendet wird, sieht vor, dass Parameter entweder mit einer kurzen oder einer langen Option angegeben werden können. Zum Beispiel kann der Benutzername cbuser entweder als -u cbuser (oder -ucbuser) oder --user=cbuser angegeben werden. Außerdem kann für eine der Passwortoptionen (-p oder --password) der Passwortwert nach dem Optionsnamen weggelassen werden, damit das Programm interaktiv nach dem Passwort fragt.

Die Standardflags für diese Befehlsoptionen sind -h oder --host, -u oder --user und -p oder --password. Du könntest deinen eigenen Code schreiben, um die Argumentliste zu durchlaufen, aber es ist viel einfacher, bestehende Module zur Verarbeitung von Optionen zu verwenden, die für diesen Zweck geschrieben wurden. Im api-Verzeichnis der recipes Distribution findest du Beispielprogramme, die dir zeigen, wie du die Befehlsargumente verarbeitest, um den Hostnamen, den Benutzernamen und das Passwort für Perl, Ruby, Python und Java zu erhalten. Eine beigefügte PDF-Datei erklärt, wie die einzelnen Programme funktionieren.

Hinweis

Soweit möglich, ahmen die Programme das Verhalten der Standard-MySQL-Clients bei der Optionsverarbeitung nach. Eine Ausnahme ist, dass die Bibliotheken, die Optionen verarbeiten, nicht zulassen, dass der Passwortwert optional ist, und dass sie keine Möglichkeit bieten, den Benutzer interaktiv zur Eingabe eines Passworts aufzufordern, wenn eine Passwortoption ohne einen Passwortwert angegeben wird. Daher sind die Programme so geschrieben, dass du bei Verwendung von -p oder --password den Passwortwert nach der Option angeben musst.

Parameter aus Optionsdateien abrufen

Wenn deine API dies unterstützt, kannst du die Verbindungsparameter in einer MySQL-Optionsdateiangeben und die API die Parameter für dich aus der Datei lesen lassen. Für APIs, die Optionsdateien nicht direkt unterstützen, kannst du vielleicht andere Dateitypen verwenden, in denen Parameter gespeichert sind, oder deine eigenen Funktionen schreiben, die Optionsdateien lesen.

Rezept 1.4 beschreibt das Format von MySQL-Optionsdateien. Wir gehen davon aus, dass du die Diskussion dort gelesen hast und konzentrieren uns hier darauf, wie du Optionsdateien aus Programmen heraus verwenden kannst. Die Dateien mit dem hier besprochenen Code findest du im Verzeichnisapi der recipes Distribution.

Unter Unix werden benutzerspezifische Optionen standardmäßig in ~/.my.cnf (d.h. in der Datei .my.cnf in deinem Home-Verzeichnis) angegeben. Der MySQL-Optionsdatei-Mechanismus kann jedoch in verschiedenen Dateien nachsehen, wenn sie existieren, obwohl keine Optionsdatei vorhanden sein muss. (Eine Liste der Standardorte, an denen MySQL-Programme nach ihnen suchen, findest du in Rezept 1.4). Wenn es mehrere Optionsdateien gibt und ein bestimmter Parameter in mehreren von ihnen angegeben ist, hat der zuletzt gefundene Wert Vorrang.

Programme, die du schreibst, verwenden keine MySQL-Optionsdateien, es sei denn, du sagst ihnen das:

  • Perl DBI und Ruby Mysql2 gem bieten direkte API-Unterstützung für das Lesen von Optionsdateien; gib einfach an, dass du sie verwenden möchtest, wenn du dich mit dem Server verbindest. Du kannst angeben, dass nur eine bestimmte Datei gelesen werden soll, oder dass die Standardsuchreihenfolge verwendet werden soll, um nach mehreren Optionsdateien zu suchen.

  • PHP PDO, Connector/Python, Java und Go unterstützen keine Optionsdateien. (Der PDO-MySQL-Treiber schon, aber nicht, wenn du mysqlnd als zugrunde liegende Bibliothek verwendest.) Als Workaround für PHP werden wir eine einfache Funktion zum Parsen von Optionsdateien schreiben. Für Java werden wir einen anderen Ansatz wählen, der Eigenschaftsdateien verwendet. Für Go werden wir die INIParsing-Bibliothek verwenden.

Obwohl der konventionelle Name unter Unix für die benutzerspezifische Optionsdatei .my.cnf im Home-Verzeichnis des aktuellen Benutzers lautet, gibt es keine Vorschrift, die besagt, dass deine eigenen Programme diese spezielle Datei verwenden müssen. Du kannst eine Optionsdatei benennen, wie du willst, und sie dort ablegen, wo du willst. Du könntest zum Beispiel eine Datei namens mcb.cnf erstellen und sie im Verzeichnis /usr/local/lib/mcbinstallieren, damit sie von Skripten verwendet werden kann, die auf die Datenbank cookbook zugreifen. Unter Umständen möchtest du sogar mehrere Optionsdateien erstellen. Dann wählst du in jedem Skript die Datei aus, die den Zugriffsrechten entspricht, die das Skript benötigt. Du könntest zum Beispiel eine Optionsdatei, mcb.cnf, haben, die Parameter für einen MySQL-Account mit Vollzugriff auflistet, und eine andere Datei, mcb-readonly.cnf, die Verbindungsparameter für einen Account auflistet, der nur Lesezugriff auf MySQL benötigt. Eine andere Möglichkeit ist es, mehrere Gruppen in derselben Optionsdatei aufzulisten und deine Skripte Optionen aus der entsprechenden Gruppe auswählen zu lassen.

Perl

Perl DBI-Skripte können Optionsdateien verwenden. Um dies zu nutzen, fügst du die entsprechenden Optionsspezifikationen in die dritte Komponente des DSN-Strings (Data Source Name) ein:

  • Um eine Optionsgruppe anzugeben, verwenden Sie mysql_read_default_group=groupnameDamit wird MySQL angewiesen, in den Standardoptionsdateien nach Optionen in der benannten Gruppe und in der Gruppe [client] zu suchen. Schreibe den groupname Wert ohne die umgebenden eckigen Klammern. (Wenn eine Gruppe in einer Optionsdatei mit einer [my_prog] Zeile beginnt, gibst du den groupname Wert als my_prog an.) Um die Standarddateien zu durchsuchen, aber nur in der Gruppe [client] zu suchen, groupname sollte client lauten.

  • Um eine bestimmte Optionsdatei zu benennen, verwende mysql_read_default_file=filename im DSN. Wenn du dies tust, sucht MySQL nur in dieser Datei und nur nach Optionen in der Gruppe [client].

  • Wenn du sowohl eine Optionsdatei als auch eine Optionsgruppe angibst, liest MySQL nur die benannte Datei, sucht aber nach Optionen sowohl in der benannten Gruppe als auch in der Gruppe [client].

Das folgende Beispiel weist MySQL an, die Standard-Suchreihenfolge der Optionsdateien zu verwenden, um nach Optionen in den Gruppen [cookbook] und [client] zu suchen:

my $conn_attrs = {PrintError => 0, RaiseError => 1, AutoCommit => 1};
# basic DSN
my $dsn = "DBI:mysql:database=cookbook";
# look in standard option files; use [cookbook] and [client] groups
$dsn .= ";mysql_read_default_group=cookbook";
my $dbh = DBI->connect ($dsn, undef, undef, $conn_attrs);

Das nächste Beispiel nennt explizit die Optionsdatei, die sich im $ENV{HOME} befindet, dem Heimatverzeichnis des Benutzers, der das Skript ausführt. So sieht MySQL nur in dieser Datei nach und verwendet Optionen aus der Gruppe [client]:

my $conn_attrs = {PrintError => 0, RaiseError => 1, AutoCommit => 1};
# basic DSN
my $dsn = "DBI:mysql:database=cookbook";
# look in user-specific option file owned by the current user
$dsn .= ";mysql_read_default_file=$ENV{HOME}/.my.cnf";
my $dbh = DBI->connect ($dsn, undef, undef, $conn_attrs);

Wenn du einen leeren Wert (undef oder die leere Zeichenkette) für die Argumente Benutzername oder Passwort des Aufrufs connect() übergibst, verwendet connect() die Werte, die in der Optionsdatei oder den Optionsdateien gefunden wurden. Ein nicht leerer Benutzername oder ein nicht leeres Passwort im connect() Aufruf überschreibt jeden Wert in der Optionsdatei. Ebenso setzt ein im DSN genannter Host jeden Wert in der Optionsdatei außer Kraft. Mit diesem Verhalten können DBI-Skripte Verbindungsparameter sowohl aus Optionsdateien als auch über die Befehlszeile abrufen:

  1. Erstelle die Variablen $host_name, $user_name und $password, jeweils mit dem Wert undef. Dann analysiere die Befehlszeilenargumente, um die Variablen auf Nicht-undef -Werte zu setzen, wenn die entsprechenden Optionen in der Befehlszeile vorhanden sind. (Das cmdline.pl Perl-Skript im api-Verzeichnis der recipes Distribution zeigt, wie das geht).

  2. Nach dem Parsen der Befehlsargumente erstellst du die DSN-Zeichenkette und rufst connect() auf. mysql_read_default_groupund mysql_read_default_fileim DSN geben an, wie die Optionsdateien verwendet werden sollen, und wenn $host_name nicht undef ist, fügst du host=$host_name zum DSN hinzu. Außerdem übergibst du $user_name und $password als Benutzername und Passwort an connect(). Diese sind standardmäßig undef; wenn sie über die Befehlszeilenargumente gesetzt wurden, haben sie nichtundef Werte, die alle Werte der Optionsdateien außer Kraft setzen.

Wenn ein Skript diesem Verfahren folgt, werden die vom Benutzer in der Kommandozeile angegebenen Parameter an connect() übergeben und haben Vorrang vor dem Inhalt der Optionsdateien.

Ruby

Ruby Mysql2-Skripte können Optionsdateien lesen, die mit dem Verbindungsparameter default_file angegeben werden. Wenn du die Standardgruppe angeben willst, verwende die Option default_group.

In diesem Beispiel wird die Standard-Suchreihenfolge der Optionsdateien verwendet, um nach Optionen in den Gruppen [cookbook] und [client] zu suchen:

client = Mysql2::Client.new(:default_group => "cookbook", :database => "cookbook")

Das folgende Beispiel verwendet die Datei .my.cnf im Home-Verzeichnis des aktuellen Benutzers, um Parameter von der Gruppe [client] zu erhalten:

client = Mysql2::Client.new(:default_file => "#{ENV['HOME']}/.my.cnf",
:database => "cookbook")

PHP

Wie bereits erwähnt, unterstützt der PDO-MySQL-Treibernicht unbedingt die Verwendung von MySQL-Optionsdateien (nicht, wenn du mysqlnd als zugrunde liegende Bibliothek verwendest). Um diese Einschränkung zu umgehen, kannst du eine Funktion verwenden, die eine Optionsdatei liest, wie zum Beispiel die Funktion read_mysql_option_file() im folgenden Listing. Sie nimmt als Argumente den Namen einer Optionsdatei und einen Optionsgruppennamen oder ein Array mit Gruppennamen entgegen. (Gruppennamen sollten ohne eckige Klammern geschrieben werden.) Sie liest dann alle Optionen, die in der Datei für die benannte(n) Gruppe(n) vorhanden sind. Wenn kein Optionsgruppenargument angegeben wird, sucht die Funktion standardmäßig in der Gruppe [client]. Der Rückgabewert ist ein Array mit Name/Wert-Paaren der Optionen oder FALSE, wenn ein Fehler auftritt. Es ist kein Fehler, wenn die Datei nicht existiert. (Beachten Sie, dass in Anführungszeichen gesetzte Optionswerte und abschließende Kommentare im Stil von # nach Optionswerten in MySQL-Optionsdateien zulässig sind, diese Funktion diese Konstrukte aber nicht verarbeitet):

function read_mysql_option_file ($filename, $group_list = "client")
{
  if (is_string ($group_list))           # convert string to array
    $group_list = array ($group_list);
  if (!is_array ($group_list))           # hmm ... garbage argument?
    return (FALSE);
  $opt = array ();                       # option name/value array
  if (!@($fp = fopen ($filename, "r")))  # if file does not exist,
    return ($opt);                       # return an empty list
  $in_named_group = 0;  # set nonzero while processing a named group
  while ($s = fgets ($fp, 1024))
  {
    $s = trim ($s);
    if (preg_match ("/^[#;]/", $s))              # skip comments
      continue;
    if (preg_match ("/^\[([^]]+)]/", $s, $arg))  # option group line
    {
      # check whether we are in one of the desired groups
      $in_named_group = 0;
      foreach ($group_list as $group_name)
      {
        if ($arg[1] == $group_name)
        {
          $in_named_group = 1;    # we are in a desired group
          break;
        }
      }
      continue;
    }
    if (!$in_named_group)         # we are not in a desired
      continue;                   # group, skip the line
    if (preg_match ("/^([^ \t=]+)[ \t]*=[ \t]*(.*)/", $s, $arg))
      $opt[$arg[1]] = $arg[2];    # name=value
    else if (preg_match ("/^([^ \t]+)/", $s, $arg))
      $opt[$arg[1]] = "";         # name only
    # else line is malformed
  }
  return ($opt);
}

Hier sind zwei Beispiele, die zeigen, wie man read_mysql_option_file() verwendet. Das erste Beispiel liest die Optionsdatei eines Benutzers, um die Parameter der Gruppe [client] zu erhalten, und verwendet diese, um sich mit dem Server zu verbinden. Das zweite Beispiel liest die systemweite Optionsdatei /etc/my.cnf und gibt die dort gefundenen Startparameter des Servers aus (d.h. die Parameter der Gruppen [mysqld] und [server] ):

$opt = read_mysql_option_file ("/home/paul/.my.cnf");
$dsn = "mysql:dbname=cookbook";
if (isset ($opt["host"]))
  $dsn .= ";host=" . $opt["host"];
$user = $opt["user"];
$password = $opt["password"];
try
{
  $dbh = new PDO ($dsn, $user, $password);
  print ("Connected\n");
  $dbh = NULL;
  print ("Disconnected\n");
}
catch (PDOException $e)
{
  print ("Cannot connect to server\n");
}

$opt = read_mysql_option_file ("/etc/my.cnf", array ("mysqld", "server"));
foreach ($opt as $name => $value)
  print ("$name => $value\n");

PHP verfügt über eine Funktion parse_ini_file(), die für das Parsen von .ini-Dateien gedacht ist. Die Syntax dieser Dateien ähnelt der von MySQL-Optionsdateien, sodass diese Funktion für dich nützlich sein könnte. Allerdings gibt es einige Unterschiede, auf die du achten solltest. Angenommen, du hast eine Datei, die wie folgt geschrieben ist:

[client]
user=paul

[client]
host=127.0.0.1

[mysql]
no-auto-rehash

Die Standardanalyse von MySQL-Optionen betrachtet die Werte user und host als Teil der Gruppe [client], während parse_ini_file() nur den Inhalt der letzten Stanza [client] zurückgibt; die Option user geht verloren. Außerdem ignoriert parse_ini_file() Optionen, die ohne einen Wert angegeben werden, so dass die Option no-auto-rehash verloren geht.

Geh

Der Go-MySQL-Treiber unterstützt keine Optionsdateien. Die INI-Parsing-Bibliothek unterstützt jedoch das Lesen von Eigenschaftsdateien, die Zeilen in diesem Format enthalten. name=value Format enthalten. Hier ist ein Beispiel für eine Eigenschaftsdatei:

# this file lists parameters for connecting to the MySQL server
[client]
user=cbuser
password=cbpass
host=localhost

Die Funktion MyCnf() zeigt eine Möglichkeit, eine Eigenschaftsdatei namens ~/.my.cnf zu lesen, um Verbindungsparameter zu erhalten:

import (
        "fmt"
        "os"
        "gopkg.in/ini.v1"
)

// Configuration Parser
func MyCnf(client string) (string, error) {
    cfg, err := ini.LoadSources(ini.LoadOptions{AllowBooleanKeys: true}, 
                                os.Getenv("HOME")+"/.my.cnf")
    if err != nil {
        return "", err
    }
    for _, s := range cfg.Sections() {
        if client != "" && s.Name() != client {
            continue
        }
        host := s.Key("host").String()
        port := s.Key("port").String()
        dbname := s.Key("dbname").String()
        user := s.Key("user").String()
        password := s.Key("password").String()
        return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", user, password, host, port, dbname),
               nil
    }
    return "", fmt.Errorf("No matching entry found in ~/.my.cnf")
}

Die Funktion MyCnf() wurde in der Datei cookbook.go definiert, die an anderer Stelle in diesem Kapitel entwickelt wurde (siehe Rezept 4.3). Sie wird in der Datei mycnf.go verwendet, die du im Verzeichnis api/06_conn_params in der recipes Distribution findest:

// mycnf.go : Reads ~/.my.cnf file for DSN construct
package main

import (
	"fmt"
	"github.com/svetasmirnova/mysqlcookbook/recipes/lib"
)

func main() {
    fmt.Println("Calling db.MyCnf()")
    var dsn string

    dsn, err := cookbook.MyCnf("client")
    if err != nil {
	  fmt.Printf("error: %v\n", err)
    } else {
	  fmt.Printf("DSN is: %s\n", dsn)
    }
}

Die Funktion MyCnf() nimmt den Namen des Abschnitts als Parameter entgegen. Wenn du den Abschnitt [client] durch einen anderen Namen ersetzen willst, ändere MyCnf() in MyCnf("other"), wobei other der Name des Abschnitts ist.

Java

Der JDBC-Treiber MySQL Connector/J unterstützt keine Optionsdateien. Die Java-Klassenbibliothekunterstützt jedoch das Lesen von Eigenschaftsdateien, die Zeilen in diesem Format enthalten. name=value Format enthalten. Dieses Format ist ähnlich, aber nicht identisch mit dem Format der MySQL-Optionsdateien (Properties-Dateien erlauben zum Beispiel keine [groupname] Zeilen). Hier ist eine einfache Eigenschaftsdatei:

# this file lists parameters for connecting to the MySQL server
user=cbuser
password=cbpass
host=localhost

Das folgende Programm, ReadPropsFile.java, zeigt eine Möglichkeit, eine Eigenschaftsdatei namens Cookbook.properties zu lesen, um Verbindungsparameter zu erhalten. Die Datei muss sich in einem Verzeichnis befinden, das in der Variable CLASSPATH angegeben ist, oder du musst den vollständigen Pfadnamen angeben (das hier gezeigte Beispiel geht davon aus, dass sich die Datei in einem CLASSPATH Verzeichnis befindet):

import java.sql.*;
import java.util.*;   // need this for properties file support

public class ReadPropsFile {
  public static void main(String[] args) {
    Connection conn = null;
    String url = null;
    String propsFile = "Cookbook.properties";
    Properties props = new Properties();

    try {
      props.load(ReadPropsFile.class.getResourceAsStream(propsFile));
    } catch (Exception e) {
      System.err.println("Cannot read properties file");
      System.exit (1);
    }
    try {
      // construct connection URL, encoding username
      // and password as parameters at the end
      url = "jdbc:mysql://"
            + props.getProperty("host")
            + "/cookbook"
            + "?user=" + props.getProperty("user")
            + "&password=" + props.getProperty("password");
      conn = DriverManager.getConnection(url);
      System.out.println("Connected");
    } catch (Exception e) {
      System.err.println("Cannot connect to server");
    } finally {
      try {
        if (conn != null) {
          conn.close();
          System.out.println("Disconnected");
        }
      } catch (SQLException e) { /* ignore close errors */ }
    }
  }
}

Wenn getProperty()einen bestimmten Standardwert zurückgeben soll, wenn die benannte Eigenschaft nicht gefunden wird, gibst du diesen Wert als zweites Argument an. Um zum Beispiel 127.0.0.1 als Standardwert für host zu verwenden, rufe getProperty() wie folgt auf:

String hostName = props.getProperty("host", "127.0.0.1");

Die Bibliotheksdatei Cookbook.java, die an anderer Stelle in diesem Kapitel entwickelt wurde (siehe Rezept 4.3), enthält einen zusätzlichen Bibliotheksaufruf in der Version der Datei, die du im lib-Verzeichnis der recipes Distribution findest: eine propsConnect() Routine, die auf den hier besprochenen Konzepten basiert. Um sie zu verwenden, richte den Inhalt der Eigenschaftsdatei Cookbook.properties ein und kopiere die Datei an denselben Ort, an dem du Cookbook.class installiert hast. Du kannst dann innerhalb eines Programms eine Verbindung herstellen, indem du die Klasse Cookbook importierst und Cookbook.propsConnect() aufrufst, anstatt Cookbook.connect() aufzurufen.

4.9 Zurücksetzen der Profiltabelle

Problem

Während der Arbeit an den Beispielen in diesem Kapitel hast du den ursprünglichen Inhalt der Tabelle profile geändert und möchtest ihn nun wiederhaben, damit du ihn bei der Arbeit mit anderen Rezepten verwenden kannst.

Lösung

Lade die Tabelle mit dem mysql Client neu.

Diskussion

Es ist eine gute Idee, die in diesem Kapitel verwendete Tabelle profile auf einen bekannten Zustand zurückzusetzen. Wechsle in das Tabellenverzeichnis der recipes Distribution und führe diese Befehle aus:

$ mysql cookbook < profile.sql
$ mysql cookbook < profile2.sql

Mehrere Anweisungen in späteren Kapiteln verwenden die Tabelle profile; wenn du sie neu initialisierst, erhältst du die gleichen Ergebnisse wie in diesen Kapiteln, wenn du die dort gezeigten Anweisungen ausführst.

In diesem Kapitel haben wir die grundlegenden Operationen besprochen, die jede unserer APIs für verschiedene Aspekte der Interaktion mit dem MySQL-Server bereitstellt. Mit diesen Operationen kannst du Programme schreiben, die jede Art von Anweisung ausführen und die Ergebnisse abrufen. Bis zu diesem Punkt haben wir einfache Anweisungen verwendet, weil der Schwerpunkt auf den APIs und nicht auf SQL liegt. Das nächste Kapitel konzentriert sich stattdessen auf SQL, um zu zeigen, wie du dem Datenbankserver komplexere Fragen stellen kannst.

Get MySQL Kochbuch, 4. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.