Datenbank Applikationen
mit Oracle8i - Perl - DBI

Akadia AG, Information Technology, Zieglerstrasse 34, CH-3007 Bern
Phone: +41 33 345 02 40 / Fax: +41 33 345 02 42 / EMail: info@akadia.com


Einführung

Im SOUG-Newsletter 1/2000 haben wir in einem ersten Teil gezeigt, wie eine Web-Applikation mit Java Servlets auf eine Oracle-Datenbank zugreifen kann (Oracle 8i Database Access mit Java Servlets). In diesem Beitrag möchten wir aufzeigen, wie dies mittels Perl und dem Datenbank-Interface (DBI) durchgeführt werden kann. Öfters werden wir gefragt, welche Tools mit Vorteil zu verwenden sind, um eine Web-Datenbank-Applikation zu realisieren. Wir betrachten es als sinnvoll, nicht über den Einsatz eines Tool, sondern einer Technologie zu sprechen. «Point and Click» Tools sind meist schnell erlernbar, aber in der Regel kurzlebig, Technologien verhalten sich meistens umgekehrt. Perl und DBI sind Technologien wie Java und JServ, sie bilden solide Fundamente, um damit eine Applikation zu entwickeln. Der Oracle Applikation Server (OAS) ist eine gute Technologie-Plattform, setzt doch die OAS Perl Cartridge ebenfalls auf Perl / DBI auf. Für kleinere und mittlere Datenbank-Applikationen ist der OAS jedoch zu mächtig und zu umfangreich, hier ist eine smarte Perl / DBI-Applikation wohl das Geeignete.

Perl / DBI

Aus der Tatsache, dass es heute kein gemeinsames API (Application Programming Interface) unter den verschiedenen Datenbank-Produkten gibt, entwickelte sich DBI zum Quasi-Standard, um eine Web-Applikation mit einer oder mehreren beliebigen Datenbanken zu koppeln. Praktisch jeder Datenbank-Hersteller offeriert eigene API's und Protokolle, im Falle von Oracle ist dies OCI und Net8 bzw. SQL*Net. Herkömmliche Client / Server-Applikationen setzen deshalb meist auf ODBC auf, während Java-Applikationen JDBC benutzen. DBI ist sehr eng mit Perl gekoppelt, in der Tat ist DBI nichts anderes als ein in Perl und C geschriebenes Perl-Modul. Perl ist nach wie vor die wohl am meist verbreitete Programmiersprache, um im Internet dynamisch HTML-Seiten zu generieren. Ausser dem Datenbank-Modul DBI existieren zu Perl eine Vielzahl weiterer Module für praktisch jede Aufgabenstellung. Perl und seine Module sind für nahezu alle Hardware-Plattformen vorhanden, insbesondere hat die Firma ActiveState eine sehr stabile Portierung von Perl und den meisten Module für Windows NT vorgenommen. Man kann sich natürlich fragen, weshalb man als Oracle-Entwickler Perl lernen soll, wenn es doch das äusserst mächtige PL/SQL gibt. PL/SQL ist aber wenig geeignet, um mit dem Betriebssystem und einem HTTP-Server direkt zu kommunizieren, genau dies ist jedoch die Domäne von Perl.

Architektur

DBI abstrahiert die proprietären Hersteller API's, es offeriert ein sehr einfach zu handhabendes Interface, um SQL-Statements abzusetzen und das zurückgegebene Result Set zu verarbeiten. DBI selbst kommuniziert nicht direkt mit dem eigentlichen Datenbank-API, es verwendet dazu immer einen entsprechenden Database Driver (DBD). DBD's sind native Libraries vom Hersteller, pro Datenbank existiert ein Driver, für Oracle ist dies das DBD::Oracle-Modul.

DBI Architektur

Der Entwickler benutzt ausschliesslich das wohldefinierte Datenbank-Interface, er braucht sich nicht um den richtigen Datenbank-Driver zu kümmern. Man kann DBI auch mit einem Dispatcher vergleichen, auf der einen Seite das einheitliche (Perl) API, auf der anderen Seite verifiziert DBI selbst, welcher Driver zu benutzen ist. Die folgende Abbildung zeigt den Datenfluss vom Programm (Perl Script) zur entsprechenden Datenbank via DBI und DBD. Der Oracle Driver DBD::Oracle kommuniziert mit der Oracle Datenbank über Net8 / SQL*Net, eine korrekt aufgesetzte SQL*Net Umgebung wird also vorausgesetzt. Es existieren heute Driver für Oracle, Informix, mSQL, MySQL, Ingres, Sybase, DB2, Empress, SearchServer, PostgreSQL, XBase und normale CSV Files.

