【JavaEE系列——JPA】——JPA Entities

来源:互联网 发布:手机网络加速哪个好用 编辑:程序博客网 时间:2024/06/05 19:44

目录

  • 目录
    • Entities
      • 实体类的要求
      • 实体类中的持久性字段和属性
      • 持久性字段
      • 持久性属性
      • 在实体字段和属性中使用集合
      • 验证持久性字段和属性
      • 实体中的属性
      • 实体关系的多重性
      • 实体关系方向
        • 双向关系
        • 单向关系
        • 查询和关系方向
        • 级联操作和关系
        • 孤儿去除关系
      • 实体中的可嵌入类

Java Persistence API 是一种基于Java标准的持久性解决方案。持 久性使用对象/关系映射方法为面向对象模型和关系数据库建立联系。Java Persistence API也可以在JavaEE环境之外的JavaSE应用程序中使用。Java的持久性包含如下几个方面:

  1. Java 持久化API
  2. 查询语言
  3. ORM元数据
  4. Java持久化标准API

接下来我们会讨论如下几个主题:

  1. Entities
  2. Entity Inheritance
  3. Managing Entities
  4. Querying Entities
  5. Database Schema Creation
  6. Further Information about Persistence

在本篇文章中,我们先来讲一下Entities:

Entities

实体是一个轻量级的持久性域对象。通常,实体表示关系数据库中的表,每个实体实例对应于该表中的一行。实体的主要编程工件是实体类,尽管实体可以使用辅助类。 实体的持久状态通过持久字段或持久属性来表示。这些字段或属性使用对象/关系映射注释将实体和实体关系映射到底层数据存储中的关系数据。

实体类的要求

实体类必须遵循如下要求:

  1. 该类必须使用javax.persistence.Entity注释进行注释。
  2. 该类必须具有公共或受保护的无参数构造函数。该类可能有其他构造函数。
  3. 该类不能被声明为final。任何方法或持久性实例变量都必须被声明为final。
  4. 如果一个实体实例作为分离的对象通过值传递,例如通过会话bean的远程业务接口,该类必须实现Serializable接口。
  5. 实体可以扩展实体和非实体类,非实体类可以扩展实体类。
  6. Persistent实例变量必须声明为private,protected或package-private,并且只能由entity类的方法直接访问。客户端必须通过访问者或业务方法访问实体的状态。

实体类中的持久性字段和属性

实体的持久状态可以通过实体的实例变量或属性来访问。字段或属性必须是以下Java语言类型:

Java primitive typesjava.lang.StringOther serializable types, including:Wrappers of Java primitive typesjava.math.BigIntegerjava.math.BigDecimaljava.util.Datejava.util.Calendarjava.sql.Datejava.sql.Timejava.sql.TimeStampUser-defined serializable typesbyte[]Byte[]char[]Character[]Enumerated typesOther entities and/or collections of entitiesEmbeddable classes

实体可以使用持久性字段,持久属性或两者的组合。如果将映射注释应用于实体的实例变量,则实体将使用持久性字段。如果将映射注释应用于实体的JavaBeans样式属性的getter方法,那么该实体将使用持久属性。

持久性字段

如果实体类使用持久字段,则Persistence运行时直接访问实体类实例变量。未注释的所有字段javax.persistence.Transient或未标记为Java transient将被持久化到数据存储。对象/关系映射注释必须应用于实例变量。

持久性属性

如果实体使用持久属性,则实体必须遵循JavaBean组件的方法约定。 JavaBeans风格的属性使用通常以实体类的实例变量名称命名的getter和setter方法。对于类型为实体的每个持久属性属性,都有一个getter方法getProperty和setter方法setProperty。如果属性是布尔值,则可以使用isProperty而不是getProperty。例如,如果Customer实体使用持久属性并具有名为firstName的私有实例变量,则该类定义了一个getFirstName和setFirstName方法来检索和设置firstName实例变量的状态。

单值持久性属性的方法签名如下:

Type getProperty()void setProperty(Type type)

持久性属性的对象/关系映射注释必须应用于getter方法。映射注释不能应用于注释@Transient或标记的瞬态的字段或属性。

在实体字段和属性中使用集合

集合值的持久字段和属性必须使用受支持的Java集合接口,而不管实体是使用持久性字段还是属性。可以使用以下集合接口:

java.util.Collectionjava.util.Setjava.util.Listjava.util.Map

如果实体类使用持久性字段,则上述方法签名中的类型必须是这些集合类型之一。也可以使用这些集合类型的通用变体。例如,如果它具有包含一组电话号码的持久性属性,则客户实体将具有以下方法:

Set<PhoneNumber> getPhoneNumbers() { ... }void setPhoneNumbers(Set<PhoneNumber>) { ... }

