Java w Oracle 9i

Autor: Ngo Chi Lang

Interfejsy programistyczne Javy w Oracle 9i

Java w Oracle 9i można używać na kilka sposobów :

Architektura Javy w Oracle 9i

Architektura Javy w Oracle 9i

Java Stored Procedures

Java Stored Procedures to zwykle klasy Javy które są przechowywane (w postaci skompilowanej lub razem ze źródłem) w bazie danych. Oracle zapewnia 100% zgodność ze specyfikacją Javy co oznacza, że dowolną klasę Javy można przechowywać i wykonywać na serwerze bazodanowym. Wyjątkami są klasy Javy które korzystają z intefejsu graficznego np. AWT, applety, które nie mogą być wykonywane z oczywistych powodów.

Procedury przechowywane Javy mogą być wykorzystane w trzech kontekstach :

Zalety procedur przechowywanych Javy (Java Stored Procedures)

Wydajność

Raz skompilowane i przechowywane w takie postaci na serwerze, procedury są szybkie i efektywne. Serwer automatycznie cache'uje kod i rozdziela między wieloma użytkownikami co pozwala odciążyć pamięć i zwiększa szybkość wywołania. Procedury pozwalają realizować logikę biznesową od razu wewnątrz serwera co zwiększa wydajność eliminująć problemy z przepustowością sieci.

Łatwość pisania i używania

Jako rozszerzenie do funkcji SQL, zwiększają moc SQL'a, pozwala uniknąć redundancję w kodzie aplikacji co oszczędza czas i zwiększa produktywność. Procedury przechowywane Javy można tworzyć praktycznie w dowolnym środowisku programistycznym.

Skalowalność

Używają automatycznego śledzenia zależności między procedurami pomaga zmniejszyć zapotrzebowanie na pamięć. Mechanizm pamięci współdzielonej w MTS (Multi-Threaded Server) pozwala Oracle'owi obsłużyć do 10,000 równoczesnych użytkowników na jednym węźle.

Łatwość zarządzania, wprowadzania modyfikacji, replikacji

Raz przetestowane procedury przechowywane mogą być używane w wielu aplikacjach. Wprowadzenie zmian w implementacji bez zmian interfejsu są przezroczyste dla aplikacji. Łatwiej też zarządzać zmianami w procedurach na serwerze niż w wielu rozproszonych aplikacjach. Mechanizm replikacji na serwerze pozwala rozsyłać poprawki w procedurach między wieloma instancjami bazy

Bezpieczenstwo

Oracle używa mechanizm bezpieczenstwa z Java 2 do ochrony maszyny wirtualnej Javy. Procedury załadowane do bazy danych są traktowane jako nieuwierzytelnione. Dopiero użytkownik z odpowiednimi uprawnieniami moze ich opublikować i dodawać uprawnienia do ich wykonania innym użytkownikom. Dla każdej procedury można ustawić szczegółowe uprawnienia np. pozwalające tylko na UPDATE na tabeli ale bez dostępu do niej (INSERT, SELECT, itp.)

Przykład.

  1. Stworzyć klasę Javy np. Hello.java
  2. public class Hello
    {
       public static String world ()
       {
          return "Hello world";
       }
    }
    
  3. Załadować do bazy za pomocą narzędzia loadjava (lub ręcznie za pomocą polecenia SQL CREATE JAVA). Można załadować wcześniej skompilować klasę Javy
    loadjava -user scott/tiger Hello.class
    albo można polecić loadjava skompilowanie w momencie załadowania lub można narzucać kompilowanie dopiero w czasie wywołania tej klasy w bazie.
  4. Opublikować procedurę/funkcję za pomocą CREATE FUNCTION/CREATE PROCEDURE
    create or replace function HELLOWORLD return VARCHAR2 as
    		 language java name 'Hello.world () return java.lang.String';
    
  5. Wywołąć z poziomu SQL*PLUS'a
    SQL>variable jakisCiag varchar2(100);
    SQL>call HELLOWORLD INTO :jakisCiag;
    SQL>print jakisCiag;
    	
    lub po prostu SELECT HELLOWORLD FROM DUAL

