实体类(Entity Class):需要被持久化的类。为了使一个类能够被持久化,这个类需要满足一系列的约束。
持久化提供者(Persistence Provider):能够将实体类持久化的持久化功能提供者,比如hibernate, JDO, Toplink等。
对实体类的配置,不依赖于具体的持久化提供者,也就是说,在实体类配置好后,更换持久化提供者,不需要修改实体类。也就是说JPA提供了一个标准,实现了实体类标准的类,可以被任何一个实现在持久化提供者标准的框架持久化。所以在用JPA做持久化时,程序员可以集中注意力在实体类的实现上,而不必太关心具体的持久化提供者。
一个能够被持久化的实体类,必须有两部分,一是需要被持久的类,二是对于这个类的持久化配置信息。实 体类必须满足以下约束:
- 被javax.persistence.Entity 标注,或者在XML配置文件里被显式声明为实体类。
- 拥有一个public 或 protected的空参数的构造方法。
- enum和interface不能做实体类。
- 不可以是final类,持久化类和实例变量不能是final的。
- 如果实体类的实例需要在不被持久化提供者管束的情况下被以传值的方式传递,那么实体类需要实现java.io.Serializable接口
- 实体类之间支持继承,多态联接和多态查询。
- 抽像类和具像类都可以做为实体类。实体类可以扩展实体类,也可以扩展非实体类,同样的,非实体类也可以扩展实体类。
- 一个实体的状态是通过实体的实例变量来表示的,而实例变量可能是和Java beans的属性相对应。实体类的实例属性只能被实体在实体自己的方法内直接访问。实体的实例变量不能被实体的客户直接访问。实体的客户只能通过访问器(getter and setter)或者其它的业务方法来访问实体的变量。实体的实例变量必须是private, protected 或者 package 可见的(不能是public的)。
典型的持久化类如下所示:
@Entity
@Table(name="PRODUCT")
public class Product {
@Id
private Long id;
private String name;
private BigDecimal price;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
}
持久化提供者有两种方式访问实体类,一是直接访问实体的实例变量(field access),二是通过实例变量的访问器(JavaBean 模式 property access)。当用annoation来标注实体类时,如果标注在实例变量上,那么将通过实例变量直接访问,如果标注在访问器(getter)上,则通过访问器以JavaBean属性的方式访问。如果既有标注在实例变量上的,又有标注在访问器上的,那么持久化提供者的访问方式将不可知,这是我们应该避免的情况。如果在XML里对已经标注过的实体类进行配置,指定了一种不同于标注所指定的访问方式,那么持久化提供者的访问方式也是不可知的。
下面所示的实体类的访问方式将不可知,因为属性product的getter上,和其它实例变量上都有标注:
@Entity
@Table(name="ORDER")
public class Order {
@Id
private Long id;
private int quantity;
private Date transdate;
@ManyToOne
private Buyer buyer;
private Product product;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Buyer getBuyer() {
return buyer;
}
public void setBuyer(Buyer buyer) {
this.buyer = buyer;
}
@ManyToOne public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public Date getTransdate() {
return transdate;
}
public void setTransdate(Date transdate) {
this.transdate = transdate;
}
}
如果持久化提供者以实例变量的方式访问实体,那么实体的所有未被标为java.persistence.Transient的非 transient 实例变量都会被持欠化,类似的,如果以访问器方式来访问,那么未被标注为java.persistence.Transient的Javabean 属性都会被持久化。
映射用的标注(javax.persistence.ManyToOne等)不能加在transient 或者javax.persistence.Transient的实例变量和属性上。
持久化实例变量或者属性,当为集合类型时,必须声明为如下接口或者其泛化:java.util.Collection, java.util.Set, java.util.List, java.util.Map。
当采用访问器方式访问时,应注意:
- 访问器里可以包含有业务逻辑,比如对数据的验证,格式化等。但是,持久化提供者调用访问器的顺利是不定的,所以不能在访问器中放置依赖于访问顺序的逻辑代码。
- 如果属性被标为延迟载入(lazy load),那么就不能在属性的访问器被持久化提供者真正调用过之前直接访问属性对应的实例变量。也就是说任何时候读写属性都尽量用访问器。
下面的实体类用访问器方式,而访问器setName被设置成依赖于访问顺序的,这将导致问题:
@Entity
@Table(name="MAN")
public class Man {
private Long id;
private String name;
private String sex;
@Id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
if("male".equals(sex)) {
this.name = "Mr." + name;
} else {
this.name = "Ms." + name;
}
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
设计者希望setSex在setName之前被调用,但是访问器的调用顺序是不定的,可以会出现先setName后setSex的情况。
实体类的子类可以覆盖父类的访问器,但是不要覆盖父类里实例变量或者属性的持久化元数据。