Apache Derby: operatory XML
Aby operatory operujące na XML działały Derby musi mieć dostęp do parsera JAXP (biblioteka Apache Xerces) oraz do biblioteki Apache Xalan.
Operatory XML to:
Przykład zbiorczy – 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 lub poszczególnych wyników. Musi istnieć możliwość zapisu dla wielu psów, gdyby inne psy klienta tez zachorowały i stosownego wyszukiwania ich wyników (klient jest weterynarzem?).
Rozwiązanie
Tworzymy klasę PiesXML2:
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; } /** * Ponieważ 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; } }
Tworzymy klasę uruchamiającą R083_XML:
package aderby.functions.opers; import aderby.DerbyUtil; import aderby.PiesXML2; import java.sql.*; public class R083_XML { //Polecenie SQl tworzące tabelę 'psy' z dwiema kolumnami: //kolumna 'id' typu Integer, autoamtycznie 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(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); }); DerbyUtil.startDerbyEngine("EmbeddedDriver"); Connection con = DerbyUtil.connectEmbeddedDB("C:/Przyklady/r083_psy", ";create=true"); Statement stat = null; try { stat = con.createStatement(); stat.addBatch(createTable); stat.executeBatch(); } catch (SQLException e) { e.printStackTrace(); } //WSTAWIANIE DOKUMENTÓW XML DO BAZY //Tworzymy polecenie przygotowywane PreparedStatement stmt = null; //Polecenie SQL wstawiająace dane do tabeli 'psy' //'id' jest wstawiane automatycznie //'temps' jest wstawiane ręcznie. Plik XML (DOCUMENT) jest //rzutowany do typu CLOB (Mozemy rzutować do innych typow //znakowych, ale CLOB jest najwygodniejszy) z zachowaniem białych //znaków. XMLPARSE jest funkcją (operatorem) wykonującym zadanie //słowa DOCUMENT, CAST, PRESERVE WHITESPACE są 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 zmiane temperatury psa //w zasadzie powinnismy utworzyc oddzielny watek i zastosowac //klase Timer do generowania pomiarow temperatury, ale //to skomplikowaloby nadmiernie nasza klase pies.setTemp(DerbyUtil.randomInRange(30, 40), true); //zapisujemy w bazie danych //moglibysmy tu uzyc poleenia np. //smtp.setClob(1, StringReader(string)), gdzie //string to lancuch znakow zawierajacy tagi xml stmt.setClob(1, pies.read2()); //wykonujemy polecenie stmt.executeUpdate(); //usuwamy parametry co przygotowuje 'stmst do przyjecia nastepnych //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); //zamykamy polaczenie DerbyUtil.close(con); //zatrzymujemy bazę danych DerbyUtil.shutdownEmbeddedDB("C:/Przyklady/r083_psy"); //zatrzymujemy silnik Derby DerbyUtil.shutdownDerbyEngine(); } }
Klasy są szczegółowo opisane w tekście klas
Wynik
Aktualna temperatura: 35.0 wzrost Aktualna temperatura: 36.0 wzrost Aktualna temperatura: 33.0 spadek Aktualna temperatura: 35.0 wzrost Aktualna temperatura: 34.0 spadek Aktualna temperatura: 39.0 wzrost Aktualna temperatura: 33.0 spadek Aktualna temperatura: 40.0 wzrost Aktualna temperatura: 30.0 spadek Aktualna temperatura: 40.0 wzrost <pies><imie>Nutka</imie><czas>1494927660613</czas><temp>33.0</temp></pies> 1 2 3 4 5 6 7 8 9 10
W folderze C:/Przyklady pojawiają się niezbędne pliki.
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.