Odwołanie do bazy przez wewnętrzny interfesj JDBC (Server-side Internal JDBC)

Wewnętrzy interfejs JDBC uruchomiowy jest wewnątrz aktualnego kontekstu użytkownika w ramach domyślnej transakcji więc nie trzeba już rejestrować driver'ów za pomocą DriverManagera, zamiast tego można po prostu użyć

Connection conn =
  DriverManager.getConnection("jdbc:default:connection:");
ponieważ procedura jest uruchomiowa wewnątrz bazy driver jest de facto zarejestrowany.
Przykład.
import java.sql.*;
import java.io.*;
import oracle.jdbc.*;

public class RowCounter {
  public static int rowCount (String tabName) throws SQLException {
    Connection conn =
      DriverManager.getConnection("jdbc:default:connection:");
    String sql = "SELECT COUNT(*) FROM " + tabName;
    int rows = 0;
    try {
      Statement stmt = conn.createStatement();
      ResultSet rset = stmt.executeQuery(sql);
      while (rset.next()) {rows = rset.getInt(1);}
      rset.close();
      stmt.close();
    } catch (SQLException e) {System.err.println(e.getMessage());}
    return rows;
  }
}

Java Stored Procedures w triggerze

Przykładowo rozpatrzmy procedurę która aktualizuje zarobki pracowników jednocześnie zapamiętując poprzednie zarobki.
import java.sql.*;
import java.io.*;
import oracle.jdbc.*;

public class DBTrigger {
  public static void logSal (int empID, float oldSal, float newSal)
  throws SQLException {
    Connection conn =
      DriverManager.getConnection("jdbc:default:connection:");
    String sql = "INSERT INTO sal_audit VALUES (?, ?, ?)";
    try {
      PreparedStatement pstmt = conn.prepareStatement(sql);
      pstmt.setInt(1, empID);
      pstmt.setFloat(2, oldSal);
      pstmt.setFloat(3, newSal);
      pstmt.executeUpdate(); 
      pstmt.close();
    } catch (SQLException e) {System.err.println(e.getMessage());}
  }
}
Po wczytaniu powyższej klasy Javy do bazy. Zdefiniujemy procedurę
CREATE OR REPLACE PROCEDURE log_sal (
  emp_id NUMBER, old_sal NUMBER, new_sal NUMBER)
AS LANGUAGE JAVA
NAME 'DBTrigger.logSal(int, float, float)';
Twórzmy do tego tabele oraz trigger aktualizujący :
CREATE TABLE sal_audit (
  empno  NUMBER, 
  oldsal NUMBER, 
  newsal NUMBER);
	
CREATE OR REPLACE TRIGGER sal_trig
AFTER UPDATE OF sal ON emp
FOR EACH ROW
WHEN (new.sal > 1.2 * old.sal)
CALL log_sal(:new.empno, :old.sal, :new.sal);

Wywoływanie Javy z poziomu PL/SQL'a

Możemy wykorzystać procedurę row_count z poprzedniego przykładu :
CREATE PROCEDURE calc_bonus (emp_id NUMBER, bonus OUT NUMBER) AS
  emp_count NUMBER;
  ...
BEGIN
  emp_count := row_count('emp');
  ...
END;

Wywołanie procedur PL/SQL z poziomu Javy

Załóżmy, że mamy funkcję napisać w PL/SQL zwracającą stan konta
 
FUNCTION balance (acct_id NUMBER) RETURN NUMBER IS
  acct_bal NUMBER;
BEGIN
  SELECT bal INTO acct_bal FROM accts
    WHERE acct_no = acct_id;
  RETURN acct_bal;
END;
Możemy tą funkcję wykorzystać w naszym programie w Javie w ten sposób:
CallableStatement cstmt = conn.prepareCall("{? = CALL balance(?)}");
cstmt.registerOutParameter(1, Types.FLOAT);
cstmt.setInt(2, acctNo);
cstmt.executeUpdate();
float acctBal = cstmt.getFloat(1);

JDBC

Aby się dostać do bazy danych z poziomu klienta można oczywiście korzystać z interfejsu JDBC. Wyróżnia się dwa sposoby :

Przez interfejs OCI

