Zielony Smok - logo witryny

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.