您是否应该使用特定技术是非常基于意见的,所以我不会在这里回答您的确切问题。我将提供一些有利和不利的选择。
直接在带注释的 JPA 实体类中使用 JavaFX 属性的优势在于,您可以保持设计简单,每个实体只有一个类,而不是围绕实体带注释的类的包装类。
直接在 JPA 实体类中使用 JavaFX 属性的缺点是:
- 潜在的性能成本。 JavaFX 属性比它们所代表的普通数据类型“更重”,并且在创建它们以及可能为它们的侦听器创建容器等方面存在一些成本。这可以以一些冗长为代价来最小化(或可能消除),请参阅下面。
- 添加对 JavaFX API 的依赖。虽然 JavaFX 附带 Oracle 的标准 JDK,但它不是 JSE 规范的必需部分,并且某些实现(例如 OpenJDK)不包括它。这在实践中可能不是什么大问题,但您应该意识到这一点。
您可以通过使用“超级懒惰”模式将使用 JavFX 属性的成本降至最低。这里的属性对象只有在它们被实际使用时才会被创建(例如,如果你向它们注册了一个监听器);否则使用具有相同数据类型的代理字段:
@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);
}
}
}
这基本上避免了(几乎)由于在非 JavaFX 环境中使用此类而导致的任何性能开销,因为除非通过 xxxProperty() 方法明确请求,否则不会实例化属性。请注意,调用这些方法是注册侦听器的唯一方法,因此如果注册了侦听器,则代码保证在随后调用setXxx(...) 时通知这些侦听器。这里的代价是一些代码冗长和一些冗余空检查的非常小的代价(JVM 可能无论如何都会优化)。
这种技术显然无法解决对 JavaFX API 的依赖问题。
另一个可能的选择是使用带有属性更改侦听器的普通 JavaBean:
@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);
}
// ...
}
现在在您的 JavaFX 客户端中,您可以执行以下操作:
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);
}
});
这里的好处是您的实体完全没有对 JavaFX 的任何依赖。代价是您的客户端代码更加冗长,并且缺少编译时检查是否存在正确方法。这里有相当多的反射,因此在评估客户端代码中的属性时,您会受到一些(可能很小的)性能影响。请参阅JavaBean wrapping with JavaFX Properties 了解更多详情。
我想另一条评论可能是相关的。您可能希望在 JavaFX 上下文内外使用实体的最常见用例是,您同时拥有一个 Web 应用程序和一个独立 (JavaFX) 客户端应用程序,它们都通过 Web 服务访问数据 (例如提供和使用 JSON)。在这种情况下,您可能会考虑并行保留两组类,一组使用 JavaFX 属性,另一组作为 Java bean 实现。由于get/set 方法的集合是相同的,因此 JSON 序列化程序应该能够将相同的 JSON 表示转换为任何一种形式。您可以使用界面保持两个表单同步:
public interface Person {
public int getAge() ;
public void setAge(int age) ;
public String getName() ;
public void setName(String name) ;
}
有了明显的实现
@Entity
public class PersonEntity implements Person {
private int age ;
private String name ;
@Id
private int id ;
// get/set methods omitted...
}
和
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...
}
现在,在 JavaFX 客户端中,您可以拥有一个 JSON 引擎,该引擎可以将 JSON 序列化到和来自PersonFX 实例,而在服务器上,您可以拥有一个 JSON 引擎,该引擎可以[反]序列化 相同 往返于PersonEntity 实例的 JSON 数据。由于 JSON 引擎将仅通过调用 get/set 方法来工作,因此从其角度来看,对象本质上具有相同的形式。自从我开始使用 JavaFX 以来,我没有使用过序列化 XML 数据/从 XML 数据序列化,所以我不确定相同的方法是否适用于 XML 数据,但我认为你也可以做到这一点。您甚至可以通过在实现类中定义 readObject 和 writeObject 方法来使用 Java 序列化流来实现这一点,这需要相同形式的数据(或使用序列化代理)。