2016-12-08 2 views
2

Je suis en train d'écrire une petite application JavaFx, où la classe Main contient un ObservableArryList d'utilisateurs. Ces utilisateurs ont ObservableList de de comptes et les comptes ont ObservableList de des transactions et ainsi de suite ...Écrire/Lire les ObservableList imbriqués de l'application JavaFX

Voici la classe Schéma: Class-Diagram

Je voudrais enregistrer et lire plus tard, les données de l'application/fram un fichier.

Je tente déjà de l'enregistrer en implémentant l'interface Serializable dans toutes mes classes, mais apparemment, vous ne pouvez pas sérialiser une liste ObservableList. J'ai également essayé de l'enregistrer dans un fichier Json en utilisant Gson, ou en tant que fichier XML avec JAXB, mais aucun d'entre eux n'a stocké les listes récursivement. Donc, ma question est: Est-ce que quelqu'un sait comment je pourrais enregistrer tous les objets qui sont actuellement dans mon application, puis les charger à nouveau?

EDIT: I mis en œuvre l'approche de stockage à base JAXB donnée par jewelsea et la sauvegarde/chargement des données fonctionne maintenant parfaitement.

Répondre

2

recommandation approche de conception générale

Pour votre problème, je serais enclin à utiliser une base de données au lieu de sérialisation. Il y a beaucoup de choix, selon vos besoins. Pour une petite base de données intégrée, quelque chose comme H2 serait un choix raisonnable. Un exemple d'intégration de JavaFX and H2 est fourni ici.

Pour la persistance, vous pouvez utiliser JDBC ou JPA. Pour une application substantielle, l'APP sera meilleure. Pour une petite application, JDBC suffit. Si vous utilisez JPA, vous pouvez l'intégrer à des classes basées sur des propriétés JavaFX, comme défini dans les articles liés à Put together JavaFX properties and JPA Entities (NO MIXED MODE) et JavaFX plus JPA example. Toutefois, vous souhaiterez peut-être conserver les objets de propriété du modèle d'affichage JavaFX séparés et utiliser un DAO pattern pour votre persistance. Garder les objets séparés vous donne un peu plus de flexibilité dans la conception et l'implémentation de votre application, mais viole les principes DRY. C'est un compromis cependant que les objets résultants respectent mieux le single responsibility principle.

Définissez des tables distinctes pour chacune de vos entités (utilisateurs, comptes, destinataires, transactions). Attribuez à chaque entrée d'entité une clé d'identification unique. Utilisez relations pour lier les références d'éléments que vous avez stockées dans vos ObservableLists. Si vous souhaitez accéder à la base de données à partir d'emplacements distants et que vous ne pouvez pas lui ouvrir de connexion directe, vous devez fournir un service sur le serveur fournissant les données (par exemple, un serveur basé sur REST). effectue l'accès à la base de données et expose les données requises au format JSON sur HTTP auxquelles votre client JavaFX accède via un client REST puis traite les réponses d'appel REST dans les structures de données basées sur les propriétés client JavaFX). De telles implémentations deviennent rapidement beaucoup de travail :-)

Probablement je n'aurais pas dû répondre à cela, car la question (ou mon interprétation de celle-ci) est trop large par les principes de StackOverflow, mais j'espère que l'information ici est utile pour vous .

réponse spécifique basée sur des informations supplémentaires

J'ai en fait déjà une application web basée au printemps démarrage avec DAO et Hibernate qui fonctionne très bien, et cette application JavaFX est prévu de se connecter à cette application Web . J'ai juste besoin de ces fichiers sauvés localement comme une petite "démo" du programme, s'il n'y a actuellement aucune connexion Internet disponible

Gotcha, cela prend tout son sens. J'ai intégré JavaFX avec SpringBoot avant, mais malheureusement, je ne peux pas publier de source pour ces implémentations publiquement.

Pour votre programme de démonstration, la persistance via JAXB ou Jackson devrait suffire. Makery fournit un bon exemple de persistance basée sur JAXB pour JavaFX.

L'astuce avec l'approche basée sur JAXB est d'obtenir quelque chose qui fonctionne avec votre modèle de données imbriqué.

approche de stockage à base JAXB exemple

Cet exemple est basé sur les idées du Makery JavaFX tutorial. Pour mieux le comprendre, consultez le tutoriel. La persistance de liste observable imbriquée est obtenue en utilisant les concepts de: JAXB: How to marshal objects in lists?.