Na maszynach z instalacja klienta Oracle'a tnz. korzystając z TNSNAMES można użyć :
Connection conn = DriverManager.getConnection 
                  ("jdbc:oracle:oci8:@MyHostString", "scott", "tiger"); 

Przez JDBC Thin Driver

Jeśli nie mamy instalacji klientów Oracle'a np. z poziomu apletów, to musimy szczegółowo określić nazwę komputera, numert portu, SID bazy używajćc :
Connection conn = DriverManager.getConnection 
                  ("jdbc:oracle:thin:@mercury:1521:orac", "scott", "tiger"); 

Oracle 9i w pełni implementuje specyfikację JDBC 2.0 (które były wcześniej dostępne jako rozszerzenia Oracle'a) i obsługuje związane z tym usprawnienia takie jak : Update Batching - zgrupowanie instrukcji INSERT, DELETE, UPDATE i wysyłanie całą grupą do serwera, Fetch size/row prefetching - zdefiniowanie liczbą wierszy pobranych za jednym razem z kursora/wczytanie z wyprzedzeniem z kursora.

SQLJ

import java.sql.*;
import sqlj.runtime.ref.DefaultContext;
import oracle.sqlj.runtime.Oracle;
#sql iterator MyIter (String ename, int empno, float sal);

public class MyExample 
{
   public static void main (String args[]) throws SQLException 
   {
      Oracle.connect
         ("jdbc:oracle:thin:@mercury:1521:orac", "scott", "tiger");

      #sql { insert into emp (ename, empno, sal)
         values ('SALMAN', 32, 20000) };
      MyIter iter;

      #sql iter={ select ename, empno, sal from emp };
      while (iter.next()) {
         System.out.println
            (iter.ename()+" "+iter.empno()+" "+iter.sal());
      }
   }
}
SQLJ pozwala osadzać polecenia SQL wewnątrz kodu Javy. Program w SQLJ jest programem napisanym w Javie zawierające polecenia SQL zgodnie ze specyfikacją ISO-Standard SQLJ Language Reference Syntax. Oracle 9i oferuje obsługę dwóch typów polecen SQL :

Oracle SQLJ składa się z dwóch części :

SQLJ Translator

Prekompilator napisany w całośći w Javie. Bierz program napisany w SQLJ np. Hello.sqlj generuje plik z rozszrzeniem .java oraz wyciąga polecenia SQL i tworzy dla każdej z nich profil(serializowane zasoby Javy zachowujące informacje o operacjach SQL używanych w kodzie), a potem automatycznie wywołuje kompilator Javy generując pliki .class

SQLJ Runtime

Jest uruchomiony przy każdym wykonywaniu programu napisanego SQLJ. Implementuje on operacje SQL, odwołująć się do bazy danych za pomocą JDBC.

Dodatkowo jest jeszcze komponent zwany customizer, który dopasowuje wcześniej wygenerowane profile do konkretnej bazy danych. W przypadku Oracle'a może on wykorzystać specyficzne rozszerzenia i typy dostępne tylko na bazie Oracle.

Jak praktycznie używać SQLJ

Programy w SQLJ można wykonywać zarówno z poziomu klienta jak i po stronie serwera.

Z poziomu klienta można wykonywać używając preprocesora sqlj a potem uruchomić za pomocą polecenia java

SQLJ po stronie serwera

Oracle 9i oferuje możliwość przechowywania i wykonywania kodu SQLJ wewnątrz serwera bazodanowego. W odróżnieniu od SQLJ wykonywane po stronie klienta programy w SQLJ uruchamiane na serwerze mają pewne odróżniające się właściwości :

Kod programu SQLJ pisanego dla serwera można skompilować wcześniej po stronie klienta i załadować do bazy (analogicznie jak w przypadku Java Stored Procedures) lub można załadować sam kod źródłowy w celu tranlacji po stronie serwera.

Przed wywołaniem procedur SQLJ należy je opublikować w sposób analogiczny jak w przypadku Java Stored Procedures :

SQLJ można używać również wewnątrz Enterprise JavaBeans lub wewnątrz CORBA Server Objects.

Odnośniki:

22.02.2002