如果实体的字段或属性由基本类型或可嵌入类的集合组成,请在字段或属性上使用javax.persistence.ElementCollection注释。 @ElementCollection的两个属性是targetClass和fetch。 targetClass属性指定basic或embeddable类的类名称,如果字段或属性是使用Java编程语言泛型定义的,则该属性是可选的。可选的fetch属性用于分别使用LAZY或EAGER的javax.persistence.FetchType常量来指定是否应该懒得或热切地检索集合。默认情况下,收集将被懒得取出。 以下实体Person具有一个持久化字段,昵称,它是将被强制抓取的String类的集合。 targetClass元素不是必需的,因为它使用泛型来定义字段:

@Entitypublic class Person {    ...    @ElementCollection(fetch=EAGER)    protected Set<String> nickname = new HashSet();    ...}

实体元素和关系的集合可以由java.util.Map集合表示。Map由一个键和一个值组成。 使用Map元素或关系时,应用以下规则。

  1. Map键或值可以是基本的Java编程语言类型,可嵌入类或实体。
  2. 当Map值是可嵌入类或基本类型时,请使用@ElementCollection注释。
  3. 当Map值是一个实体时,使用@OneToMany或@ManyToMany注释。
  4. 仅在双向关系的一侧使用Map类型。

如果Map的关键类型是Java编程语言的基本类型,请使用注释javax.persistence.MapKeyColumn设置键的列映射。默认情况下,@MapKeyColumn的name属性格式为RELATIONSHIP-FIELD / PROPERTY-NAME_KEY。例如,如果引用关系字段名称是图像,则默认名称属性为IMAGE_KEY。

如果Map的关键类型是实体,请使用javax.persistence.MapKeyJoinColumn注释。如果需要多个列来设置映射,请使用注释javax.persistence.MapKeyJoinColumns来包含多个@MapKeyJoinColumn注释。如果没有@MapKeyJoinColumn存在,映射列名称默认设置为RELATIONSHIP-FIELD / PROPERTY-NAME_KEY。例如,如果关系字段名称是employee,则默认名称属性为EMPLOYEE_KEY。

如果Java编程语言通用类型不在关系字段或属性中使用,则必须使用javax.persistence.MapKeyClass注释显式设置关键类。 如果Map键是作为Map值的实体的主键或持久字段或属性,请使用javax.persistence.MapKey注释。 @MapKeyClass和@MapKey注释不能在同一个字段或属性上使用。

如果Map值是Java编程语言的基本类型或可嵌入类,它将被映射为底层数据库中的集合表。如果不使用泛型类型,则@ElementCollection注释的targetClass属性必须设置为Map值的类型。 如果Map值是实体,并且是多对多或一对多单向关系的一部分,则它将被映射为底层数据库中的连接表。也可以使用@JoinColumn注释来映射使用Map的单向一对多关系。

验证持久性字段和属性

用于JavaBean验证的Java API(Bean验证)提供了验证应用程序数据的机制。 Bean验证集成到Java EE容器中,允许在企业应用程序的任何层级中使用相同的验证逻辑。 Bean验证约束可以应用于持久实体类,可嵌入类和映射超类。默认情况下,Persistence提供程序将在PrePersist,PreUpdate和PreRemove生命周期事件之后立即对具有持久性字段或属性的Bean验证约束的实体进行自动执行验证。 Bean验证约束是应用于Java编程语言类的字段或属性的注释。 Bean验证提供了一组约束以及用于定义自定义约束的API。自定义约束可以是默认约束的特定组合,也可以是不使用默认约束的新约束。每个约束与至少一个验证器类相关联,验证器类验证受限字段或属性的值。自定义约束开发人员还必须为约束提供一个验证器类。 Bean验证约束应用于持久性类的持久性字段或属性。添加Bean验证约束时,使用与持久类相同的访问策略。也就是说,如果持久化类使用字段访问,则在类的字段上应用Bean验证约束注释。如果类使用属性访问,则对getter方法应用约束。 表21-1列出了在javax.validation.constraints包中定义的Bean Validation的内置约束。 表21-1中列出的所有内置约束都具有相应的注释ConstraintName.List,用于对同一字段或属性上的同一类型的多个约束进行分组。例如,以下persistent字段有两个@Pattern约束:

@Pattern.List({    @Pattern(regexp="..."),    @Pattern(regexp="...")})

以下实体类Contact已将Bean验证约束应用于其持久字段:

