Using javafx.beans properties in model classes

I’m going to offer something of a dissenting opinion here.

JavaFX Properties and JPA

As I commented on jewelsea’s answer, using a JavaFX property-based bean with JPA is possible as long as you use “property access” rather than “field access”. The blog post I linked there goes into more detail on this, but the basic idea is that any annotations should be on the get...() methods and not on the fields. As far as I can see, this does prevent use of any of the read-only JavaFX property patterns in conjunction with JPA, but I’ve never really felt JPA played well with read only properties (i.e. get methods and no set method) anyway.

Serialization

Contrary to my comment on jewelsea’s answer, and with the benefit of a few weeks to work with this (and having been placed in a position where I was facing replicating several entity classes on a JavaFX client side using JavaFX properties), I think the lack of serialization of JavaFX properties can be worked around. The key observation is that you really only need to serialize the wrapped state of the property (not, for example, any listeners). You can do this by implementing java.io.Externalizable. Externalizable is a sub-interface of Serializable that requires you to fill in the readExternal(...) and writeExternal(...) methods. These methods can be implemented to externalize just the state wrapped by the property, rather than the property itself. This means that if your entity is serialized and then deserialized, you will end up with a new property instance, and any listeners will not be preserved (i.e. the listeners effectively become transient), but as far as I can see this would be what was wanted in any reasonable use case.

I experimented with beans defined this way and it all seems to work nicely. Additionally, I ran a small experiment in transferring them between the client and a restful web service, using the Jackson mapper to convert to and from a JSON representation. Since the mapper just relies on using the get and set methods, this works just fine.

Some caveats

A couple of points need to be observed. As with any Serialization, it’s important to have a no-argument constructor. And of course, all values wrapped by the JavaFX properties must themselves be serializable – again this is the same rule as for any serializable bean.

The point about JavaFX properties working via side-effects is well taken, and care needs to be exercised when moving these properties (which are, to some extent, designed with a single-threaded model in mind) to a potentially multi-threaded server. A good rule of thumb is probably that if you use this strategy, listeners should only be registered on the client side (and remember, those listeners are transient with respect to transfer back to the server, whether by serialization or by JSON representation). Of course, that suggests that using these on the server side then might be a bad design; it becomes a trade-off between the convenience of having a single entity that is “all things to all people” (observable properties for the JavaFX client, serializable for persistence and/or remote access, and with persistent mappings for JPA) versus exposing functionality (e.g. observability) where it might not be fully appropriate (on the server).

Finally, if you do use the JPA annotations, those have runtime retention which implies (I think) that your JavaFX client will need the a javax.persistence specification on the classpath).

Here’s an example of such a “man for all seasons” entity:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.time.MonthDay;

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

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * Entity implementation class for Entity: Person
 *
 */
@Entity

public class Person implements Externalizable {


    private static final long serialVersionUID = 1L;

    public Person() {

    }

    public Person(String name, MonthDay birthday) {
        setName(name);
        setBirthday(birthday);
    }

    private final IntegerProperty id = new SimpleIntegerProperty(this, "id");

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getId() {
        return id.get();
    }

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

    public IntegerProperty idProperty() {
        return id ;
    }

    private final StringProperty name = new SimpleStringProperty(this, "name");

    // redundant, but here to indicate that annotations must be on the property accessors:
    @Column(name="name")
    public final String getName() {
        return name.get();
    }

    public final void setName(String name) {
        this.name.set(name);
    }

    public StringProperty nameProperty() {
        return name ;
    }

    private final ObjectProperty<MonthDay> birthday = new SimpleObjectProperty<>();

    public final MonthDay getBirthday() {
        return birthday.get();
    }

    public final void setBirthday(MonthDay birthday) {
        this.birthday.set(birthday);
    }

    public ObjectProperty<MonthDay> birthdayProperty() {
        return birthday ;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(getId());
        out.writeObject(getName());
        out.writeObject(getBirthday());
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        setId(in.readInt());
        setName((String) in.readObject());
        setBirthday((MonthDay) in.readObject());
    }

}

Leave a Comment