Advanced Java Services | Oracle Stored Procedures |
Bevor man Stored Procedures von Java aus verwenden will, muß man einigermaßen wissen, was eine
Stored Procedure in SQL ist. Eine Stored Procedure ist das was man in C/C++ eine Funktion nennt
oder in PASCAl eine Prozedur oder in Java eine Methode. In Java ist allerdings eine Methode an
ein Objekt oder eine Klasse gebunden. Eine Stored Procedure ist also eine Art SQL-Unterprogramm
und stellt eine SQL-Spracherweiterung dar, die nicht mehr standardisiert ist. Hier werden einige
Beispiele für Stored Procedures unter Oracle vorgestellt. Für andere Datenbanken gilt eine ähnlich
Syntax, aber eben nicht die gleiche. Oracle unterscheidet noch ein wenig altmodisch zwischen
procedure und function, eine Unterscheidung, die es ja in der C/C++/Java Familie nicht mehr gibt.
Eine Prozedur kann wie eine Funktion keine, einen oder mehrere Eingabeparameter besitzen . Diese
Parameter können reiner Eingabeparameter, reine Ausgabeparameter oder auch Ein- und Ausgabeparameter
sein und bilden die sog. Parameterliste. Der Unterschied zwischen einer Prozedur und einer Funktion
liegt im return-Wert. Eine Prozedur hat keinen Returnwert, eine Funktion genau einen. Der Returwert
der Funktion kann nicht als Eingabeparameter verwendet werden. Er ist ein reiner Ausgabeparameter
und stellt gewissermaßen das Ergebnis der Funktion dar. Parameter wie auch Returnwert haben
jeweils einen bestimmten SQL-Datentyp oder auch einen speziellen Oracle-SQL-Datentyp.
Referenzen auf Datentypen müssen in Oracle als eigener Typ definiert werden. Dies geschieht in einem
PACKAGE. Ein Package kann neben Typdefinitionen auch Definitionen von Prozeduren und Funktionen
enthalten.
Beispiel: Eine Stored Procedure, die ein ResultSet zurückgibt.
Ein Java-ResultSet entspricht in Oracle einer Referenz auf einen cursor und muß als eigener Typ
vorgestellt werden:
create or replace package types as type resultset is ref cursor; end;
Nun schreiben wir uns eine Funktion, die eine komplette Tabelle zurückliefert. Sie erhält keine Übergabeparameter, verwendet obige Typdefinition und liefert die Standard-SUN-Beispiel-Tabelle coffees zurück.
create or replace function getresultset return types.resultset as rc types.resultset; begin open rc for select * from coffees ; return rc; end;
Hinter dem ersten return wird der Typ des Returnwerts festgelegt und eine Variable rc von diesem Typ
angelegt.
Der Funktionsrumpf wird zwischen begin und end eingeschlossen und enthält zwei Statements.
Die Ergänzung von or replace ist sehr praktisch, da man in Oracle eine Funktion oder Prozedur
oder Package nur einmal anlegen kann. So kann man problemlos Variationen herbeiführen.
Man kann aber auch per Hand löschen mit
drop package paket_name; //oder drop procedure prozedur_name; //>oder drop function funktions_name;
Ausgehend von einem Objekt vom Typ Connection con sieht das ganze von Java aus folgendermaßen aus:
Statement stmt = con.createStatement(); String typeString = "create or replace package types as " + "type resultset is ref cursor ; " + "end;" ; stmt.execute(typeString);
Nachdem wir den Typ vereinbart haben, legen wir nun die Stored Procedure an.
String procString = "create or replace function getresultset " + "return types.resultset as rc types.resultset; " + "begin " + " open rc for select * from coffees ; " + " return rc; " + "end;" ; stmt.execute(procString);
Damit hat Oracle von der Existenz der Prozedur erfahren. Aufruf und Ausgabe der ResultSet sieht nun wie folgt aus.
//import oracle.jdbc.driver.*; !! CallableStatement cs = con.prepareCall("{ ? = call getresultset }"); // OUT-parameter registrieren cs.registerOutParameter(1, OracleTypes.CURSOR); cs.execute();
Jeder Ausgabeparameter muß mit einer Methode registerOutParameter() von Callable Statement registriert werden. Die Numerierung beginnt stets mit 1 und dem Returnwert, falls vorhanden. Nach dem Registrieren kann die Funktion ausgeführt werden. Dies geschieht mit execute() oder executeUpdate(), aber nicht mit executeQuery(). Obwohl das Ergebnis eine ResultSet ist, liefert executeQuery() hier null ! Das richtige Ergebnis liefert entweder die Methode getObject() oder die Methode getCursor() mit dem Index 1.
ResultSet rs = (ResultSet)cs.getObject(1);
oder
import oracle.jdbc.driver.*; ResultSet rs = ( (OracleCallableStatement) cs).getCursor(1);
Die Ausgabe des ResultSets ist straightforward
ResultSetMetaData rsmd = rs.getMetaData(); int cols = rsmd.getColumnCount() ; while (rs.next()) { for(int i=1; i<cols; i++) System.out.print( rs.getString(i)+'\t' ); System.out.println( rs.getString(cols) ); }
Hier nun das komplette Beispiel. Das Anlegen der Prozedur und das Ausgeben des ResultSets wird jeweils in eine Methode verlagert.
import java.sql.*; import java.util.*; import oracle.jdbc.driver.*; public class CallableStatementDemo { public static void main(String args[]) { //Treiber laden Driver drv = new oracle.jdbc.driver.OracleDriver() ; System.out.println("Treiberliste"); for( Enumeration en = DriverManager.getDrivers(); en.hasMoreElements() ; ) System.out.println( en.nextElement().getClass().getName() ); try { // Verbindung zu oracle herstellen. Connection con = DriverManager.getConnection("jdbc:oracle:thin:@:1521:oracle", "scott", "tiger"); // Anlegen der stored procedure. createStoredProcedure(con); // Vorbereiten der stored procedure CallableStatement cs = con.prepareCall("{ ? = call getresultset }"); // OUT-parameter registrieren cs.registerOutParameter(1, OracleTypes.CURSOR); cs.execute(); // keine executeQuery() hier!! // beide möglichkeiten flutschen !! ResultSet rs = (ResultSet)cs.getObject(1); //ResultSet rs = ( (OracleCallableStatement) cs).getCursor(1); // ResultSet ausgeben printResultSet(rs); } catch(SQLException ex) { System.out.println(ex); } } // end main // ------------------------------ createStoredProcedure ------------------------------ static void createStoredProcedure(Connection conn) throws SQLException { Statement stmt = conn.createStatement(); // typ muß angelegt werden // resultset ist eine referenz auf cursor // ref cursor ist nämlich kein typ, sondern muß erst als solcher angelegt werden String typeString = "create or replace package types as " + " type resultset is ref cursor ; " + "end;" ; stmt.execute(typeString); // funktion verwendet diesen typ // returntyp ist vom typ types.resultset, // ref cursor kann kein returntyp sein !!! // die variable rc vom typ types.resultset wird angelegt, // erhält einen wert und wird zurückgegeben String procString = "create or replace function getresultset " + " return types.resultset as rc types.resultset; " + " begin " + " open rc for select * from coffees ; " + " return rc; " + " end;" ; stmt.execute(procString); } // end createStoredProcedure // ---------------------------------- printResultSet --------------------------------- public static void printResultSet(ResultSet rs) throws SQLException { ResultSetMetaData rsmd = rs.getMetaData(); int cols = rsmd.getColumnCount() ; while (rs.next()) { for(int i=1; i<cols; i++) System.out.print( rs.getString(i)+'\t' ); System.out.println( rs.getString(cols) ); } } // end printResultSet } // end class