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).
Sprache | Schnittstelle |
---|---|
Perl | Perl DBI |
Ruby | Mysql2 gem |
PHP | PDO |
Python | DB-API |
Geh | Go sql |
Java | JDBC |
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 oderNULL
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
undcbpass
. Wir verbinden uns mit einem MySQL-Server, der auf dem lokalen Host läuft, um auf eine Datenbank namenscookbook
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 vonSHOW
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
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"
;
"Connected\n"
;
$dbh
->
disconnect
();
"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 perldoc
warnings
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:
Der zweite Doppelpunkt im DSN ist auch dann erforderlich, wenn du keine weiteren Optionen angibst.options
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 Optionenhost
unddatabase
, 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 PrintError
und 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 port
an:
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, -w
zu 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 port
an:
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
(
"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"
);
(
"Connected
\n
"
);
}
catch
(
PDOException
$e
)
{
die
(
"Cannot connect to server
\n
"
);
}
$dbh
=
NULL
;
(
"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 dbname
weg.
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_socket
an:
$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"
)
(
"Connected"
)
except
:
(
"Cannot connect to server"
)
else
:
conn
.
close
()
(
"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 try
Anweisung), 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 try
Klausel 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 database
weg 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
)
(
"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_socket
an:
conn_params
=
{
"database"
:
"cookbook"
,
"unix_socket"
:
"/var/tmp/mysql.sock"
,
"user"
:
"cbuser"
,
"password"
:
"cbpass"
,
}
conn
=
mysql
.
connector
.
connect
(
**
conn_params
)
(
"Connected"
)
Um die Portnummer für TCP/IP-Verbindungen anzugeben, füge den Parameter host
hinzu 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 defer
Anweisung, 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 unix
an:
// 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 tcp
in 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&..¶mN=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_HOME
auf 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_name
auslä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
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:
PrintError
Wenn diese Option aktiviert ist, gibt DBI eine Fehlermeldung unterwarn()
aus.RaiseError
falls aktiviert, veranlasst DBI, eine Fehlermeldung mitdie()
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 AutoCommit
und 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::err
0 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 PrintError
und 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
"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 eval
Blocks 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
(
$@
)
{
"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ährendRaiseError
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
);
(
"Connected
\n
"
);
}
catch
(
PDOException
$e
)
{
(
"Cannot connect to server
\n
"
);
(
"Error code: "
.
$e
->
getCode
()
.
"
\n
"
);
(
"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
)
{
(
"Cannot execute query
\n
"
);
(
"Error information using exception object:
\n
"
);
(
"SQLSTATE value: "
.
$e
->
getCode
()
.
"
\n
"
);
(
"Error message: "
.
$e
->
getMessage
()
.
"
\n
"
);
(
"Error information using database handle:
\n
"
);
(
"Error code: "
.
$dbh
->
errorCode
()
.
"
\n
"
);
$errorInfo
=
$dbh
->
errorInfo
();
(
"SQLSTATE value: "
.
$errorInfo
[
0
]
.
"
\n
"
);
(
"Error number: "
.
$errorInfo
[
1
]
.
"
\n
"
);
(
"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
)
(
"Connected"
)
except
mysql
.
connector
.
Error
as
e
:
(
"Cannot connect to server"
)
(
"Error code:
%s
"
%
e
.
errno
)
(
"Error message:
%s
"
%
e
.
msg
)
(
"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.
Funktion oder Anweisung | Bedeutung |
---|---|
defer | Verschiebt 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
.
(
"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 MethodegetMessage()
. JDBC-Methoden können Ausnahmen mit Hilfe vonSQLException
-Objekten auslösen; diese sind wieException
-Objekte, unterstützen aber auch die MethodengetErrorCode()
undgetSQLState()
.getErrorCode()
undgetMessage()
geben die MySQL-spezifische Fehlernummer und den Meldungsstring zurück, undgetSQLState()
gibt einen String zurück, der den SQLSTATE-Wert enthält.Einige Methoden erzeugen
SQLWarning
Objekte, um Informationen über nicht tödliche Warnungen bereitzustellen.SQLWarning
ist eine Unterklasse vonSQLException
, 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
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.
Sprache | Name der Variable | Variable Art |
---|---|---|
Perl | PERL5LIB | Umgebungsvariable |
Ruby | RUBYLIB | Umgebungsvariable |
PHP | include_path | Konfigurationsvariable |
Python | PYTHONPATH | Umgebungsvariable |
Geh | GOPATH | Umgebungsvariable |
Java | CLASSPATH | 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. alswwwusr
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 PERL5LIB
setzt, 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
();
"Connected\n"
;
};
die
"$@"
if
$@
;
$dbh
->
disconnect
();
"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
();
"Connected\n"
;
$dbh
->
disconnect
();
"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.connect
definiert, 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 RUBYLIB
setzt, 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
"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
()
"Disconnected
\n
"
end
harness.rb
hat keine require
Anweisung für das Modul Mysql2. Das ist unnötig, weil das Modul Cookbook
selbst 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
"Connected
\n
"
client
.
close
"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
();
(
"Connected
\n
"
);
}
catch
(
PDOException
$e
)
{
(
"Cannot connect to server
\n
"
);
(
"Error code: "
.
$e
->
getCode
()
.
"
\n
"
);
(
"Error message: "
.
$e
->
getMessage
()
.
"
\n
"
);
exit
(
1
);
}
$dbh
=
NULL
;
(
"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
undinclude
weisen PHP an, die genannte Datei zu lesen. Sie sind ähnlich, aberrequire
bricht das Skript ab, wenn die Datei nicht gefunden wird;include
gibt nur eine Warnung aus.require_once
undinclude_once
sind wierequire
undinclude
, 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 PYTHONPATH
setzen, 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
()
(
"Connected"
)
except
mysql
.
connector
.
Error
as
e
:
(
"Cannot connect to server"
)
(
"Error code:
%s
"
%
e
.
errno
)
(
"Error message:
%s
"
%
e
.
msg
)
else
:
conn
.
close
()
(
"Disconnected"
)
Die Datei cookbook.py importiert das Modul mysql.connector
, aber ein Skript, das cookbook
importiert, 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
()
(
"Connected"
)
conn
.
close
()
(
"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:
4.4 Ausführen von Anweisungen und Abrufen von Ergebnissen
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 name
sind 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 profile
nach 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
, oderUPDATE
Anweisungen, die keine Ergebnismenge zurückgeben, wie z.B.
INSERT
,DELETE
oderUPDATE
. In der Regel verändern Anweisungen dieser Art die Datenbank in irgendeiner Weise. Es gibt einige Ausnahmen, wie z. B.USE
db_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 istUPDATE
: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
, oderDESCRIBE
Anweisungen, die eine Ergebnismenge zurückgeben, wie
SELECT
,SHOW
,EXPLAIN
oderDESCRIBE
. Wir bezeichnen solche Anweisungen allgemein alsSELECT
Anweisungen, 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 eineSELECT
: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
;
"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:
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, wennRaiseError
aktiviert ist; andernfalls gibtprepare()
undef
zurück).Rufe
execute()
auf, um die Anweisungauszuführen und die Ergebnismenge zu erzeugen.Schleife, um die von der Anweisung zurückgegebenen Zeilen zu holen. DBI bietet dafür mehrere Methoden, die wir kurz erläutern.
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
())
{
"id: $val[0], name: $val[1], cats: $val[2]\n"
;
++
$count
;
}
$sth
->
finish
();
"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 SELECT
Anweisungen 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
())
{
"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
())
{
"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.
Methode | Rü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 undef
zweideutig, 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 setAttribute
database-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'"
)
(
"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
(
"id:
%s
, name:
%s
, cats:
%s
"
%
(
row
[
0
],
row
[
1
],
row
[
2
]))
(
"Number of rows returned:
%d
"
%
cursor
.
rowcount
)
cursor
.
close
()
Wie du im vorangegangenen Beispiel sehen kannst, ist das Attribut rowcount
auch für SELECT
Anweisungen 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
:
(
"id:
%s
, name:
%s
, cats:
%s
"
%
(
id
,
name
,
cats
))
(
"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
:
(
"id:
%s
, name:
%s
, cats:
%s
"
%
(
row
[
0
],
row
[
1
],
row
[
2
]))
(
"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 get
XXX
()
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 get
XXX
()
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 get
XXX
()
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
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 INSERT
Anweisung 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 name
selbst 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 NULL
setzen - 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
NULL
ohne 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 SELECT
Anweisung 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, dieNULL
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<
und>
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 SELECT
Anweisung 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
())
{
"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
:
(
"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 set
XXX
()
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 set
XXX
()
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 birth
als 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 set
XXX
()
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
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_QUOTES
aktiviert 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
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 NULL
Werte abbildet, oder welche Methode aufgerufen werden muss. Tabelle 4-5 zeigt diese Werte.
Sprache | Wert oder Methode zur NULL-Erkennung |
---|---|
Perl DBI | undef Wert |
Ruby Mysql2 gem | nil Wert |
PHP PDO | NULL Wert |
Python DB API | None Wert |
Go sql Schnittstelle | Go Null-Typ-Implementierung für die nullbaren Datentypen. |
Java JDBC | wasNull() 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 profile
eine 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 NULL
Werte 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 use
warnings
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"
;
}
(
"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"
(
"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.
Standard Go Typ | Typ, der NULL-Werte enthalten kann |
---|---|
bool | NullBool |
float64 | NullFloat64 |
int32 | NullInt32 |
int64 | NullInt64 |
string | NullString |
time.Time | NullTime |
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 get
XXX
()
Aufrufen ebenso.
Hier ist ein Beispiel, das jede Zeile einer Ergebnismenge als kommagetrennte Liste von Werten ausgibt, wobei "NULL"
für jeden NULL
Wert 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
.
(
", "
);
if
(
rs
.
wasNull
())
System
.
out
.
(
"NULL"
);
else
System
.
out
.
(
val
);
}
System
.
out
.
println
();
}
rs
.
close
();
// close result set
s
.
close
();
// close statement
4.8 Abrufen der Verbindungsparameter
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
oderksh
; oder .login fürcsh
odertcsh
). 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
undmysqladmin
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 dieINI
Parsing-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=
Damit wird MySQL angewiesen, in den Standardoptionsdateien nach Optionen in der benannten Gruppe und in der Gruppegroupname
[client]
zu suchen. Schreibe dengroupname
Wert ohne die umgebenden eckigen Klammern. (Wenn eine Gruppe in einer Optionsdatei mit einer[my_prog]
Zeile beginnt, gibst du dengroupname
Wert alsmy_prog
an.) Um die Standarddateien zu durchsuchen, aber nur in der Gruppe[client]
zu suchen,groupname
sollteclient
lauten.Um eine bestimmte Optionsdatei zu benennen, verwende
mysql_read_default_file=
im DSN. Wenn du dies tust, sucht MySQL nur in dieser Datei und nur nach Optionen in der Gruppefilename
[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:
Erstelle die Variablen
$host_name
,$user_name
und$password
, jeweils mit dem Wertundef
. Dann analysiere die Befehlszeilenargumente, um die Variablen auf Nicht-undef
-Werte zu setzen, wenn die entsprechenden Optionen in der Befehlszeile vorhanden sind. (Dascmdline.pl
Perl-Skript im api-Verzeichnis derrecipes
Distribution zeigt, wie das geht).Nach dem Parsen der Befehlsargumente erstellst du die DSN-Zeichenkette und rufst
connect()
auf.mysql_read_default_group
undmysql_read_default_file
im DSN geben an, wie die Optionsdateien verwendet werden sollen, und wenn$host_name
nichtundef
ist, fügst duhost=$host_name
zum DSN hinzu. Außerdem übergibst du$user_name
und$password
als Benutzername und Passwort anconnect()
. Diese sind standardmäßigundef
; 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
);
(
"Connected
\n
"
);
$dbh
=
NULL
;
(
"Disconnected
\n
"
);
}
catch
(
PDOException
$e
)
{
(
"Cannot connect to server
\n
"
);
}
$opt
=
read_mysql_option_file
(
"/etc/my.cnf"
,
array
(
"mysqld"
,
"server"
));
foreach
(
$opt
as
$name
=>
$value
)
(
"
$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 [
Zeilen). Hier ist eine einfache Eigenschaftsdatei:groupname
]
# 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
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.