Apache Derby: ładowanie klas z plików JAR w bazie danych
Gdy tworzymy np. własny typ danych to opisuje go klasa Java implementująca Externalizable
. Gdy tworzymy procedury i metody – również przygotowujemy stosowne metody Java. Ta klasa i metody muszą być dostępne tam, gdzie jest baza danych. Najlepiej, gdyby całość materiału Java mogła być umieszczona na stałe w bazie i była zawsze widoczna gdy jest potrzebna. I właśnie do tego celu służy ten świetny, choć kiepsko opisany mechanizm ładowania klas z plików JAR umieszczonych w bazie danych.
Klasy te są widoczne jedynie dla funkcji i procedur SQL zawartych w bazie danych.
Klasy umieszczone w plikach JAR w bazie danych są ładowane w pierwszej kolejności.
Spróbujmy zatem jak to działa.
Przygotowanie klas Java
Klasa Kolor.java
Tworzymy klasę aderby.specyf.jars.Kolor
będąca nowym typem danych do przechowywania informacji o kolorze w postaci pojedynczej liczby int
. Oczywiście spokojnie moglibyśmy przechowywać kolor jako liczbę INT
, ale musimy jakoś zilustrować recepturę.
package aderby.specyf.jars; import java.io.*; public class Kolor implements Externalizable { private int kolor; public Kolor() { this(0); } public Kolor(int kolor) { this.kolor = kolor; } @Override public void readExternal(ObjectInput in) throws IOException { setKolor(in.readInt()); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(kolor); } public int getKolor() { return kolor; } public void setKolor(int kolor) { this.kolor = kolor; } }
Klasie towarzyszy kilka metod umieszczonych w klasie DerbyUtil
pozwalających na przeliczanie kolorów na RGB i liczby heksadecymalne i odwrotnie.
Działanie klasy Kolor
ilustruje klasa:
Klasa R097_TEST.java
package aderby.specyf.jars; import aderby.DerbyUtil; import java.sql.*; public class R097_TEST { public static final String createTypeKolor = "CREATE TYPE kolor " + "EXTERNAL NAME 'aderby.specyf.jars.Kolor' LANGUAGE JAVA"; public static final String createTable = "CREATE TABLE colors(" + "id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS " + "IDENTITY(START WITH 1, INCREMENT BY 1), color KOLOR)"; public static void main(String[] args) { DerbyUtil.startDerbyEngine("EmbeddedDriver"); Connection con = DerbyUtil.connectEmbeddedDB("C:/Przyklady/r097_types", ";create=true"); Statement stat = null; try { stat = con.createStatement(); stat.addBatch(createTypeKolor); stat.addBatch(createTable); stat.executeBatch(); } catch (SQLException e) { e.printStackTrace(); } PreparedStatement stmt = null; String insertSQL = "INSERT INTO colors (id, color) values (DEFAULT,?)"; try { stmt = con.prepareStatement(insertSQL); stmt.setObject(1, new Kolor(1234567)); stmt.executeUpdate(); stmt.clearParameters(); } catch (SQLException e1) { e1.printStackTrace(); } String selectSQL = "SELECT * FROM colors"; PreparedStatement pstm = null; ResultSet rs = null; int id = -1; int col = -1; try { pstm = con.prepareStatement(selectSQL); rs = pstm.executeQuery(); while (rs.next()) { id = rs.getInt("id"); col = ((Kolor) rs.getObject("color")).getKolor(); } } catch (SQLException e) { e.printStackTrace(); } System.out.println("id: " + id); int[] cols = DerbyUtil.IntToRGB(col); DerbyUtil.print(cols); DerbyUtil.close(rs); DerbyUtil.close(stat); DerbyUtil.close(stmt); DerbyUtil.close(pstm); DerbyUtil.close(con); DerbyUtil.shutdownDerbyEngine(); } }
A oto wynik uruchomienia klasy.
id: 1 [18, 214, 135]
Utworzenie pliku archiwum
W sposób podany w innej recepturze w pliku typy.jar umieszczamy klasę Kolor
w pakiecie types. Plik umieszczamy w folderze C:/Przykłady.
Plik *.jar nie powinien zawierać plików z pakietów java
i javax
oraz innych klas JDK.
Klasy te nawet jeśli zostaną spakowane w archiwum i tak nie będą z niego załadowane.
Jeśli poza powyższymi klasami do uruchomienia twojej klasy potrzebne są klasy z innych bibliotek Java w plikach *.jar to problem można rozwiązać przez:
- rozpakowanie archiwum takiej biblioteki i dodanie do swojego projektu potrzebnych klasy, o ile prawa autorskie na to pozwalają
- umieszczenie plików *.jar tych bibliotek w bazie Derby podobnie jak to uczynisz z własną aplikacją. System Derby pozwala na załadowanie dowolnej liczby klas z dowolnej liczby archiwów z dowolnej liczby schematów i baz danych (w obrębie systemu)
- dostarczenie użytkownikowi bibliotek i zadbanie, żeby znalazły się one u ścieżce klas użytkownika
Utworzenie bazy danych
Tworzymy bazę danych, która będzie zawierać plik typy.jar. Wykorzystamy klasę R097_CREATE_DB
.
Klasa R097_CREATE_DB
package aderby.specyf.jars; import aderby.DerbyUtil; import java.sql.*; public class R097_CREATE_DB { private static final String baza = "C:/Przyklady/baza_typy"; public static void main(String[] args) { DerbyUtil.startDerbyEngine(DerbyUtil.embdriver); Connection conn = DerbyUtil.connectEmbeddedDB(baza, ";create=true"); DerbyUtil.close(conn); DerbyUtil.shutdownEmbeddedDB(baza); DerbyUtil.shutdownDerbyEngine(); } }
Po uruchomieniu klasy w folderze C:/Przyklady pojawia się baza danych (Rys. 290):
Struktura bazy przedstawiona jest na obrazku (Rys. 291):
Instalowanie pliku archiwum w bazie danych
Do bazy danych można dodać więcej niż jeden plik archiwum.
Aby zainstalować plik archiwum w bazie danych musimy użyć procedury systemowej bazy danych
SQLJ.INSTALL_JAR
, która może być uruchomiona przez obiekt JDBC – polecenie wywołujące klasy CallableStatement
.
Składnia polecenia dla naszego przykładu jest następująca:
CALL SQLJ.INSTALL_JAR('C:\Przyklady\typy.jar', APP.typy, 0)
CALL – oznacza 'wywołaj’
SQLJ – to biblioteka zewnętrzna do unifikacji SQL z Javą – użyta przez Derby.
INSTALL_JAR – to jedna z procedur tej biblioteki.
Argumenty metody oznaczają kolejno:
- ścieżka do pliku *.jar, który będzie umieszczony w bazie
- nazwa archiwum w bazie poprzedzona nazwą schematu. O schematach powiemy więcej przy omawianiu budowy bazy danych. Tutaj powiemy tylko, że schemat jest czymś w rodzaju folderu, czy kontenera, kory zawiera tabele, procedury, jary, etc. Schematów w bazie może być wiele. Jeśli nie utworzysz schematu, w bazie jest obecny tylko jeden schemat (domyślny)- APP
- tak więc archiwum zostanie umieszczone w schemacie APP pod nazwą typy
- można wstawić 0 lub 1. '1′ oznacza że istnieje plik deskryptora. '0′ oznacza, że pliku nie ma. W Derby umownie wstawia się '0′ – chociaż można wstawić '1′, ponieważ ten argument i tak jest ignorowany przez Derby
Dodanie pliku archiwum do bazy danych
Użyjemy metody DerbyUtil.installJar
. Po uruchomieniu klasy R097_INSTALL_JAR
w bazie danych pojawi się dodatkowy folder jar (Rys. 292):
Klasa R097_INSTALL_JAR.java
package aderby.specyf.jars; import aderby.DerbyUtil; import java.sql.*; public class R097_INSTALL_JAR { private static final String baza = "C:/Przyklady/baza_typy"; public static final String jarPath = "C:/Przyklady/typy.jar"; public static final String jarName = "APP.typy"; public static final int deploy = 0; public static void main(String[] args) { DerbyUtil.startDerbyEngine(DerbyUtil.embdriver); Connection conn = DerbyUtil.connectEmbeddedDB(baza, ""); DerbyUtil.installJar(conn, jarPath, jarName, deploy); DerbyUtil.close(conn); DerbyUtil.shutdownEmbeddedDB(baza); DerbyUtil.shutdownDerbyEngine(); } }
Ustawienie archiwum w ścieżce klas bazy danych
Musimy poinformować bazę o ścieżce do pliku archiwum. Możemy to uczynić przez wywołanie procedury systemowej:
CALL SYSCS.SYSCS_SET_DATABASE_PROPERTY
i uruchomienie jej przy użyciu obiektu JDBC klasy polecenia wywołującego CallableStatement
.
Składnia procedury jest następująca:
CALL SYSCS.SYSCS_SET_DATABASE_PROPERTY('derby.database.classpath','APP.baza1')
Procedura ma dwa argumenty:
- nazwa ustawianej właściwości bazy danych
- nazwa pliku archiwum w bazie danych poprzedzona nazwą schematu. Jeśli ustawiamy ścieżkę do więcej niż jednego archiwum – nazwy archiwów oddzielamy ’:’, np. 'APP.b1:APP.b2′
Do ustawienia właściwości u użyjemy metody DerbyUtil.setDBProperty
:
public static boolean setDBProperty(Connection conn, String property, String value) { CallableStatement cstat = null; boolean done = false; try{ cstat = conn.prepareCall( "CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(?,?)"); cstat.setString(1, property); cstat.setString(2, value); done = cstat.execute(); } catch (SQLException e){ e.printStackTrace(); } finally{ close(cstat); } return done; }
Ustawienia dokonamy przez uruchomienie klasy R097_SET_PROP.java
.
Klasa R097_SET_PROP.java
package aderby.specyf.jars; import aderby.DerbyUtil; import java.sql.*; public class R097_SET_PROP { private static final String baza = "C:/Przyklady/baza_typy"; public static final String jarName = "APP.typy"; public static final String property = "derby.database.classpath"; public static final String sql1 = "VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY('derby.database.classpath')"; public static void main(String[] args) { DerbyUtil.startDerbyEngine(DerbyUtil.embdriver); Connection conn = DerbyUtil.connectEmbeddedDB(baza, ""); DerbyUtil.setDBProperty(conn, property, jarName); Statement stat = null; ResultSet rs = null; try { stat = conn.createStatement(); rs = stat.executeQuery(sql1); rs.next(); System.out.println(rs.getString(1)); } catch (SQLException e) { e.printStackTrace(); } DerbyUtil.close(rs); DerbyUtil.close(stat); DerbyUtil.close(conn); DerbyUtil.shutdownEmbeddedDB(baza); DerbyUtil.shutdownDerbyEngine(); } }
Po uruchomieniu tej klasy mamy ustawioną ścieżkę klas do pliku archiwum w bazie
APP.typy
Ścieżka musi być ustawiana przy każdym ‘odpaleniu’ bazy danych.
h4 class=”derby”>Ładowanie klas w obrębie bazy danych
Tworzymy nową klasę R097_GET_CLASS
.
Klasa R097_GET_CLASS
package aderby.specyf.jars; import aderby.DerbyUtil; import java.sql.*; public class R097_GET_CLASS { public static final String createTypeKolor = "CREATE TYPE kolor " + "EXTERNAL NAME 'types.Kolor' LANGUAGE JAVA"; public static final String createTable = "CREATE TABLE colors(" + "id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS " + "IDENTITY(START WITH 1, INCREMENT BY 1), color KOLOR)"; public static final String jarPath = "C:/Przyklady/typy.jar"; public static final String jarName = "APP.typy"; public static final int deploy = 0; public static final String property = "derby.database.classpath"; public static void main(String[] args) { DerbyUtil.startDerbyEngine("EmbeddedDriver"); Connection conn = DerbyUtil.connectEmbeddedDB("C:/Przyklady/baza_types", ";create=true"); DerbyUtil.installJar(conn, jarPath, jarName, deploy); DerbyUtil.setDBProperty(conn, property, jarName); Statement stat = null; try { stat = conn.createStatement(); stat.addBatch(createTypeKolor); stat.addBatch(createTable); stat.executeBatch(); } catch (SQLException e) { e.printStackTrace(); } PreparedStatement stmt = null; String insertSQL = "INSERT INTO colors (id, color) values (DEFAULT,?)"; try { stmt = conn.prepareStatement(insertSQL); //stmt.setObject(1, new Kolor(1234567)); stmt.setObject(1, new types.Kolor(1234567)); stmt.executeUpdate(); stmt.clearParameters(); } catch (SQLException e1) { e1.printStackTrace(); } String selectSQL = "SELECT * FROM colors"; PreparedStatement pstm = null; ResultSet rs = null; int id = -1; int col = -1; try { pstm = conn.prepareStatement(selectSQL); rs = pstm.executeQuery(); while (rs.next()) { id = rs.getInt("id"); //col = ((Kolor) rs.getObject("color")).getKolor(); col = ((types.Kolor) rs.getObject("color")).getKolor(); } } catch (SQLException e) { e.printStackTrace(); } System.out.println("id: " + id); int[] cols = DerbyUtil.IntToRGB(col); DerbyUtil.print(cols); DerbyUtil.close(rs); DerbyUtil.close(stat); DerbyUtil.close(stmt); DerbyUtil.close(pstm); DerbyUtil.close(conn); DerbyUtil.shutdownDerbyEngine(); } }
Wszystko jest podobnie, ale typ KOLOR
jest tworzony w oparciu o klasę types.Kolor
.
Przy próbie uruchomienia kodu wyrzucany jest błąd.
Jak widać mamy tutaj dwa odniesienia do klasy Kolor
:
- aderby.specyf.jars.Kolor
- types.Kolor
Typ types.Kolor
jest już widoczny w obrębie bazy danych. SQL protestuje przed próbą umieszczenia obiektu klasy aderby.specyf.jars.Kolor
w polu types.Kolor
. Rozwiązanie jest oczywiste:
- w aplikacji klasa
Kolor
powinna być w takim samym pakiecie jak klasa w pliku *.jar.
Gdy klasę Kolor umieścimy (dodatkowo – była już używana) w pakiecie types
i w kodzie aplikacji odwołamy się do typu types.Kolor
to otrzymamy prawidłowy wynik.
W kodzie klasy R097_GET_CLASS
trzeba więc dokonać dwu zmian:
//stmt.setObject(1, new Kolor(1234567)); stmt.setObject(1, new types.Kolor(1234567));
//col = ((Kolor) rs.getObject("color")).getKolor(); col = ((types.Kolor) rs.getObject("color")).getKolor();
id: 1 [18, 214, 135]
Wymiana pliku archiwum w bazie danych
Plik w archiwum można wymienić używając procedury SQLJ
:
CALL SQLJ.REPLACE_JAR('C:/Przyklady/new_jar.jar','APP.types')
gdzie:
- C:\Przyklady\new_jar.jar’ – jest adresem do pliku *,jar, który ma się znaleźć w bazie danych w miejscu aktualnego pliku/li>
- ’APP.types’ – to nazwa jaru istniejącego w bazie danych, który ma zostać podmieniony.
Po wykonaniu operacji w bazie pod nazwą wewnętrzną 'APP.baza1′ będzie się znajdował 'new_jar.jar’.
Możemy użyć metody klasy DerbyUtil:
public static boolean replaceJar(Connection conn, String newJar, String oldJarDBName) { CallableStatement cstat = null; boolean done = false; try{ cstat = conn.prepareCall("CALL SQLJ.REPLACE_JAR(?,?)"); cstat.setString(1, newJar); cstat.setString(2, oldJarDBName); done = cstat.execute(); } catch (SQLException e){ e.printStackTrace(); } finally{ DerbyUtil.close(cstat); } return done; }
Przykład w klasie R097_REPLACE.java
.
R097_REPLACE.java
package aderby.specyf.jars; import aderby.DerbyUtil; import java.sql.*; public class R097_REMOVE { private static final String baza = "C:/Przyklady/baza_types"; public static final String embdriver = "EmbeddedDriver"; public static final String newJar = "C:/Przyklady/typy1.jar"; public static final String oldJarDBName = "APP.typy"; public static void main(String[] args) { DerbyUtil.startDerbyEngine(embdriver); Connection conn = DerbyUtil.connectEmbeddedDB(baza); DerbyUtil.removeJar(conn, oldJarDBName, 0); DerbyUtil.close(conn); DerbyUtil.shutdownEmbeddedDB(baza); DerbyUtil.shutdownDerbyEngine(); } }
Następuje podmiana pliku typy.jar na
typy1.jar
Odinstalowanie pliku archiwum w bazie danych
Plik *.jar znajdujący się w bazie danych można usunąć używając procedury SQLJ
:
CALL SQLJ.REMOVE_JAR('APP.baza1')
’APP.baza1′ oznacza tutaj plik *.jar do usunięcia.
Można użyć metody:
public static boolean removeJar(Connection conn, String jarDBName, int undeploy) { CallableStatement cstat = null; boolean done = false; try{ cstat = conn.prepareCall("CALL SQLJ.REMOVE_JAR(?,?)"); cstat.setString(1, jarDBName); cstat.setInt(2, undeploy); done = cstat.execute(); } catch (SQLException e){ e.printStackTrace(); } finally{ DerbyUtil.close(cstat); } return done; }
Przykład w klasie R097_REMOVE.java
. Usuwamy plik typy1.jar
z bazy danych.
R097_REMOVR.java
span class="s0">package aderby.specyf.jars; import aderby.DerbyUtil; import java.sql.*; public class R097_REMOVE { private static final String baza = "C:/Przyklady/baza_types"; public static final String embdriver = "EmbeddedDriver"; public static final String newJar = "C:/Przyklady/typy1.jar"; public static final String oldJarDBName = "APP.typy"; public static void main(String[] args) { DerbyUtil.startDerbyEngine(embdriver); Connection conn = DerbyUtil.connectEmbeddedDB(baza); DerbyUtil.removeJar(conn, oldJarDBName, 0); DerbyUtil.close(conn); DerbyUtil.shutdownEmbeddedDB(baza); DerbyUtil.shutdownDerbyEngine(); } }
Plik jar został usunięty z bazy danych chociaż pozostał folder ‘jar’ w którym ten plik się mieścił.
Dynamika zmian
Po zainstalowaniu lub wymianie pliku archiwum w bazie danych nie ma potrzeby zamknięcia i ponownego uruchomienia bazy danych jeżeli spełniony jest warunek:
- jeżeli ścieżka klas bazy danych jest już ustawiona prawidłowo, a zainstalowanie nowego archiwum lub wymiana pliku archiwum nie wymaga zmiany w tej ścieżce (czyli nie ma potrzeby zmiany
derby.database.classpath
)
Przy zmianie derby.database.classpath
i restarcie – wszystkie klasy w jarach są ładowane od nowa, nawet z tych jarów, które nie były zmieniane.
Pliki do ściągnięcia
Aktualny (tworzony narastająco) plik module-info.java
Aktualny (tworzony narastająco) plik DerbyUtil.java
Pliki tworzone narastająco zastępują poprzednie pliki o tej samej nazwie i działają dla wszystkich wcześniej opublikowanych przykładów we wszystkich wpisach w projekcie. W przypadku pliku module-info.java
może być potrzebne skreślenie niepotrzebnych wpisów.