DBI - DBDs

DBI definiert sogenannte Handles, welche man benutzt um mit der Datenbank zu kommunizieren. Driver Handles repräsentieren einen von DBI geladenen und initialisierten DB-Driver, Database Handles verkörpern eine aktive Datenbank-Verbindung (Connection) und Statement Handles kapseln individuelle SQL-Statements.

DBI Handles

Driver Handles

Driver Handles repräsentieren von DBI geladene und initialisierte DB-Driver. Pro Datenbank existiert genau ein Driver. Die Funktion
@drh = DBI->available_drivers; erstellt eine Liste der zur Verfügung stehenden DB-Driver. Da der DB-Driver die konkrete Datenbank vollständig kapselt, können gleichzeitig mehrere DB-Driver geladen und benutzt werden. Damit ist es beispielsweise möglich, Daten aus einer Access-Datenbank (DBD::ODBC) in eine Oracle-Datenbank (DBD::Oracle) zu transferieren. Die DBI-Spezifikation kennzeichnet den Driver Handle in der Regel mit $drh.

Database Handles

Database Handles verkörpern den ersten Schritt, um eine Verbindung mit einer bestimmten Datenbank herzustellen, sie kapseln einen Connection Handle. Bevor mit einer Datenbank kommuniziert werden kann, muss die Funktion $dbh = DBI->connect($data_source, $username, $auth, \%attr); aufgerufen werden. Zwischen Driver Handles und Database Handles besteht eine hierarchische Anordnung, wobei die letzteren «Kinder» der ersteren sind. Die DBI-Spezifikation kennzeichnet den Database Handle in der Regel mit $dbh.

Statement Handles

Statement Handles referenzieren einzelne SQL-Statements, beispielsweise $sth = $dbh->prepare( "SELECT * FROM emp"). Zwischen Database Handles und Statement Handles besteht ebenfalls eine Hierarchie, wie dies die obige Abbildung verdeutlicht. Die Anzahl möglicher Statement Handles pro Database Handle ist nicht limitiert. Die DBI-Spezifikation kennzeichnet den Statement Handle in der Regel mit $sth.


Beispiele

Einfaches Query

In diesem einführenden Beispiel werden die einzelnen Schritte gezeigt. Zuerst wird das DBI-Modul mit der Anweisung use DBI; geladen, anschliessend werden die DB-Connection-Parameter für die Oracle8i Datenbank zusammengestellt. Das Error Handling erfolgt mit der Perl-Anweisung || (wenn vorangehendes Statement nicht erfolgreich war, so gebe eine Fehlermeldung aus und beende das Programm). Nach erfolgreicher DB-Connection wird das SQL-Statement zusammengestellt und in einem LOOP werden die Daten aus der Datenbank selektiert und ausgegeben. Die Perl-Syntax mag im ersten Augenblick etwas kryptisch erscheinen, nach kurzer Zeit hat man sich aber an diese sehr effiziente Schreibweise gewöhnt.

#!/usr/bin/perl

# Load DBI Module
use DBI;

# Setup Connection to Oracle8i
my $user = 'scott';
my $password = 'tiger';
my $data_source = 'dbi:Oracle:RAB1';
my $empno = 7700;

my $dbh = DBI->connect($data_source, $user, $password)
  || die "Can't connect to $data_source: $DBI::errstr";

# Prepare Statement Handle
my $sth = $dbh->prepare(q{
      SELECT empno, ename
      FROM emp
     WHERE empno >= ?
}) || die "Can't prepare statement: $DBI::errstr";

# Execute and Fetch Statement
my $rc = $sth->execute($empno)
  || die "Can't execute statement: $DBI::errstr";

print "Query will return $sth->{NUM_OF_FIELDS} fields.\n\n";
print "Field names: @{ $sth->{NAME} }\n";

while (($empno, $ename) = $sth->fetchrow_array) {
  print "$empno: $ename\n";
}

# Check for problems which may have terminated the fetch early
die $sth->errstr if $sth->err;