La clé de la solution est ce bit de code dans la classe User. Il fournit la liste des comptes sous forme d'ObservableList imbriqué et fournit un accesseur standard accounts() pour récupérer les ObservableList conformément aux conventions JavaFX. Il fournit également une méthode getAccounts() et setAccounts() qui copie dans et hors de ObservableList vers une liste Java standard et notifie le getter avec des annotations JAXB @Xml... pour permettre à JAXB de gérer la sérialisation et la désérialisation des comptes liés aux utilisateurs.

private final ObservableList<Account> accounts = FXCollections.observableArrayList(); 

public ObservableList<Account> accounts() { return accounts; } 

@XmlElementWrapper(name="accounts") 
@XmlElement(name = "account") 
public List<Account> getAccounts() { 
    return new ArrayList<>(accounts); 
} 

public void setAccounts(List<Account> accounts) { 
    this.accounts.setAll(accounts); 
} 

UserAccountPersistence.java

import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 

import javax.xml.bind.JAXBContext; 
import javax.xml.bind.JAXBException; 
import javax.xml.bind.Marshaller; 
import javax.xml.bind.Unmarshaller; 
import java.io.File; 
import java.io.IOException; 
import java.nio.file.Files; 
import java.util.prefs.Preferences; 
import java.util.stream.Collectors; 

public class UserAccountPersistence { 

    private ObservableList<User> users = FXCollections.observableArrayList(); 

    public UserAccountPersistence() throws JAXBException, IOException { 
     File dbFile = getDatabaseFilePath(); 
     if (dbFile == null) { 
      setDatabaseFilePath(new File(System.getProperty("user.home") + "/" + "user-account.xml")); 
      dbFile = getDatabaseFilePath(); 
     } 

     if (!dbFile.exists()) { 
      createTestData(); 
      saveData(dbFile); 
     } else { 
      loadData(dbFile); 
     } 

     System.out.println("Persisted Data: "); 
     System.out.println(
       Files.lines(dbFile.toPath()) 
         .collect(Collectors.joining("\n")) 
     ); 
     System.out.println("Database File: " + dbFile); 
    } 

    private void createTestData() { 
     users.add(new User("Hans", "Muster")); 
     users.add(new User("Ruth", "Mueller")); 
     users.add(new User("Heinz", "Kurz")); 

     users.get(0).accounts().addAll(
       new Account(10), 
       new Account(20) 
     ); 

     users.get(2).accounts().addAll(
       new Account(15) 
     ); 
    } 

    public File getDatabaseFilePath() { 
     Preferences prefs = Preferences.userNodeForPackage(UserAccountPersistence.class); 
     String filePath = prefs.get("filePath", null); 
     if (filePath != null) { 
      return new File(filePath); 
     } else { 
      return null; 
     } 
    } 

    public void setDatabaseFilePath(File file) { 
     Preferences prefs = Preferences.userNodeForPackage(UserAccountPersistence.class); 
     if (file != null) { 
      prefs.put("filePath", file.getPath()); 
     } else { 
      prefs.remove("filePath"); 
     } 
    } 

    public void loadData(File file) throws JAXBException { 
     JAXBContext context = JAXBContext 
       .newInstance(UserListWrapper.class); 
     Unmarshaller um = context.createUnmarshaller(); 

     UserListWrapper wrapper = (UserListWrapper) um.unmarshal(file); 

     users.clear(); 
     users.addAll(wrapper.getPersons()); 

     setDatabaseFilePath(file); 
    } 

    public void saveData(File file) throws JAXBException { 
     JAXBContext context = JAXBContext 
       .newInstance(UserListWrapper.class); 
     Marshaller m = context.createMarshaller(); 
     m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 

     UserListWrapper wrapper = new UserListWrapper(); 
     wrapper.setPersons(users); 

     m.marshal(wrapper, file); 

     setDatabaseFilePath(file); 
    } 

    public static void main(String[] args) throws JAXBException, IOException { 
     UserAccountPersistence userAccountPersistence = new UserAccountPersistence(); 
    } 
} 

UserListWrapper.java

import java.util.List; 

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

@XmlRootElement(name = "users") 
public class UserListWrapper { 

    private List<User> persons; 

    @XmlElement(name = "user") 
    public List<User> getPersons() { 
     return persons; 
    } 

    public void setPersons(List<User> persons) { 
     this.persons = persons; 
    } 
} 

User.java

import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 
import javafx.collections.FXCollections; 
import javafx.collections.ObservableList; 

import javax.xml.bind.annotation.XmlElement; 
import javax.xml.bind.annotation.XmlElementWrapper; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.UUID; 

public class User { 
    private final StringProperty id; 
    private final StringProperty firstName; 
    private final StringProperty lastName; 

    private final ObservableList<Account> accounts = FXCollections.observableArrayList(); 

    public User() { 
     this(UUID.randomUUID().toString(), null, null); 
    } 

