Whether or not you should use a particular technique is very opinion-based, so I won't answer your exact question here. I will just offer some options with pros and cons.
The advantage of using JavaFX properties directly in your JPA-annotated entity class is that you keep the design simple, with just one class representing each entity, instead of wrapper classes around the entity-annotated classes.
The disadvantages to using JavaFX properties directly in the JPA entity classes are:
- Potential performance cost. JavaFX properties are "heavier" than the plain data type they represent, and there is some cost in creating them and presumably in creating containers for their listeners, etc. This can be minimized (or perhaps eliminated) at the cost of some verbosity, see below.
- Adding a dependency on the JavaFX API. While JavaFX ships with the standard JDK from Oracle, it is not a required part of the JSE spec, and some implementations (e.g. OpenJDK) do not include it. This is probably not much of a problem in practice, but you should be aware of it.
You can minimize the cost of using JavFX properties by using the "super lazy" pattern. Here the property objects are only created if they are actually used (e.g. if you register a listener with them); otherwise a surrogate field with the same datatype is used:
@Entity
@Access(AccessType.PROPERTY)
public class Person {
private IntegerProperty age ;
private int _age ;
private StringProperty name ;
private String _name ;
private int id ;
@Id
public int getId() {
return id ;
}
public void setId(int id) {
this.id = id ;
}
public IntegerProperty ageProperty() {
if (age == null) {
age = new SimpleIntegerProperty(_age);
}
return age ;
}
public int getAge() {
if (age == null) {
return _age ;
} else {
return age.get();
}
}
public void setAge(int age) {
if (this.age == null) {
_age = age ;
} else {
this.age.set(age);
}
}
public StringProperty nameProperty() {
if (name == null) {
name = new SimpleStringProperty(_name);
}
return name ;
}
public String getName() {
if (name == null) {
return _name ;
} else {
return name.get();
}
}
public void setName(String name) {
if (this.name == null) {
_name = name ;
} else {
this.name.set(name);
}
}
}
This basically avoids (almost) any performance overhead due to using this class in a non-JavaFX environment, because the properties are not instantiated unless explicitly requested via the xxxProperty()
methods. Note that calling these methods is the only way to register listeners, so if listeners are registered, the code guarantees to notify those listeners if setXxx(...)
is subsequently invoked. The cost here is some code verbosity and the very minor cost of some redundant null checking (which the JVM will probably optimize for anyway).
This technique obviously doesn't get around the issue of dependency on the JavaFX API.
Another possible option is to use a plain JavaBean with a property change listener:
@Entity
public class Person {
@Id
private int id ;
private int age ;
private String name ;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public int getAge() {
return age ;
}
public void setAge(int age) {
int oldAge = age ;
this.age = age ;
pcs.firePropertyChange("age", oldAge, age);
}
public String getName() {
return name ;
}
public void setName(String name) {
String oldName = name ;
this.name = name ;
pcs.firePropertyChange("name", oldName, name);
}
// ...
}
Now in your JavaFX Client you can do things like:
TableView<Person> contactTable = new TableView<>();
TableColumn<Person, String> nameCol = new TableView<>("Name");
nameCol.setCellValueFactory(cellData -> {
try {
return JavaBeanStringPropertyBuilder.create()
.bean(cellData.getValue())
.name("name")
.build();
} catch (Exception exc) {
return new RuntimeException(exc);
}
});
The benefits here are that your entity is completely devoid of any dependency on JavaFX. The cost is that your client code is both more verbose and lacks compile-time checking for the existence of the correct method. There is a fair amount of reflection under the hood here, so you will take some (probably minor) performance hit in the evaluation of the properties in the client code. See JavaBean wrapping with JavaFX Properties for more details on this.
I guess one other comment may be pertinent. The most common use case in which you might want to use an entity both in and out of a JavaFX context is where you have a both a web application and a stand-alone (JavaFX) client application which both access data via a web service (e.g. providing and consuming JSON). In this case you might consider keeping two sets of classes in parallel, one using JavaFX properties and one implemented as a Java bean. Since the collection of get/set
methods is identical, the JSON serializer should be able to convert the same JSON representation to either form. You can keep both forms synchronized using an interface:
public interface Person {
public int getAge() ;
public void setAge(int age) ;
public String getName() ;
public void setName(String name) ;
}
With the obvious implementations
@Entity
public class PersonEntity implements Person {
private int age ;
private String name ;
@Id
private int id ;
// get/set methods omitted...
}
and
public class PersonFX implements Person {
private final StringProperty name = new SimpleStringProperty() ;
private final IntegerProperty age = new SimpleIntegerProperty() ;
public StringProperty nameProperty() {
return name ;
}
@Override
public final String getName() {
return nameProperty().get();
}
@Override
public final void setName(String name) {
nameProperty().set(name);
}
// similarly for age...
}
Now in the JavaFX client you can have a JSON engine that [de]serializes JSON to and from PersonFX
instances, and on the server you have a JSON engine that [de]serializes the same JSON data to and from PersonEntity
instances. Since the JSON engine will just work via calls to get/set methods, the objects essentially have the same form from its perspective. I haven't worked with serializing to/from XML data since I started working with JavaFX, so I don't know for certain the same approach would work with XML data, but I assume you could make that work too. You could even do this with Java serialized streams, by defining readObject
and writeObject
methods in your implementation classes, that expected the same form of data (or by using a Serialization proxy).