Write/Read 来自 JavaFX 应用程序的嵌套 ObservableList

Write/Read nested ObservableList's from JavaFX Application

我正在编写一个小型 JavaFx 应用程序,其中 Main Class 包含用户的 ObservableArryList。这些用户有 ObservableList 的账户,那些账户有 ObservableList 的交易等等...

这里是 Class-图表:

我想保存并稍后读取应用程序的数据to/fram一个文件。

我已经尝试通过在我的所有 类 中实现 Serializable 接口来保存它,但显然你不能序列化 ObservableList。

我还尝试使用 Gson 将其保存在 Json 文件中,或使用 JAXB 将其保存为 XML 文件,但其中 none 以递归方式存储列表。

所以我的问题是: 有人知道我如何保存当前在我的应用程序中的所有对象,然后再加载它们吗?

编辑: 我实现了 jewelsea 给出的基于 JAXB 的存储方法,现在数据的 saving/loading 工作正常。

一般设计方法建议

对于你的问题,我倾向于使用数据库而不是序列化。有很多可供选择,这取决于您的需要。对于小型嵌入式数据库,此处提供了类似 H2 would be a reasonable choice. An example for integrating JavaFX and H2 的内容。

对于持久性,您可以直接使用 JDBC 或 JPA。对于大量的应用程序,JPA 会更好。对于小型应用程序,JDBC 就足够了。如果您使用 JPA,则可以将其与基于 class 的 JavaFX 属性 集成,如文章 linked 至 and this JavaFX plus JPA example. However, you may wish to keep the JavaFX view model property objects separate and use a DAO pattern for your persistence. Keeping the objects separate gives you a bit more flexibility in your application design and implementation, but violates DRY principles. It's a trade-off though as the resultant objects better respect the single responsibility principle.[=29 中所定义=]

为您的每个实体(用户、帐户、收件人、交易)定义单独的表。为每个实体条目分配一个唯一的 id 键。使用 relations 到 link 您存储在 ObservableLists 中的项目引用。

如果你想从远程位置访问数据库并且你不能打开一个直接的端口连接到它,那么你需要在提供数据的服务器上提供一个服务(例如一个基于 REST 的服务器)执行数据库访问并将所需数据公开为 JSON 通过 HTTP,您的 JavaFX 客户端通过 REST 客户端访问,然后将 REST 调用响应处理到客户端 JavaFX 属性基于数据结构)。这样的实现很快就会变成很多工作:-)

可能我不应该回答这个问题,因为根据 Whosebug 原则,这个问题(或我对它的解释)过于宽泛,但希望这里的信息对您有用。

基于附加信息的具体答案

I actually already have a spring-boot based web application with DAO and Hibernate that is working fine, and this JavaFX App is planned to connect to that web app. I just need this locally saved files as a little "demo" of the program, if there is currently no internet connection available

明白了,这完全有道理。我之前已经将 JavaFX 与 SpringBoot 集成,但遗憾的是我无法公开发布这些实现的源代码。

对于您的演示程序,通过 JAXB 或 Jackson 进行持久化应该就足够了。 Makery 为 JavaFX 提供了一个基于 JAXB 的持久性的很好的例子。

基于 JAXB 的方法的诀窍是获得适用于您的嵌套数据模型的东西。

基于 JAXB 的存储方法示例

此示例基于 Makery JavaFX tutorial. To better understand it, consult the tutorial. The nested observable list persistence is achieved using the concepts from: JAXB: How to marshal objects in lists? 的想法。

解决的关键是Userclass中的这段代码。它以嵌套的 ObservableList 形式提供帐户列表,并提供标准访问器 accounts() 以根据 JavaFX 约定检索 ObservableList。它还提供了一个 getAccounts()setAccounts() 方法,将 ObservableList 复制进出到标准 Java 列表,并用 JAXB @Xml... 注释标记 getter使 JAXB 能够处理 linked 给用户的帐户的序列化和反序列化。

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);
    }
}

输出

$ 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>