Zielony Smok - logo witryny

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.