    public User(String firstName, String lastName) { 
     this(UUID.randomUUID().toString(), firstName, lastName); 
    } 

    public User(String id, String firstName, String lastName) { 
     this.id = new SimpleStringProperty(id); 
     this.firstName = new SimpleStringProperty(firstName); 
     this.lastName = new SimpleStringProperty(lastName); 
    } 

    public String getId() { 
     return id.get(); 
    } 

    public void setId(String id) { 
     this.id.set(id); 
    } 

    public StringProperty idProperty() { 
     return id; 
    } 

    public String getFirstName() { 
     return firstName.get(); 
    } 

    public void setFirstName(String firstName) { 
     this.firstName.set(firstName); 
    } 

    public StringProperty firstNameProperty() { 
     return firstName; 
    } 

    public String getLastName() { 
     return lastName.get(); 
    } 

    public void setLastName(String lastName) { 
     this.lastName.set(lastName); 
    } 

    public StringProperty lastNameProperty() { 
     return lastName; 
    } 

    public ObservableList<Account> accounts() { return accounts; } 

    @XmlElementWrapper(name="accounts") 
    @XmlElement(name = "account") 
    public List<Account> getAccounts() { 
     return new ArrayList<>(accounts); 
    } 

    public void setAccounts(List<Account> accounts) { 
     this.accounts.setAll(accounts); 
    } 

} 

Account.java

import javafx.beans.property.IntegerProperty; 
import javafx.beans.property.SimpleIntegerProperty; 
import javafx.beans.property.SimpleStringProperty; 
import javafx.beans.property.StringProperty; 

import java.util.UUID; 

public class Account { 
    private final StringProperty id; 
    private final IntegerProperty balance; 

    public Account() { 
     this(UUID.randomUUID().toString(), 0); 
    } 

    public Account(int balance) { 
     this(UUID.randomUUID().toString(), balance); 
    } 

    public Account(String id, int balance) { 
     this.id = new SimpleStringProperty(id); 
     this.balance = new SimpleIntegerProperty(balance); 
    } 

    public String getId() { 
     return id.get(); 
    } 

    public void setId(String id) { 
     this.id.set(id); 
    } 

    public StringProperty idProperty() { 
     return id; 
    } 

    public int getBalance() { 
     return balance.get(); 
    } 

    public IntegerProperty balanceProperty() { 
     return balance; 
    } 

    public void setBalance(int balance) { 
     this.balance.set(balance); 
    } 
} 

Sortie

$ cat /Users/jewelsea/user-account.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<users> 
    <user> 
     <accounts> 
      <account> 
       <balance>10</balance> 
       <id>a17b8244-5d3a-4fb4-a992-da26f4e14917</id> 
      </account> 
      <account> 
       <balance>20</balance> 
       <id>f0b23df5-3cc0-418c-9840-633bc0f0b3ca</id> 
      </account> 
     </accounts> 
     <firstName>Hans</firstName> 
     <id>078dad74-ea9d-407d-9be5-d36c52c53b0d</id> 
     <lastName>Muster</lastName> 
    </user> 
    <user> 
     <accounts/> 
     <firstName>Ruth</firstName> 
     <id>78513f1b-75ee-4ca9-a6f0-444f517e3377</id> 
     <lastName>Mueller</lastName> 
    </user> 
    <user> 
     <accounts> 
      <account> 
       <balance>15</balance> 
       <id>77c4fd3c-5f7a-46cf-a806-da1e6f93baab</id> 
      </account> 
     </accounts> 
     <firstName>Heinz</firstName> 
     <id>651d9206-42a5-4b76-b89e-be46dce8df74</id> 
     <lastName>Kurz</lastName> 
    </user> 
</users> 
+0

Merci beaucoup pour votre réponse rapide! En fait, j'ai déjà une application web basée sur le démarrage au printemps avec DAO et Hibernate qui fonctionne bien, et cette application JavaFX est prévue pour se connecter à cette application web. J'ai juste besoin de ces fichiers sauvés localement comme une petite "démo" du programme, s'il n'y a actuellement aucune connexion Internet disponible.Je vais certainement vérifier les exemples de H2 que vous avez fournis! – UniqUnicorn

+0

J'ai mis à jour la question avec le nouvel exemple JAXB que vous avez fourni! – UniqUnicorn

+0

@UniqUnicorn Je ne sais pas pourquoi le unmarshalling ne fonctionne pas pour votre version du code. J'ai revérifié le code dans ma réponse et unmarshalling travaille pour le code que j'ai fourni. Je ne connais pas la raison de la différence de comportement que vous éprouvez pour votre mise en œuvre. – jewelsea