Oracle a Java
Následující text si neklade za cíl kompletně vyčerpat veškeré možnosti slovního spojení Oracle a Java, některé partie jsou pouze načrtnuty. Správcovské nástroje, které jsou vesměs napsány v Javě, vývojové protředí JDeveloper či Oracle Appserver nejsou vůbec zahrnuty.
Klientská strana
Klient má v Javě v zásadě dvě možnosti práce s databází Oracle: standardní JDBC a méně standardní JSQL/SQLj.
JDBC
JDBC (Java Database Connectivity) je sada balíků tříd, které jsou součástí Sun JDK/JRE minimálně od verze 1.3, které vytváří jednotné rozhraní mezi relačními databázemi a programem v Javě. JDBC používá mechanismus ovladačů, kdy každá databáze dodává vlastní ovladač, který realizuje požadavky kladené klientským programem. Takovýto ovladač dodává i Oracle pro své databáze. Je uložen v adresáři s instalací, většinou jako jdbc/lib/classes*.zip (od Oracle 8.1.6 existují dvě verze - classes111.zip pro JDK/JRE 1.1.x a classes12.zip pro JDK/JRE 1.2.x a vyšší). Tento archív je třeba přidat do classpath klientského kódu (způsob je závislý na vývojovém a runtime prostředí).
Je možné využít dvou způsobů připojení k databázi: THIN a OCI. THIN se připojuje k databázi přímo přes síťový protokol, OCI (Oracle Call Interface) používá nativní knihovny z klientské instalace Oracle (v takovém případě je třeba používat ovladač stejné verze jako je instalace).
Příklad spojení přes THIN:
// JDBC balíky import java.sql.*; class Test { public static void main(String args[]) throws Exception { // při překladu není nutno mít ovladač v classpath Class.forName("oracle.jdbc.driver.OracleDriver"); // otevření spojení s databází // - jdbc označuje JDBC "URI", oracle označuje použitý JDBC protokol // (prakticky ovladač) a thin je sub-protokol (prakticky typ spojení) Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@mates:1521:semora", "login", "heslo"); // položení SQL dotazu Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT banner FROM SYS.V_$VERSION"); // vypsání výsledku while (rs.next()) { // první sloupec výsledku jako java.lang.String System.out.println(rs.getString(1)); } // uzavření dotazu i všech výsledků st.close(); // uzavření spojení conn.close(); } }
Spojení přes OCI se liší pouze použitým JDBC "URI":
// typů OCI je více (např. ještě starší oci7) Connection conn = DriverManager.getConnection("jdbc:oracle:oci8:@ (DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP) (Host=mates.ms.mff.cuni.cz)(Port=1521))) (CONNECT_DATA=(SID=semora)(SERVER=DEDICATED)))", "login", "heslo");
Oba příklady lze přeložit a spustit následující sadou příkazů (konkrétní classes*.zip je závislý na verzi Oracle):
javac Test.java java -classpath $ORACLE_HOME/jdbc/lib/classes111.zip:. Test
Základní pojmy v JDBC jsou Connection, Statement (a PreparedStatement) a ResultSet.
Connection reprezentuje spojení do databáze (viz výše), jdou skrz něj získat informace o databázi, informace o vzniklých chybách a varováních, vyznačovat transakce a v neposlední řadě také vytvářet Statement objekty.
Statement reprezentuje jedno SQL či DDL "volání" databáze. V podstatě se chová stejně jako sqlplus konzole. Objekt tohoto typu může být bez problémů použit opakovaně.
PreparedStatement je variantou třídy Statement, která umožňuje komfortnější a bezpečnější sestavování (a provádění) SQL příkazů. Na místech SQL příkazu, kam je třeba vložit nějakou hodnotu, např. řetězec či číslo, se umístí takzvaný placeholder (konkrétně otazník). Potom se pomocí set*() metod nastaví hodnoty pro jednotlivé placeholdery (jsou číslovány od 1). Výhodou tohoto přístupu je, že se JDBC ovladač postará o správné předání hodnoty do databáze, což v případě čistého Statement je třeba vyřešit ve vlastní režii (správné uvozovky na správných místech, správný formát data a času apod.). Příklad:
import java.sql.*; class Test { public static void main(String args[]) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@mates:1521:semora", "login", "heslo"); // položení SQL dotazu, v podmínce se používá placeholder PreparedStatement st = conn.prepareStatement( "SELECT name FROM persons WHERE surname=?"); st.setString(1, "Smith"); ResultSet rs = st.executeQuery(); // vypsání výsledku while (rs.next()) { // sloupec name jako java.lang.String System.out.println(rs.getString("name")); } st.close(); conn.close(); } }
ResultSet prezentuje výsledek dotazu iterativním způsobem. Poskytuje metody pro přečtení informací z aktuální řádky výsledku na základě jména sloupce či pozice (v JDBC se, na rozdíl od zbytku Javy, čísluje od 1). Jednotlivé hodnoty lze reprezentovat různými způsoby, přičemž JDBC ovladač se stará o řádné konverze. Např. metoda getObject() vrátí javový typ, který se JDBC ovladači zdá nejbližší databázovému typu sloupce (pro většinu typů společných různým databázím je definováno v JDBC), metoda getString() vrátí vždy java.lang.String, getInt() vrátí vždy int apod. Lze pracovat i s BLOBy.
Zájemci o bližší informace nechť nahlédnou do dokumentace k balíkům java.sql.* a spol, např. na stránky Sunu JDBC Technology.
JSQL/SQLj
JSQL a SQLj jsou dva názvy pro společný projekt firem IBM, Tandem a Oracle, který měl umožnit lepší propojení Javy a SQL databází. Některé prameny uvádí, že JSQL je standard a SQLj jeho implementace pro Oracle, novější prameny používají jednotný název SQLj pro obojí. Současný SQLj standard obsahuje více částí, z nichž asi nejzajímavější je SQLj: Embedded SQL (standardizováno ANSI), který umožňuje vkládání SQL příkazů přímo do kódu.
K preprocessingu (a případné kompilaci) slouží nástroj sqlj, který ze zdrojového souboru *.sqlj vytvoří *.class či *.java (dle předaných argumentů). Jak bude patrné z příkladů, tak SQLj: Embedded SQL staví nad JDBC a "pouze" automatizuje volání různých SQL příkazů.
Syntax základního embedded SQL je velmi jednoduchá: #sql { statement };
. Je možno použít odkazy :variable
na proměnné viditelné v místě zápisu.
Příklad:
// SQLj a JDBC balíky import sqlj.runtime.*; import sqlj.runtime.ref.*; import java.sql.*; class Test { public static void main(String args[]) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@mates:1521:semora", "login", "heslo"); // nastavení spojení pro vložené SQL DefaultContext.setDefaultContext(new DefaultContext(conn)); // položení SQLJ dotazu String banner; // použit zvláštní příkaz SELECT cosi1, cosi2 INTO proměnná1, proměnná2 ... #sql { SELECT banner INTO :banner FROM SYS.V_$VERSION }; System.out.println(banner); conn.close(); } }
Příklad by mělo být možno přeložit a spustit následující sadou příkazů (konkrétní classes*.zip je závislý na verzi Oracle), bohužel sqlj vždy zhavaruje na interní chybu:
sqlj Test.sqlj java -classpath $ORACLE_HOME/jdbc/lib/classes111.zip:. Test
Preprocesor sqlj lze pustit také v poměrně zajímavém módu kontroly překládaného kódu zda se nevyskytují odlišnosti mezi SQLj kódem a existujícím databázovým schématem.
Výše zmíněný příklad ukázal čtení jednořádkového výsledku. Pro čtení víceřádkového výsledku se používají hlavně SQLj iterátory (v terminologii standardu občas nazývané kurzory). Základním typem iterátoru (a jediným standardním) je silně typový iterátor. Oracle podporuje ještě jeden druh iterátoru zvaný slabě typový, který zde nebude rozebírán.
Silně typové iterátory jsou dvou druhů: pojmenované a poziční. Příklad užití pojmenovaného iterátoru:
import sqlj.runtime.*; import sqlj.runtime.ref.*; import java.sql.*; // deklarace iterátoru - sqlj vytvoří zvláštní třídy #sql iterator TestNamedIter (String banner); class Test { public static void main(String args[]) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@mates:1521:semora", "login", "heslo"); DefaultContext.setDefaultContext(new DefaultContext(conn)); // deklarace instance iterátoru TestNamedIter niter; #sql niter = { SELECT banner FROM SYS.V_$VERSION }; // iterace výsledků while (niter.next()) { System.out.println(niter.banner()); } // zavření iterátoru niter.close(); conn.close(); } }
Příklad užití pozičního iterátoru:
import sqlj.runtime.*; import sqlj.runtime.ref.*; import java.sql.*; // deklarace iterátoru - poziční operátor nemá pojmenované parametry #sql iterator TestPosIter (String); class Test { public static void main(String args[]) throws Exception { Class.forName("oracle.jdbc.driver.OracleDriver"); Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@mates:1521:semora", "login", "heslo"); DefaultContext.setDefaultContext(new DefaultContext(conn)); // deklarace instance iterátoru TestPosIter piter; #sql piter = { SELECT banner FROM SYS.V_$VERSION }; // iterace výsledků while (true) { String banner; // načtení další řádky výsledku - FETCH proměnná_iterátor INTO proměnné #sql { FETCH :piter INTO :banner }; // test se musí provádět až po FETCH if (piter.endFetch()) { break; } System.out.println(banner); } // zavření iterátoru piter.close(); conn.close(); } }
Zájemci o bližší seznámení s SQLj nechť nahlédnou do Oracle9i SQLJ Developer's Guide and Reference.
Serverová strana
Podpora Javy na serveru se skládá ze tří částí: podpory běhu, podpory volání tříd a podpory práce s databází.
Zájemci o bližší seznámení se serverovou podporou Javy nechť nahlédnou do Oracle9i Java Developer's Guide.
Podpora běhu
Zdrojové kódy (*.java) a přeložené třídy (*.class) (a případné přidružené datové soubory) jsou uloženy v databázi (ve schématu toho kterého uživatele, tj. hierarchie tříd a konkrétní implementace jsou privátní, pakliže není řečeno jinak) a jsou vidět ve view USER_OBJECTS. Jejich provádění (resp. provádění přeložených tříd) má na starosti JServer, což je JVM (Java Virtual Machine) implementace, která běží jako součást Oracle serveru. Při běhu se využívají dva paměťové pooly, které jsou sdílené všemi uživateli:
- SHARED POOL
- používá se při načítání tříd do databáze a při kompilaci zdrojových kódů
- JAVA POOL
- používá se pro paměť alokovanou běžícími třídami
K nahrávání a rušení tříd slouží utility loadjava a dropjava, popř. stejně pojmenované funkce z PL/SQL balíku DBMS_JAVA. Příklad loadjava:
loadjava -thin -u login/password@mates:1521:semora Test.java
Příklad dropjava:
dropjava -thin -u login/password@mates:1521:semora Test.java
Jako parametr loadjava lze uvést i JAR nebo ZIP, které se na straně serveru rozloží do jednotlivých částí.
Pakliže se loadjava pustí s parametrem -resolve, tak se provede okamžitý resolving (dohledání vazeb na další třídy). V případě, že se ukládaly zdrojové soubory, tak se před resolvingem nejprve přeloží. Pakliže se -resolve neuvede, tak se resolving provádí až při použití patřičné třídy. Lze také uvést -resolver, což je odkaz do schémat, ve kterých se mají hledat odkazované třídy (odpovída tradičnímu CLASSPATH).
Již existující objekty lze nahradit novější verzí (loadjava neudělá nic, pakliže nedošlo ke změně souboru). Pakliže byla do databáze uložena novější verze zdrojového kódu a loadjava nebyl nucen provést kompilaci, tak se při nejbližším spuštění daného kódu provede rekompilace. Taktéž se provádí rekompilace, pakliže se změní závislosti.
JServer standardně používa JIT (Just-In-Time) kompilaci do nativního kódu. Pro větší zrychlení je k dispozici Accelerator, což je externí utilita, která generuje z javových tříd nativní sdílené knihovny.
Podpora volání tříd
Statické metody lze volat jako jakékoliv jiné PL/SQL funkce, pouze je třeba předem nadeklarovat funkci příkazem CREATE OR REPLACE FUNCTION. Příklad třídy, jejíž metoda bude dostupná jako funkce:
class Fibonacci { public static int fib(int n) { if (n == 1 || n == 2) return 1; else return fib(n - 1) + fib(n - 2); } }
Načtení třídy do databáze:
loadjava -thin -u login/password@mates:1521:semora Fibonacci.java
Definice funkce:
CREATE OR REPLACE FUNCTION fib (n NUMBER) RETURN NUMBER AS LANGUAGE JAVA NAME 'Fibonacci.fib(int) return int';
A její volání:
VARIABLE f NUMBER CALL fib(13) INTO :f; PRINT f
Funkce je možno sdružovat do balíků (neplést s Java balíky) stejně jako PL/SQL funkce.
Podpora práce s databází
Podpora práce s databází na serveru je stejná jako na klientovi, tj. lze použít jak JDBC, tak SQLj. Zdrojové kódy v SQLj jsou dokonce automaticky rozpoznány, předzpracovány a kompilovány. Jediným rozdílem je použité JDBC "URI", jelikož v serveru již není třeba specifikovat parametry spojení. Příklad:
import java.sql.*; class Test { public static void main(String args[]) throws Exception { // použití implicitního spojení Connection conn = DriverManager.getConnection("jdbc:default:connection:"); Statement st = conn.createStatement(); ResultSet rs = st.executeQuery("SELECT banner FROM SYS.V_$VERSION"); while (rs.next()) { System.out.println(rs.getString(1)); } st.close(); conn.close(); } }
V uvedeném ilustračním příkladě se provádí výpis na standardní výstup, který se na serveru nezobrazí, ale uloží do trasovacího logu (je-li kód spuštěn např. jako uložená procedura). Toto chování je možno v sqlplus konzoli změnit, takže výpisy se zobrazí po skončení příkazu (velikost bufferu 2000 bytů):
SET SERVEROUTPUT ON CALL dbms_java.set_output(2000);
Zdroje
- Oracle9i dokumentace
- Oracle/Java FAQ
http://www.orafaq.com/faqjdbc.htm - SQLj.org
http://www.sqlj.org/