# Close Statement Handle and disconnect
$sth->finish;
$dbh->disconnect;

Transaktionen

Im folgenden Beispiel wird eine Datei employee.csv, welche Mitarbeiter Daten enthält in die Datenbank eingelesen.

#!/usr/bin/perl

# Load DBI Module
use DBI;
use strict;

# Setup Connection to Oracle8i
my $user = 'scott';
my $password = 'tiger';
my $data_source = 'dbi:Oracle:RAB1';

my $dbh = DBI->connect($data_source, $user, $password)
  || die "Can't connect to $data_source: $DBI::errstr";

# Here's a complete example program to insert some data from a file
# This example uses RaiseError to avoid needing to check each call

my $dbh = DBI->connect($data_source, $user, $password, {
  RaiseError => 1, AutoCommit => 0
});

my $sth = $dbh->prepare( q{
  INSERT INTO emp (empno, ename) VALUES (?, ?)
});

open FH, "<employee.csv" or die "Unable to open employee.csv: $!";
while (<FH>) {
  chop;
  my ($empno, $ename) = split /,/;
  print ("$empno,$ename\n");
  $sth->execute($empno, $ename);
}
close FH;

$dbh->commit;
$dbh->disconnect;


Persistente Datenbank Verbindungen mit ModPerl und Apache::DBI

Perl / DBI-Applikationen werden oft als CGI-Script (Common Gateway Interface der HTTP-Server) zusammen mit einem HTTP-Server benutzt. Die CGI-Schnittstelle des HTTP-Servers hat im wesentlichen die Aufgabe, ein externes Programm zu starten und mit diesem über den Standard-Ausgabe- und Eingabe-Kanal zu kommunizieren. Das Starten des Perl-Interpreters als externes Programm und der Aufbau der Datenbank-Verbindung sind «teure» Operationen, die bei jedem HTTP-Request erneut ausgeführt werden. Dieser Nachteil der CGI-Schnittstelle kann mit dem Einsatz des HTTP-Servers Apache mit ModPerl und Apache::DBI eliminiert werden. ModPerl ist Built-In Perl Interpreter und Apache::DBI ist das Apache-Modul von DBI. Mit ModPerl entfällt der Aufruf des externen Perl-Interpreters und Apache::DBI ermöglicht eine persistente Datenbank-Verbindung über einen vordefinierten Oracle Account. Normale Perl/DBI-Scripts können ohne nennenswerte Änderungen auch als ModPerl/Apache::DBI-Scripts verwendet werden.

ModPerl Architecture

Konfiguration von ModPerl / Apache::DBI

In der Regel ist Apache bereits mit ModPerl gelinkt. Wenn nicht, müssen die Apache und ModPerl Sourcen compiliert werden. Der folgende Abschnitt zeigt das Vorgehen unter RedHat Linux 6.0 / 6.1 mit Apache-1.3.9 und ModPerl-1.21. Die Download Sites haben wir unter «Benötigte Komponenten und Installation» zusammengestellt.

Installation

gunzip < apache_1.3.9.tar.gz | tar xvf -
gunzip < mod_perl-1.21.tar.gz | tar xvf -
cd mod_perl-1.21
perl Makefile.PL EVERYTHING=1 APACHE_PREFIX=/usr/local/apache
Configure mod_perl with ../apache_1.3.9/src ? [y] y
Shall I build httpd in ../apache_1.3.9/src for you? [y] y
make
make test
make install

Folgende Zeilen sind im Apache Konfigurationsfile «http.conf» einzutragen, damit ModPerl und Apache::DBI aktivert werden:

LoadModule perl_module modules/libperl.so
AddModule mod_perl.c


PerlModule Apache::Registry
PerlModule Apache::DBI

<Files *.perl>
  SetHandler perl-script
  PerlHandler Apache::Registry
  Options ExecCGI
  PerlSendHeader On
</Files>

Damit werden alle Perl Scripts mit der Extension «.perl» als ModPerl Script ausgeführt

Beispiel eines ModPerl Scripts

Das folgende Beispiel zeigt das für ModPerl umgeschriebene PerlScript «Einfaches Query».
Sie können das ModPerl Script testen indem Sie hier klicken.

