General design approach recommendation
For your problem, I'd be inclined to use a database instead of serialization. There are many to choose from, depending on your needs. For a small embedded database, something like H2 would be a reasonable choice. An example for integrating JavaFX and H2 is provided here.
For persistence you could use straight JDBC or JPA. For a substantial application, JPA will be better. For a small application, JDBC suffices. If you use JPA, you can integrate it with JavaFX property based classes, as defined in the articles linked to Put together JavaFX properties and JPA Entities (NO MIXED MODE) 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.
Define separate tables for each of your entities (users, accounts, recipients, transactions). Assign each entity entry a unique id key. Use relations to link the item references that you have stored in your ObservableLists.
If you want to access the database from remote locations and you can't open up a direct port connection to it, then you will need to provide a service on the server that provides the data (e.g. a REST based server that performs the database access and exposes required data as JSON over HTTP that your JavaFX client accesses via a REST client then processes the REST call responses into client JavaFX property based data structures). Such implementations quickly become a lot of work :-)
Probably I shouldn't have answered this, as the question (or my interpretation of it) is too broad by StackOverflow principles, but hopefully the info here is useful to you.
Specific answer based upon additional info
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
Gotcha, that makes total sense. I have integrated JavaFX with SpringBoot before, but unfortunately I can't publish source for those implementations publicly.
For your demo program, persistence via JAXB or Jackson should suffice. Makery provide a nice example for JAXB based persistence for JavaFX.
The trick with the JAXB based approach is to get something that works with your nested data model.
JAXB based storage approach example
This example is based upon ideas from the 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?.
The key to the solution is this bit of code in the User
class. It provides the account list as a nested ObservableList and provides a standard accessor accounts()
to retrieve the ObservableList
as per JavaFX conventions. It also provides a getAccounts()
and setAccounts()
method that copies in and out of the ObservableList to a standard Java List and notates the getter with a JAXB @Xml...
annotations to enable JAXB to handle the serialization and deserialization of the accounts linked to the users.
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);
}
}
Output
$ 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>