
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.