@Entitypublic class Contact implements Serializable {    @Id    @GeneratedValue(strategy = GenerationType.AUTO)    private Long id;    @NotNull    protected String firstName;    @NotNull    protected String lastName;    @Pattern(regexp = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\."            + "[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@"            + "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9]"            + "(?:[a-z0-9-]*[a-z0-9])?",            message = "{invalid.email}")    protected String email;    @Pattern(regexp = "^\\(?(\\d{3})\\)?[- ]?(\\d{3})[- ]?(\\d{4})$",            message = "{invalid.phonenumber}")    protected String mobilePhone;    @Pattern(regexp = "^\\(?(\\d{3})\\)?[- ]?(\\d{3})[- ]?(\\d{4})$",            message = "{invalid.phonenumber}")    protected String homePhone;    @Temporal(javax.persistence.TemporalType.DATE)    @Past    protected Date birthday;    ...}

firstName和lastName字段上的@NotNull注释指定现在需要这些字段。如果创建了一个新的Contact实例,其中firstName或lastName尚未初始化,Bean验证将抛出验证错误。类似地,如果先前创建的联系人实例已被修改,以使firstName或lastName为null,则会抛出验证错误。

电子邮件字段有一个@Pattern约束应用于它,一个复杂的正则表达式匹配大多数有效的电子邮件地址。如果电子邮件的值与此正则表达式不匹配,则会抛出验证错误。

homePhone和mobilePhone字段具有相同的@Pattern约束。正则表达式匹配美国和加拿大的10位电话号码,格式为(xxx)xxx-xxxx。

生日字段用@Past约束注释,确保生日的值必须在过去。

实体中的属性

每个实体都有唯一的对象标识符。例如,客户实体可能被客户编号识别。唯一标识符或主键使客户端能够定位特定的实体实例。每个实体必须有一个主键。实体可以具有简单或复合主键。

简单主键使用javax.persistence.Id注释来表示主键属性或字段。 当主键由多个属性组成时,使用复合主键,该属性对应于一组单个持久属性或字段。复合主键必须在主键类中定义。复合主键使用javax.persistence.EmbeddedId和javax.persistence.IdClass注释表示。

主键或复合主键的属性或字段必须是以下Java语言类型之一:

Java primitive typesJava primitive wrapper typesjava.lang.Stringjava.util.Date (the temporal type should be DATE)java.sql.Datejava.math.BigDecimaljava.math.BigInteger

主键不能使用浮点类型。如果使用生成的主键,则只能使用整数类型。 主要关键类别必须符合这些要求。

  1. 该类的访问控制修饰符必须是public。
  2. 如果使用基于属性的访问,则主键类的属性必须为public或protected。
  3. 该类必须具有公共默认构造函数。
  4. 该类必须实现hashCode()和equals(Object other)方法。
  5. 该类必须是可序列化的。
  6. 复合主键必须被表示并映射到实体类的多个字段或属性,或者必须被表示和映射为可嵌入类
  7. 如果类映射到实体类的多个字段或属性,则主键类中的主键字段或属性的名称和类型必须与实体类的名称和类型相匹配。

以下主键类是一个复合键,并且customerOrder和itemId字段一起唯一标识一个实体:

public final class LineItemKey implements Serializable {    private Integer customerOrder;    private int itemId;    public LineItemKey() {}    public LineItemKey(Integer order, int itemId) {        this.setCustomerOrder(order);        this.setItemId(itemId);    }    @Override    public int hashCode() {        return ((this.getCustomerOrder() == null                ? 0 : this.getCustomerOrder().hashCode())                ^ ((int) this.getItemId()));    }    @Override    public boolean equals(Object otherOb) {        if (this == otherOb) {            return true;        }        if (!(otherOb instanceof LineItemKey)) {            return false;        }        LineItemKey other = (LineItemKey) otherOb;        return ((this.getCustomerOrder() == null                ? other.getCustomerOrder() == null : this.getCustomerOrder()                .equals(other.getCustomerOrder()))                && (this.getItemId() == other.getItemId()));    }    @Override    public String toString() {        return "" + getCustomerOrder() + "-" + getItemId();    }    /* Getters and setters */}

实体关系的多重性

多重性有以下类型:

  1. 一对一:每个实体实例与另一个实体的单个实例相关。例如,要建立一个物理仓库,其中每个存储仓都包含一个窗口小部件,StorageBin和Widget将具有一对一的关系。一对一的关系在相应的持久性属性或字段上使用javax.persistence.OneToOne注释。
  2. 一对多:实体实例可以与其他实体的多个实例相关。例如,销售订单可以有多个订单项。在订单应用程序中,CustomerOrder将与LineItem具有一对多的关系。一对多关系在对应的持久性属性或字段上使用javax.persistence.OneToMany注释。
  3. 多对一:实体的多个实例可以与另一个实体的单个实例相关联。这种多重性与一对多关系相反。在刚刚提到的例子中,从LineItem的角度来看,与CustomerOrder的关系是多对一的。多对一关系在相应的持久性属性或字段上使用javax.persistence.ManyToOne注释。
  4. 多对多:实体实例可以与彼此的多个实例相关联。例如,每个大学课程都有很多学生,每个学生都可以参加几门课程。因此,在入学申请中,课程和学生将会有多对多关系。多对多关系对相应的持久性属性或字段使用javax.persistence.ManyToMany注释。

实体关系方向

实体关系方向既拥有一面,又有反面。单向关系只有一个方面。关系的拥有侧决定了Persistence运行时如何更新数据库中的关系。

双向关系

在双向关系中,每个实体都有一个关系字段或属性引用另一个实体。通过关系字段或属性,实体类的代码可以访问其相关对象。如果一个实体有一个相关的字段,那么该实体就被称为“知道”其相关对象。例如,如果CustomerOrder知道它有什么LineItem实例,如果LineItem知道它属于哪个CustomerOrder,它们就具有双向关系。

双向关系必须遵循这些规则。

双向关系的反面必须通过使用@OneToOne,@OneToMany或@ManyToMany注释的mappedBy元素来引用它自己的一方。 mappedBy元素指定作为关系所有者的实体中的属性或字段。
多对一双向关系的许多方面都不能定义mappedBy元素。许多方面永远是关系的拥有者。
对于一对一双向关系,拥有方对应于包含相应外键的一侧。
对于多对多双向关系,任一方可能是拥有方面。

单向关系

在单向关系中,只有一个实体具有引用另一个实体的关系字段或属性。例如,LineItem将有一个标识Product的关系字段,但Product不会有LineItem的关系字段或属性。换句话说,LineItem知道产品,但产品不知道哪个LineItem实例引用它。

查询和关系方向

Java持久性查询语言和Criteria API查询通常跨越关系。关系的方向确定查询是否可以从一个实体导航到另一个实体。例如,查询可以从LineItem导航到Product,但不能以相反的方向导航。对于CustomerOrder和LineItem,查询可以在两个方向上导航,因为这两个实体具有双向关系。

级联操作和关系

使用关系的实体往往依赖于关系中另一个实体的存在。例如,订单项是订单的一部分;如果订单被删除,订单项也应该被删除。这被称为级联删除关系。

javax.persistence.CascadeType枚举类型定义了在关系注释的级联元素中应用的级联操作。表37-1列出了实体的级联操作。

Cascade Operation Description ALL 所有级联操作都将应用于父实体的相关实体。全部相当于指定cascade = {DETACH,MERGE,PERSIST,REFRESH,REMOVE} DETACH 如果父实体与持久性上下文分离,则相关实体也将被分离。 MERGE 如果父实体合并到持久性上下文中,则相关实体也将被合并。 PERSIST 如果父实体持久化到持久化上下文中,相关实体也将被持久化。 REFRESH 如果父实体在当前持久性上下文中刷新,相关实体也将被刷新。 REMOVe 如果父实体从当前持久性上下文中删除,则相关实体也将被删除。

级联删除关系使用@OneToOne和@OneToMany关系的cascade = REMOVE元素规范来指定。例如:

@OneToMany(cascade=REMOVE, mappedBy="customer")public Set<CustomerOrder> getOrders() { return orders; }

孤儿去除关系

当一对一或一对多关系中的目标实体从关系中移除时,往往希望将删除操作级联到目标实体。这样的目标实体被认为是“孤儿”,orphanRemoval属性可用于指定应该删除孤立的实体。例如,如果订单有许多订单项,其中一个订单项从订单中删除,则将删除的订单项视为孤立的。如果orphanRemoval设置为true,则在从订单中删除订单项时,订单项实体将被删除。

@OneToMany和@oneToOne中的orphanRemoval属性采用布尔值,默认为false。

以下示例将在删除客户实体时将删除操作级联到孤立订单实体:

@OneToMany(mappedBy="customer", orphanRemoval="true")public List<CustomerOrder> getOrders() { ... }

实体中的可嵌入类

可嵌入类用于表示实体的状态,但不具有自己的持久标识,与实体类不同。可嵌入类的实例共享拥有它的实体的身份。可嵌入类仅存在于另一个实体的状态。实体可以具有单值或收藏值嵌入类属性。

可嵌入类与实体类具有相同的规则,但使用javax.persistence.Embeddable注释而不是@Entity注释。

以下可嵌入类ZipCode具有zip和plusFour字段:

@Embeddablepublic class ZipCode {    String zip;    String plusFour;    ...}

这个可嵌入类被Address实体使用:

@Entitypublic class Address {    @Id    protected long id    String street1;    String street2;    String city;    String province;    @Embedded    ZipCode zipCode;    String country;    ...}

将嵌入式类作为其持久状态的一部分的实体可以使用javax.persistence.Embedded注释对该字段或属性进行注释,但不需要这样做。

可嵌入类本身可以使用其他可嵌入类来表示其状态。它们还可以包含基本Java编程语言类型或其他可嵌入类的集合。可嵌入类还可以包含与其他实体或实体集合的关系。如果可嵌入类具有这样的关系,则关系是从目标实体或实体集合到拥有可嵌入类的实体。