#!/usr/bin/perl
# ModPerl Script using Apache::DBI
use strict;
use CGI qw(:standard);
# Disable Perl Buffering
$| = 1;
# Compile with warnings on
local $^W=1;
# Load Apache::DBI Module
$Apache::DBI::DEBUG = 2;
# Print HTML header and Headline
print header(), start_html("ModPerl Script using Apache::DBI");
# Setup Connection to Oracle8i
BEGIN {
  $ENV{ORACLE_HOME} = q(/prod/oracle/8.1.5);
  $ENV{TNS_ADMIN}   = q(/home/oracle/config/8.1.5);
  $ENV{NLS_LANG}    = q(AMERICAN_AMERICA.WE8ISO8859P1);
}
my $user = 'scott';
my $password = 'tiger';
my $data_source = 'dbi:Oracle:RAB1';
my $empno = 7700;
my $ename;
my $dbh = DBI->connect($data_source, $user, $password)
  || die "Can't connect to $data_source: $DBI::errstr";
# Prepare Statement Handle
my $sth = $dbh->prepare(q{
      SELECT empno, ename
      FROM emp
     WHERE empno >= ?
}) || die "Can't prepare statement: $DBI::errstr";
# Execute and Fetch Statement
my $rc = $sth->execute($empno)
  || die "Can't execute statement: $DBI::errstr";
print ("<pre>\n");
print ("Query will return $sth->{NUM_OF_FIELDS} fields.\n");
print ("Field names: @{$sth->{NAME}}\n\n");
while (($empno, $ename) = $sth->fetchrow_array) {
  print ("$empno: $ename\n");
}
print ("</pre>\n");
# Do not disconnect
print end_html();

Perl / DBI Script als «Ersatz» von SQL*Plus ?

Als abschliessendes Beispiel haben wir eine sehr einfache Variante von SQL*Plus mittels Perl / DBI realisiert. Nach dem Starten des Programms erscheint der Prompt: MYSQL>. Es können nun beliebige SQL-Statements eingegeben werden, welche vom Perl/DBI-Programm ausgeführt werden.

#!/usr/bin/perl

use DBI;
$dbname = 'RAB1'; $user = 'scott';
$password = 'tiger'; $dbd = 'Oracle';

$dbh = DBI->connect($dbname,$user,$password,$dbd) ||
       die "Error connecting $DBI::errstr\n";;

while(1) {
    print "MYSQL> "; # SQL-Prompt
    $stmt = <STDIN>;
    last unless defined($stmt);
    last if ($stmt =~ /^\s*exit/);
    chomp ($stmt);
    $stmt =~ s/;\s*$//;

    $sth = $dbh->prepare($stmt);
    if ($DBI::err) {
        print STDERR "$DBI::errstr\n";
        next;
    }
    $sth->execute() ;
    if ($DBI::err) {
        print STDERR "$DBI::errstr\n";
        next;
    }
    if ($stmt =~ /^\s*select/i) {
        my $rl_names = $sth->{NAME};          # Reference to List of Colum Names
        while (@results = $sth->fetchrow) {  # Retrieve results
            if ($DBI::err) {
                print STDERR $DBI::errstr,"\n";
                last;
            }
            foreach $field_name (@$rl_names) {
                printf "%10s: %s\n", $field_name, shift @results;
            }
            print "\n";
        }
        $sth->finish;
    }
}
$dbh->commit;


Zusammenfassung

Der Einsatz von Public Domain Software muss keine Gratwanderung sein, im Gegenteil. Die Software-Qualität von «Gratis-Software» wie dem legendären GNU-Compiler GCC oder dem am meisten eingesetzten HTTP-Server Apache oder des frei erhältlichen UNIX-Betriebssystems LINUX ist excellent. Zu dieser Kategorie gehören auch Perl / DBI und andere Software-Pakete. Die hohe Qualität der frei erhältlichen Software kommt dadurch zustande, dass viele Entwickler weltweit daran arbeiten und die Software im eigenen Nutzen einsetzen und veröffentlichen. Dadurch werden viele Fehler erst entdeckt und behoben, das Internet spielt dabei natürlich die entscheidende Rolle. Es geht uns in diesem Artikel aber keineswegs darum, kommerzielle Software als minderwertig zu deklarieren, sondern die beiden «Welten» nutzbringend einzusetzen. Wir hoffen, dass uns dies mit dem vorliegenden Artikel gelungen ist.