12. Oktober 2015
Denny Israel
0

JavaFX Persistence – Teil 1 (JAXB)

Bei der Nutzung von JavaFX als Frontendtechnologie gewinnt man viele Vorteile durch die Nutzung der JavaFX Properties. Änderungen an Daten können einfach per Binding an GUI Elemente übertragen und dort korrekt dargestellt werden. Eine explizite Handhabung der Änderungen (Change Propagation) ist nicht nötig, bzw. wird durch JavaFX übernommen.

Voraussetzung dafür ist jedoch der Einsatz von JavaFX Properties bis runter zum Model, sodass eine Kette von Bindings bis in die GUI aufgebaut werden kann.

View-ViewModel-Model

Abbildung 1 Binding zwischen View und Model für automatische Change Propagation

Ein Problem stellt dabei das Speichern und Laden der Daten dar. Die üblicherweise verwendeten Persistenzframeworks (JAXB, JPA) tun sich auf den ersten Blick schwer damit JavaFX Properties zu verarbeiten. Dies muss jedoch keinen Widerspruch darstellen. Im Folgenden ersten Teil möchte ich auf das Speichern und Laden von Modeldaten mit Hilfe von JAXB eingehen. Die Modeldaten sind dabei mit JavaFX Properties angereichert und an die GUI gebunden.

JAXB kann zwar nicht mit Properties umgehen, muss es aber auch nicht. Schon die Empfehlung für Getter/Setter von Properties bietet einen Ausweg. Für eine StringProperty (myValue) beispielsweise wird empfohlen, folgende 3 Methoden anzugeben:

private final StringProperty myValue= new SimpleStringProperty();	

public final StringProperty myValueProperty() {
 return this.myValue;
}

public final String getMyValue() {
 return myValue.get();
}

public final void setMyValue(final String myValue) {
 this.myValue.set(myValue);
}

Diese bieten uns alles Nötige um JAXB zu „überreden“ diese Daten doch zu speichern und zu laden. Hierzu ist es jedoch nötig auf Feldzugriff zu verzichten und den Zugriffstyp auf PROPERTY umzustellen:

@XmlAccessorType(XmlAccessType.PROPERTY)

Hinzu kommt, dass alle gewünschten Zugriffe markiert werden. Hierzu wird der Getter wie folgt markiert:

@XmlAttribute

oder

@XmlElement

Dies versetzt JAXB in die Lage auf die Daten zuzugreifen, diese zu speichern und auch wieder zu laden, indem JAXB angewiesen wird die Setter/Getter zu benutzen. Die Namen der Methoden sind gemäß Beansspezifikation anzugeben (getMyValue(), setMyValue(…)).

An einem Beispiel soll das Vorgehen verdeutlicht werden:

In unserer kleinen Beispielapplikation sieht der Nutzer eine Liste von Fahrzeugmodellen. Er kann neue Modelle zur Liste hinzufügen und das jeweils erste Element löschen. Zusätzlich kann er die Liste als XML Dokument speichern und laden. Der Einfachheit halber wurde auf das Editieren der Modelle und das freie wählen des Speicherortes verzichtet.

Die Modellklassen umfassen die Klasse Car, sowie die Klasse CarList:

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "cars")
public class CarList {
 ListProperty<Car> cars = new SimpleListProperty<>(FXCollections.observableArrayList());
 public ListProperty<Car> carsProperty() {
  return this.cars;
 }
 @XmlElement(name = "car")
 public ObservableList<Car> getCars() {
  return cars.get();
 }
 public void setCars(final ObservableList<Car> cars) {
  this.cars.set(cars);
 }
}

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "car")
public class Car {
 private final StringProperty manufacturer = new SimpleStringProperty();
 private final StringProperty model = new SimpleStringProperty();

 public final StringProperty manufacturerProperty() {
  return this.manufacturer;
 }
 @XmlAttribute
 public final String getManufacturer() {
  return this.manufacturerProperty().get();
 }
 public final void setManufacturer(final String manufacturer) {
  this.manufacturerProperty().set(manufacturer);
 }
 public final StringProperty modelProperty() {
  return this.model;
 }
 @XmlAttribute
 public final String getModel() {
  return this.modelProperty().get();
 }
 public final void setModel(final String model) {
  this.modelProperty().set(model);
 }
}

Car hat die Eigenschaften Model und Manufacturer, welche in der GUI angezeigt werden und gespeichert bzw. geladen werden können. Welche Modelle angezeigt werden, definiert die ListProperty „cars“ in CarList. Da jegliche Modeldaten als Properties ausgelegt sind, kann die GUI (in unserem Fall eine ListView) daran gebunden werden und zeigt immer die aktuellen Daten an. Selbst Änderungen der gesamten Liste – weil z.B. die Instanz von CarList komplett durch JAXB geladen wurde – können korrekt abgebildet werden.

Die aktuelle Instanz von CarList wird in einer ObjectProperty gehalten, um auf Lade- und Speicheroperationen reagieren zu können. Folgender Code zeigt die Property und das Binden an die GUI:

public class CarView { //der Einfachheit halber kein ViewModel
 …
 final ObjectProperty<CarList> cars = new SimpleObjectProperty<>(new CarList());
 @FXML
 private ListView<Car> lvCars;

 @FXML
 public void initialize() {
  …
  lvCars.itemsProperty().bind(cars.get().carsProperty());
  cars.addListener((obs, oldVal, newVal) -> {
   if (newVal != null) {
    lvCars.itemsProperty().bind(cars.get().carsProperty());
   }
  });
 }
 …
}

Das Laden und Speichern kann nun einfach durch JAXB übernommen werden:

public class CarView { //der Einfachheit halber kein ViewModel
 
 final ObjectProperty<CarList> cars = new SimpleObjectProperty<>(new CarList());
 …

 @FXML
 void loadCars(final ActionEvent event) {
  try {
   final Unmarshaller unmarshaller = 
      JAXBContext.newInstance(CarList.class).createUnmarshaller();
   cars.set((CarList) unmarshaller.unmarshal(new File("cars.xml")));
  } catch (final JAXBException e) {
   e.printStackTrace();
  }
 }

 @FXML
 void saveCars(final ActionEvent event) {
  try {
   final Marshaller marshaller = 
      JAXBContext.newInstance(CarList.class).createMarshaller();
   marshaller.marshal(cars.get(), new File("cars.xml"));
  } catch (final JAXBException e) {
   e.printStackTrace();
  }
 }
}

Das vollständige Beispiel liegt auf Github: https://github.com/sideisra/javafx-persistence/tree/jaxb.

Der zweite Teil der Serie beschäftigt sich mit der Persistenz durch JPA.

Denny ist Senior Consultant für Softwareentwicklung im Bereich Java und JavaFX. Er beschäftigt sich vornehmlich mit allen Aspekten der Entwicklung von JavaFX Anwendungen, von der Oberfläche über die Architektur bis hin zum Backend und der Anbindung an andere Systeme. Darüber hinaus interessiert er sich für moderne Web-Technologien wie React und Angular 2.

Twitter Xing 

TeilenTweet about this on TwitterShare on Facebook0Share on Google+0Share on LinkedIn0