Apache Derby: typ danych XML
Opis
Używany do przechowywania dokumentów XML. Dokumenty mogą być pełnymi dokumentami XML spełniającymi odpowiednie
wymagania albo mogą być sekwencjami XML, które mogą nie spełniać wymogów XML.
Odpowiadający typ Java
java.lang.SQLXML
. Nie używany przez Derby.
Odpowiadający typ JDBC
java.sql.Types.SQLXML . Nie implementowany przez Derby.
Ograniczenia
Aby móc pracować z dokumentami XML muszą być dostępne biblioteki Xalan i Xerces. Derby nie implementuje interfejsów JDBC do obsługi XML i dlatego nie jest możliwe wyszukiwanie wartości XML bezpośrednio ze zbiorów wynikowych ResultSet
. Aby uzyskać dostęp do tych danych należy przekształcić dane XML w łańcuchy znaków (lub strumienie znaków) przy użyciu operatorów Derby: XMLPARSE
i XMLSERIALIZE
w zapytaniach SQL.
Operatory XMLEXISTS
, XMLPARSE
, XMLQUERY
i XMLSERIALIZE
będą omówione w rozdziale 'Funkcje i operatory’.
Przykład
Zadanie programistyczne. Zlecone przez klienta, więc nie możemy forsować lepszych i szybszych rozwiązań. Klient ma chorego psa któremu trzeba mierzyć temperaturę. O każdej zmianie temperatury psa musi być powiadomiony weterynarz. Każdy pomiar dla danego psa musi być zapisany w pliku *.xml. Każdy plik *.xml musi być wpisany do bazy danych. Musimy mieć możliwość wyszukiwania wszystkich albo poszczególnych wyników. Musi istnieć możliwość zapisu dla wielu psów, gdyby inne psy klienta też zachorowały oraz możliwość wyszukiwania ich wyników (klient jest weterynarzem?).
Rozwiązanie:
Tworzymy klasę PiesXML2.
Tworzymy klasę uruchamiającą przykład: R061.java
Klasy są szczegółowo opisane w tekście tych klas.
Klasa PiesXML2.java
package aderby; import java.beans.*; import java.io.*; import java.util.*; import javax.xml.stream.*; public class PiesXML2 implements Serializable { private static final long serialVersionUID = 1608191363862330368L; private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); private String imie;//imie psa private long czas = 0L;//czas pomiaru private double temp = 0.0d;//zmierzona temperatura private File plik;//plik z danymi pomiaru public PiesXML2() { this("Pies"); } public PiesXML2(String imie) { this.imie = imie; } public synchronized void addPropertyChangeListener( PropertyChangeListener list) { pcs.addPropertyChangeListener(list); } public synchronized void removePropertyChangeListener( PropertyChangeListener list) { pcs.removePropertyChangeListener(list); } public String getImie() { return imie; } public void setImie(String imie) { this.imie = imie; } public long getCzas() { return czas; } public void setCzas(long czas) { this.czas = czas; } public double getTemp() { return temp; } /** * @param temp temperatura * @param fireEvent czy poinformowac o zdarzeniu i zapisać plik (true) * czy tylko zmienić temeraturę(false) */ public synchronized void setTemp(double temp, boolean fireEvent) { if (fireEvent) { double oldValue = this.temp; this.temp = temp; write(); pcs.firePropertyChange("temp zmieniona", oldValue, temp); } else { this.temp = temp; } } /** * Metoda używa StAX do zapisania aktualnego stanu psa do pliku * * @return - zwraca true jeśli zapis się udał, false w przeciwnym przypadku */ public boolean write() { //podaje czas w milisekundach czas = (new GregorianCalendar()).getTimeInMillis(); //tworzy nazwe pliku. Umieszczenie czasu w nazwie //zapobiega powtorzeniu sie nazwy, plik = new File("C:/Przyklady/" + imie + czas + ".xml"); //Zapisanie danych XMLOutputFactory xof = XMLOutputFactory.newInstance(); FileWriter fw; XMLStreamWriter xsw; try { fw = new FileWriter(plik); xsw = xof.createXMLStreamWriter(fw); xsw.writeStartDocument("1.0"); xsw.writeStartElement("pies"); //- xsw.writeStartElement("imie"); xsw.writeCharacters(this.imie); xsw.writeEndElement(); //- xsw.writeStartElement("czas"); xsw.writeCharacters(Long.toString(czas)); xsw.writeEndElement(); //- xsw.writeStartElement("temp"); xsw.writeCharacters(Double.toString(this.temp)); xsw.writeEndElement(); //- xsw.writeEndElement(); xsw.flush(); xsw.close(); } catch (IOException e) { System.out.println("Dane nie zapisane"); return false; } catch (XMLStreamException e) { System.out.println("Błąd w strumieniowaniu pliku"); return false; } //zamkmniecie strumieni try { xsw.close(); } catch (XMLStreamException e) { e.printStackTrace(); } try { fw.close(); } catch (IOException e) { e.printStackTrace(); } return true; } /** * Odczytanie danych z pliku. Ponieważ zapisujemy dane tego samego obiektu * to możemy odczytać tylko ostatni stan. * * @return zwraca obiekt będący ostatnim stanem psa */ public PiesXML2 read() { PiesXML2 pies = null; XMLInputFactory xif = XMLInputFactory.newInstance(); FileReader fr = null; XMLStreamReader xsr = null; try { if (plik.exists()) { fr = new FileReader(plik); xsr = xif.createXMLStreamReader(fr); while (xsr.hasNext()) { int eventType = xsr.getEventType(); switch (eventType) { case XMLStreamConstants.START_ELEMENT: String elName = xsr.getLocalName(); if (elName.equals("imie")) { pies = new PiesXML2(); pies.setImie(xsr.getElementText()); } if (elName.equals("czas")) { Objects.requireNonNull(pies).setCzas( Long.parseLong(xsr.getElementText())); } if (elName.equals("temp")) { Objects.requireNonNull(pies).setTemp(Double.parseDouble( xsr.getElementText()), false); } break; case XMLStreamConstants.END_ELEMENT: elName = xsr.getLocalName(); if (elName.equals("imie")) { return pies; } break; } xsr.next(); } } } catch (IOException e) { System.out.println("Błąd w odczycie pliku"); } catch (XMLStreamException e) { System.out.println("Błąd w strumieniowaniu pliku"); } if (xsr != null) { try { xsr.close(); } catch (XMLStreamException e) { e.printStackTrace(); } } if (fr != null) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } return pies; } /** * Poniewaz dane XML będziemy zapisywali jako Clob, a metoda setClob * przyjmuje jako obiekt nie plik a reader, w związku z tym * ta metoda zwraca stosowny obiekt Reader (do ostatniego pliku ze stanem * psa) * * @return obiekt Reader */ public Reader read2() { FileReader fr = null; try { if (plik.exists()) { fr = new FileReader(plik); } } catch (IOException e) { System.out.println("Błąd w odczycie pliku"); } return fr; } @Override public String toString() { return imie + " " + czas + " " + temp; } }
Klasa R061_XML.java
package aderby.types; import aderby.DerbyUtil; import aderby.PiesXML2; import java.beans.*; import java.sql.*; import java.sql.Statement; @SuppressWarnings("RedundantCast") public class R061_XML { //Polecenie SQl tworzące tabelę 'psy' z dwiema kolumnami: //kolumna 'id' typu Integer, automatycznie inkrementowana //kolumna 'psy' typu XML przechowująca dane XML public static final String createTable = "CREATE TABLE psy(" + "id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS " + "IDENTITY(START WITH 1, INCREMENT BY 1), temps XML)"; public static void main(String[] args) { //'Przygotowujemy' psa PiesXML2 pies = new PiesXML2("Nutka"); pies.addPropertyChangeListener((PropertyChangeListener) evt -> { String zmiana; if (((Double) evt.getOldValue() - (Double) evt.getNewValue()) > 0) { zmiana = "spadek"; } else if (((Double) evt.getOldValue() - (Double) evt.getNewValue()) < 0) { zmiana = "wzrost"; } else { zmiana = "bez zmian"; } System.out.println("Aktualna temperatura: " + evt.getNewValue() + " " + zmiana); }); //startujemy silnik Derby DerbyUtil.startDerbyEngine("EmbeddedDriver"); //Tworzymy połączenie z bazą 'baza_testowa'. Jeśli baza nie istnieje //zostanie utworzona. Jeśli istnieje zostanie użyta. Connection con = DerbyUtil.connectEmbeddedDB("C:/Przyklady/R061", ";create=true"); //Tworzymy polecenie; Statement stat = null; try { //nadajemy mu wartość stat = con.createStatement(); //dodajemy polecenie wsadowe tworzące tabele bazy danych' stat.addBatch(createTable); //wykonujemy polecenia wsadowe stat.executeBatch(); } catch (SQLException e) { e.printStackTrace(); } //WSTAWIANIE DOKUMENTÓW XML DO BAZY //Tworzymy polecenie przygotowywane PreparedStatement stmt = null; //Polecenie SQL wstawiające dane do tabeli 'psy' //'id' jest wstawiane automatycznie //'temps' jest wstawiane ręcznie. Plik XML (DOCUMENT) jest //rzutowany do typu CLOB (Możemy rzutować do innych typów //znakowych, ale CLOB jest najwygodniejszy) z zachowaniem białych //znaków. XMLPARSE jest funkcją (operatorem) wykonującym zadanie //słowa DOCUMENT, CAST, PRESERVE WHITESPACE sa obowiązkowe String insertSQL = "INSERT INTO psy (id, temps) VALUES (DEFAULT, " + "XMLPARSE(DOCUMENT CAST(? AS CLOB) PRESERVE WHITESPACE))"; try { for (int i = 0; i < 10; i++) { //przygotowujemy polecenie stmt = con.prepareStatement(insertSQL); //rejestrujemy zmianę temperatury psa //w zasadzie powinniśmy utworzyć oddzielny wątek i zastosować //klasę Timer do generowania pomiarów temperatury, ale //to skomplikowałoby nadmiernie nasza klasę pies.setTemp(DerbyUtil.randomInRange(30, 40), true); //zapisujemy w bazie danych //moglibyśmy tu użyć polecenia np. //smtp.setClob(1, StringReader(string)), gdzie //string to łańcuch znaków zawierający tagi xml stmt.setClob(1, pies.read2()); //wykonujemy polecenie stmt.executeUpdate(); //usuwamy parametry co przygotowuje 'stmst' do przyjęcia następnych //danych stmt.clearParameters(); } } catch (SQLException e) { e.printStackTrace(); } //ODCZYTYWANIE DOKUMENTÓW XML Z BAZY String readSQL = "SELECT XMLSERIALIZE(temps AS CLOB) FROM psy WHERE id =3"; ResultSet rs = null; String str; try { stat = con.createStatement(); rs = stat.executeQuery(readSQL); while (rs.next()) { str = rs.getString(1); System.out.println(str); } } catch (SQLException e) { e.printStackTrace(); } //WYSZUKIWANIE DOKUMENTU XML W BAZIE według wartości konkretnego //tagu w dokumencie String selectSQL = "SELECT id FROM psy WHERE XMLEXISTS('//imie[text()=\"Nutka\"]' PASSING BY REF temps)"; ResultSet rs1 = null; try { stat = con.createStatement(); rs1 = stat.executeQuery(selectSQL); while (rs1.next()) { System.out.println(rs1.getInt(1)); } } catch (SQLException e) { e.printStackTrace(); } DerbyUtil.close(stat); DerbyUtil.close(stmt); DerbyUtil.close(rs); DerbyUtil.close(rs1); //DerbyUtil.close(pstm); //zamykamy połączenie DerbyUtil.close(con); //zatrzymujemy bazę danych DerbyUtil.shutdownEmbeddedDB("C:/Przyklady/r061"); //zatrzymujemy silnik Derby DerbyUtil.shutdownDerbyEngine(); } }
Po uruchomieniu klasy R061
na konsoli zobaczymy:
Aktualna temperatura: 30.0 wzrost Aktualna temperatura: 33.0 wzrost Aktualna temperatura: 36.0 wzrost Aktualna temperatura: 39.0 wzrost Aktualna temperatura: 34.0 spadek Aktualna temperatura: 40.0 wzrost Aktualna temperatura: 35.0 spadek Aktualna temperatura: 30.0 spadek Aktualna temperatura: 32.0 wzrost <pies><imie>Nutka</imie><czas>1575626122947</czas><temp>36.0</temp></pies> 1 2 3 4 5 6 7 8 9 10
W folderze C:/Przyklady pojawia się baza r061 oraz pliki *.xml z zapisem temperatury.
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.