在这里我想提出一些不同意见。
JavaFX 属性和 JPA
正如我对 Jewelsea 的答案的评论,只要您使用“属性访问”而不是“字段访问”,就可以将基于 JavaFX 属性的 bean 与 JPA 一起使用。这博客文章我链接那里对此进行了更详细的介绍,但基本思想是任何注释都应该在get...()
方法而不是在田野上。据我所知,这确实阻止了将任何只读 JavaFX 属性模式与 JPA 结合使用,但我从来没有真正感觉到 JPA 与只读属性(即 get 方法和无 set 方法)配合得很好。
序列化
与我对jewelsea的答案的评论相反,并且得益于几周的时间来处理这个问题(并且我面临着使用JavaFX属性在JavaFX客户端上复制多个实体类的情况),我认为缺乏JavaFX 属性序列化的问题可以解决。关键的观察是,您实际上只需要序列化属性的包装状态(例如,不需要任何侦听器)。您可以通过实施来做到这一点java.io.Externalizable
. Externalizable
是一个子接口Serializable
这需要您填写readExternal(...)
and writeExternal(...)
方法。可以实现这些方法来仅外部化属性所包装的状态,而不是属性本身。这意味着如果您的实体被序列化然后反序列化,您最终将得到一个新的属性实例,并且任何侦听器都不会被保留(即侦听器实际上变成transient
),但据我所知,这将是任何合理用例中所需要的。
我尝试了以这种方式定义的 bean,一切似乎都运行良好。此外,我还进行了一个小实验,使用 Jackson 映射器在 JSON 表示形式之间进行转换,在客户端和 Restful Web 服务之间传输它们。由于映射器仅依赖于使用 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());
}
}