我将在这里提出一些不同的意见。
JavaFX 属性和 JPA
正如我对jewelsea 的回答所评论的那样,只要您使用“属性访问”而不是“字段访问”,就可以将基于JavaFX 属性的bean 与JPA 结合使用。我在那里链接的blog post 对此进行了更详细的说明,但基本思想是任何注释都应该在get...() 方法上,而不是在字段上。据我所知,这确实阻止了将任何只读 JavaFX 属性模式与 JPA 结合使用,但我从来没有真正觉得 JPA 与只读属性(即 get 方法和无 set 方法)配合得很好.
序列化
与我对 Jewelsea 的回答的评论相反,并且有几个星期的时间来处理这个问题(并且已经被放置在我面临使用 JavaFX 属性在 JavaFX 客户端复制多个实体类的位置),我认为可以解决 JavaFX 属性缺乏序列化的问题。关键的观察是您实际上只需要序列化属性的包装状态(例如,不需要任何侦听器)。您可以通过实现java.io.Externalizable 来做到这一点。 Externalizable是Serializable的子接口,需要填写readExternal(...)和writeExternal(...)方法。可以实现这些方法以仅外部化由属性包装的状态,而不是属性本身。这意味着如果您的实体被序列化然后反序列化,您最终将得到一个新的属性实例,并且不会保留任何侦听器(即侦听器实际上变为transient),但据我所知,这将是在任何合理的用例中都需要什么。
我尝试了以这种方式定义的 bean,并且一切似乎都运行良好。此外,我进行了一个小型实验,在客户端和一个 RESTful Web 服务之间传输它们,使用 Jackson 映射器在 JSON 表示形式之间进行转换。由于映射器仅依赖于使用 get 和 set 方法,因此效果很好。
一些注意事项
需要注意几点。与任何序列化一样,有一个无参数的构造函数很重要。当然,JavaFX 属性包装的所有值本身都必须是可序列化的——这与任何可序列化 bean 的规则相同。
关于通过副作用工作的 JavaFX 属性的观点已经得到很好的理解,并且在将这些属性(在某种程度上,在设计时考虑到单线程模型)移动到潜在的多线程时需要小心谨慎。线程服务器。一个好的经验法则可能是,如果您使用此策略,侦听器应该只在客户端注册(请记住,这些侦听器在传回服务器方面是暂时的,无论是通过序列化还是通过 JSON 表示)。当然,这表明在服务器端使用这些可能是一个糟糕的设计。它成为了一个单一实体的便利性之间的权衡,该实体是“所有人的一切”(JavaFX 客户端的可观察属性,可序列化的持久性和/或远程访问,以及 JPA 的持久性映射)与公开功能(例如可观察性)可能不完全合适的地方(在服务器上)。
最后,如果您确实使用了 JPA 注释,它们具有运行时保留,这意味着(我认为)您的 JavaFX 客户端将需要类路径上的 javax.persistence 规范)。
这是一个这样的“四季皆宜的男人”实体的例子:
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());
}
}