第二章、实体类(JPA)

实体是一个轻量级持久化域对象

主要的编程工件是实体类,实体类可以使用辅助类,这些辅助类用作帮助类或用于表示实体状态的辅助类。

本章介绍对实体类和实体的要求。

2.1、实体类

  • 实体类必须是Entity注解进行标记,或在XML描述符中表示为实体。
  • 实体类必须具有无参的构造方法,实体也可以具有其他构造方法,无参构造方法必须是public或protected修饰的
  • 实体必须是顶级类,枚举和接口不得指定为实体。
  • 实体类不能是final修饰的,实体类的任何方法或持久性实例变量都不得是final修饰的。
  • 如果实体实例要通过值作为传输的对象(例如,通过远程接口)传递,则实体类必须实现Serializable接口。
  • 实体支持继承、多态关联和多态查询
  • 抽象类和具体类都可以是实体。实体可以扩展非实体类以及实体类,并且非实体类可以扩展实体类。
  • 实体的持久状态有实体变量表示, 实体变量可能对应于JavaBeans属性。实体变量只能由实体实例本身直接从实体的方法内部访问,实例变量不能由实体的客户端访问,客户只能通过实体的方法(即访问getter/setter方法)或者其他业务业务方法,才能使用实体的状态。

2.2、持久化字段和属性

  • 持久性提供程序运行时 [ 1 ] ^{[1]} [1] 通过JavaBeans方式属性访问器(“属性访问") 或实例变量(“字段访问”)访问实体的持久状态。如果第2.3节”访问类型“中所述, 确定提供者对给定类或实体层次结构使用的是持久性属性还是持久性字段,还是两者的组合。

    • 术语备注:实体类的持久字段和属性在本文档中通常称为类的"属性"
  • 类的实例变量必须是private, protected或package(包可见)的修饰,而与使用字段访问还是属性访问无关,使用属性访问时,属性访问器方法必须是public或protected修饰。

  • 对于使用属性访问的持久属性,要求实体类遵循Java Beans读写属性(有Java Beans Introspector类定义)的方法签名约定。

  • 在这种情况下,对于实体类型T的每个持久属性,都有一个getter方法,getProperty和setter方法的setProperty. 对于布尔属性,可以将isProperty用作getter方法的替代名称 [ 2 ] ^{[2]} [2]

  • 对于单值持久属性,这些方法签名为:

    • T getProperty()
      
      void setProperty(T t)
      
  • 备注

    • [1] : 术语"持久性提供程序运行时"是指持久性实现的运行时环境,在Java EE环境中,这可能是Java EE环境中,这可能是Java EE容器或与其集成的第三方持久性提供程序实现。
    • [2] : 具体来说,如果getX是getter方法的名称,而setX是setter方法的名称,其中X是字符串,则持久属性的名称有java.beans.Introspector.decapitalize(X)的结果定义
  • 不论实体类是否遵守JavaBeans, 都必须根据以下集合值接口之一定义集合值的持久字段和属性。上面提到的方法约定以及是否使用字段或属性访问:java.util.Collection, java.util.Set, java.util.List [ 3 ] ^{[3]} [3]和java.util.Map. 在实体成为持久化之前,应用程序可以使用集合实现类型来初始化字段或属性,一旦实体被管理(或分离),随后的访问必须通过接口进行。

    • 术语备注:除非另有说明,否则在本规范中使用术语”集合“和"集合值"来表示以上任何类型。在要区分java.util.Collection类型(或其子类型之一)的情况下,将这样标识该类型。当需要区分java.util.Map类型的集合时,术语"map"和"map collection"用于应用于java.util.Map类型的集合
  • 对于具有集合值的持久属性, 类型T必须是上述方法签名中的这些集合接口类型之一。鼓励使用这些集合类型的泛型变量(例如Set)

  • 除了返回并设置实例的持久状态外,属性访问器方法还可以包含其他业务逻辑,例如,以执行验证,当使用基于属性的访问时,持久性提供运行时将执行此逻辑。

    • 使用属性访问时,在向访问其方法添加业务逻辑时应谨慎行事。未定义持久性提供运行时在加载或存储持久性状态时调用这些方法的顺序。因此,此类方法中包含的逻辑不应依赖于特定的调用顺序。
  • 如果使用属性访问并指定了延迟提取,则便携式应用程序不应直接访问托管实例的属性方法基础的实体状态,直到持久性提供程序将其提取 [ 4 ] ^{[4]} [4]

  • 如果将持久性上下文加入到事务中,则属性访问器方法引发的运行时异常会导致当前事务被标记为回滚,持久性运行时使用此类方法抛出的异常来加载或存储持久状态时,会导致持久性运行时将当前事务标记为回滚,并引发包装了应用异常的PersistenceException.

  • 实体子类可以覆盖属性访问器方法。但是可移植应用程序不得覆盖适用于实体超类的持久字段或属性的对象/关系映射的元数据。

  • 备注

    • [3] : 除非使用OrderColumn构造或使用OrderBy构造并且对列表的修改遵循指定的顺序,否则便携式应用不应期望在持久性上下文维护列表的顺序。
    • [4] : 延迟获取是对持久性提供程序的提示,可以通过Basic, OneToOne, OneToMany, ManyToOne, ManyToMany和ElementCollection批注及其XML等效项进行指定。请参阅第11章。
  • 这个持久化字段或实体的属性可能如下类型: java原生数据类型, java.lang.String, 和其他序列化类型(包含原生数据类型的包装类, java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar [ 5 ] ^{[5]} [5], java.sql.Date, java.sql.time, java.sql.Timestamp, byte[], Byte[], char[] Character[] java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.OffsetTime, java.time.OffsetDateTime,和用户自定实现Serializable接口类型),枚举类型,实体类型,实体的集合类型,内嵌类(请参阅2.5节),集合的基础和内嵌类型(请参阅2.6)。

  • 可以指定对象/关联映射元数据以定制对象/关系映射以及实体状态和关系的加载和存储。请参阅第11章。

  • 备注

    • [5] : 请注意,Calendar实例必须针对其映射到的类型进行完全初始化。

2.2.1、例子

  • public class Customer implements Serializable{
      
      private Long id;
      
      private String name;
      
      private Address address;
      
      private Collection<Order> orders = new HashSet();
      
      private Set<PhoneNumber> phones = new HashSet();
      
      // 无参构造函数
      public Customer(){}
      
      
      @Id  //使用属性访问
      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 Address getAddress(){
        return address;
      }
      public void setAddress(){
        this.address=address;
      }
      
      @OneToMany
      public Collection<Order> getOrders(){
        return orders;
      }
      
      public void setOrders(Collection<Order> orders){
        this.orders = orders;
      }
      
      @ManyToMany
      public Set<PhoneNumber> getPhones(){
        return phones;
      }
      
      public void setPhones(Set<PhoneNumber> phones){
        this.phones = phones;
      }
      
      // 业务方法添加一个电话号码给客户
      public void addPhone(PhoneNumber phone){
        this.getPhones().add(phone);
        //更新phone的实体实例以引用此客户
        phone.addCustomer(this);
      }
    }
    

2.3、Access type (访问类型)

2.3.1、默认访问类型

  • 默认情况下,单一访问类型(字段或属性访问)适用于实体层次结构。实体层次结构的默认访问类型由映射注解在未明确的指定访问类型的实体类型的属性和映射的超类的属性上的放置方式决定。如 2.2.3节所述,通过访问注解显式指定访问类型 [ 6 ] ^{[6]} [6]
  • 当使用注解定义默认访问类型时,将映射注解放置在实体类的持久字段或持久属性上时,会将访问类型分别指定为基于字段或基于属性的访问。
    • 当使用基于字段的访问时,实体类的对象/关系映射注解将对实体变量进行标记, 而持久性提供程序运行时将直接访问实例变量,所有未使用Transient注解进行标记的non-transient实例变量都是持久性的。
    • 使用基于属性的访问时,实体的对象/关系映射注解 [ 7 ] ^{[7]} [7]类注解getter属性访问器,并且持久性提供程序运行时通过属性访问器方法访问持久状态。所有未使用Transient注解进行标记的属性都是持久性的。
    • 映射注解不能应用于transient或Transient修饰的字段或属性上。
  • 实体层次结构中以这种方法默认访问类型的所有此类都必须在字段或属性上的注解位置上保持一致,以便在层次结构中应用单个一致的默认访问类型。 这些类使用的任何可嵌入类都将具有与层次结构的默认访问类型相同的访问类型,除非按以下定义指定了Access注解。
  • 如果无法确定默认的访问类型并且未通过注解或XML描述符显式指定访问类型,则会出现错误。 未定义将注解在实体层次结构中的字段和属性上混合放置而未明确指定Access注解的应用程序的行为。
  • 备注
    • [6] : 第12章介绍了使用XML作为替代方法以及Java语言注解和XML元素在定义默认和显式访问类型中的交互
    • [7] : 这些注解不得应用于setter方法。

2.3.2、显式访问类型

  • 可以通过应用于该类的Access注解,为该类指定单个实体类,映射超类或可嵌入类的访问类型, 而与实体层次结果的默认设置无关,此显式访问类型规范不会影响实体层次结构中其他实体类或映射的超类的访问类型。适用以下规则:
    • 当将Access(FIELD)应用于实体类, 映射的超类或可嵌入的类时,可以将映射注解放置在该类的实例变量上,并且持久性提供程序运行时通过该类定义的实例变量访问持久性状态,所有未使用(Transient)瞬态注解进行标记的非瞬态实例变量都是持久性的,将Access(FIELD)应用于此类时,可以有选择地在该类中指定各个属性以进行属性访问。若要指定持久性提供程序运行时访问的持久性属性,必须将该属性性指定为Access(PROPERTY). [ 8 ] ^{[8]} [8] 如果将映射注解放置在未为其指定Access(PROPERTY)的类定义的任何属性上,则该行为未定义,从超类继承的持久状态将根据这些超类的访问类型进行访问。
    • 将Access(PROPERTY)应用于实体类,映射的超类或可嵌入的类时,可以将映射注解放在该类的属性上,并且持久性提供程序运行时会通过该类定义的属性访问持久状态。所有未使用Transient注解进行标记的属性都是持久性的,将Access(PROPERTY)应用于此类时,可以有选择地在该类中指定各个属性,例如变量访问。 若要指定持久性实例变量以供持久性提供程序运行时访问,则该实例变量必须指定为Access(FIELD), 如果将映射注解放置在未为其指定Access(FIELD)的类定义的任何实例变量上,则该行为未定义。从超类继承的持久状态将根据这些超类的访问类型进行访问。
    • 请注意,当将访问类型组合在一个类中时,应使用Transient注解来避免重复的持久映射

2.3.3 内嵌类的访问类型

  • 可嵌入类的访问类型取决于实体类,映射超类或嵌入它的可嵌入类的访问类型(包括作为元素集合的成员),而与包含对象的访问类型无关,类已被明确指定或默认,可以通过如上所述的Access注解为该可嵌入类指定不同的可嵌入类访问类型。

2.3.4、内嵌类的默认访问类型和映射超类

  • 定义在字段访问上下文和属性访问上下文中使用且其访问类型未通过Access注解或XML映射文件明确指定的可嵌入类或映射超类时,必须格外小心。
  • 应该定义此类,以使所得持久性属性的数量,名称和类型相同,而与所使用的访问类型无关。如果属性发生在同一个持久性单元内的不同访问类型的上下文中,则这个这些属性与访问类型无关的类在于元模型API结合使用时是不确定的。

2.4、主键和实体身份

  • 每个实体都应该有一个主键

  • 必须在做完实体层次结构根的实体类上或在做为实体层次结构中所有实体类的(直接或间接)超类的映射超类上定义主键。在实体层次结构中,主键必须定义一次。

  • 主键对应于实体类的一个和多个字段或属性(“attributes”)

    • 一个简单的(即非复合的)主键必须对应于实体类的单个持久字段或属性。必须使用Id注解或id XML元素来表示一个简单的主键。参见11.1.21节。
    • 组合主键必须对应于单个持久性字段或属性,或对应于如下所述的一组此类字段或属性。必须定义一个主键类来表示一个复合主键。当数据库键由几列组成时,从主数据库进行映射时,通常会出现复合主键,EmbeddedId或IdClass注解用于表示复合主键,参见11.1.17 和11.1.22节
  • 简单主键或复合主键的字段或属性应为以下之一类型:任何java基本类型和任何基本类型的包装类类型,java.lang.String; java.util.Date; java.sql.Date; java.math.BigDecimal; java.math.BigInteger [ 9 ] ^{[9]} [9], 如果主键是从另一个实体的主键派生的复合主键,主键可以包含一个属性,其类型为所引用实体的主键的类型,如第2.4.1节中所述。主键使用其他类型之外的实体将不可移植性,如果使用生成的主键,则仅整数类型可移植的。如果将java.util.Date用作主键字段或属性,则应将时间类型指定为DATE。

  • 以下规则适用于复合主键:

    • 主键类必须是public修饰和必须有public修饰无参构造函数。
    • 主键类的访问类型(基于字段或基于属性的访问)由作为主键的实体的访问类型确定,除非主键是嵌入式Id且指定了其他访问类型。请参见第2.3节”访问类型“。
    • 如果使用基于属性的访问,则主键类的属性必须是public或protected修饰的
    • 主键类必须实现序列化接口
    • 主键类必须定义equals和hashCode方法,这些方法的值相等的语义必须与键映射到的数据类型的数据库相等一致。
    • 组合主键必须表示并映射为可嵌入类(请参见第11.1.17节"EmbeddedId 注解"),或者必须表示id类并映射到实体类的多个字段或属性(请参见第11.1.22节 ”IdClass注解)
    • 如果将组合主键类表示为id类,则该主键类中的主键字段或属性的名称以及与该id类映射的实体类的名称必须相对应,并且他们的类型必须相同。
    • 对应于派生身份的主键必须符合2.4.1节的规则
  • 它的主键值唯一地标识了持久性上下文中的一个实体实例,并在第3章“实体操作” 中对EntityManager操作进行了标识。 应用程序不得更改主键的值 [ 10 ] ^{[10]} [10]。如果发生这种情况,则行为是不确定的 [ 11 ] ^{[11]} [11]

  • 备注

    • [9] : 但是,一般而言,绝不能在主键中使用近似数字类型(例如浮点型)
    • [10] : 这包括不更改作为主键的可变类型的值或不对复合主键进行分配的值。
    • [11] : 该实现可以但不要求引发异常,可移植应用程序不得依赖任何此类型特定的行为。

2.4.1、对应于派生身份的主键

  • 当前一个实体("从属"实体)是与该实体多对一或一对一关系的所有者时,一个实体的身份可以从另一个实体("父"实体)的身份派生而来,父实体和外键将关系从依赖关系映射到父关系。
  • 如果多对一或一对一实体关系与主键属性相对应,则在不将该关系分配给实体的情况下,包含该关系的实体不能持久化,因为包含该关系的实体的身份为从引用的实体派生。 [ 12 ] ^{[12]} [12]
  • 可以通过简单的主键或复合主键来捕获派生身份,如下面第2.4.1.1小节所述。
  • 如果从属实体类除具有对应于父级主键的属性外还具有主键属性,或者父级具有复合主键,则必须使用嵌入式id或id类指定从属实体的主键。当父对象具有复合主键时,父实体和从属实体都不必使用嵌入式id或都使用ID类来表示复合主键。
  • 一个从属实体可以有多个父实体。
2.4.1.1、派生身份描述
  • 如果从属实体类使用id类表示其主键,则必须遵守以下两个规则之一:

    • id类的属性名称和从属实体类的Id属性名称必须对应如下:
      • 实体类中的id属性和id类中的对应属性必须具有相同的名称。
      • 如果实体类中的Id属性为基本类型,则id类中的对应属性必须具有相同的类型。
      • 如果实体中的Id属性与父实体存在多对一或一对一关系,则id类中的对应属性必须与父类的id类或嵌入式id具有相同的Java类型实体(如果父实体具有复合主键)或父实体的Id属性的类型(如果父实体具有简单的主键)。
    • 如果从属实体具有单个主键属性(即,关系属性),则由从属性实体指定的id类必须与父实体的主键类相同 [ 13 ] ^{[13]} [13],Id注解应用于与父实体的关系
  • 备注

    • [12] :如果该应用程序没有设置与之对应的主键属性,则该属性的值可能直到将实体刷新到数据库后才可以使用。
    • [13] : 请注意,在这种情况下,遵循第一条规则作为替代是正确的。
  • 如果从属实体使用嵌入式ID表示其主键,则对应于关系数的嵌入式ID中的属性必须与父实体的主键具有相同的类型,并且必须由应用于父实体的MapsId注解指定,关系属性,必须使用MapsId 注解的value元素在关系属性所对应的嵌入式ID中指定属性的名称。如果从属实体的嵌入ID与父实体的主键具有相同的Java类型,则关系属性会将关系映射到从属实体和主键,在这种情况下MapsId 注解指定没有value元素 [ 14 ] ^{[14]} [14]

  • 如果从属实体具有单个主键属性(即,关系属性或与该关系属性相对应的属性),并且父实体的主键是简单的主键,则从属实体的主键与父实体类型相同的简单主键(并且未指定EmbeddedId和IdClass)。 在这种情况下,要么(1)关联属性被注解为Id,要么(2)指定一个单独的Id属性,而关系属性被注解为MapsId(并且未指定MapsId注解的value元素)

  • 备注

    • [14] :注意父类的主键可能代表一个内嵌id或一个id类
2.4.1.2、派生身份的映射
  • 从父实体的身份派生的主键属性由对应的关系属性映射,此关系的默认映射如2.10节中所述。如果默认映射不适用或要覆盖默认映射,则在关系属性上使用JoinColumn或JoinColumns注解
  • 如果从属实体使用嵌入的id表示其主键,则AttributeOverride注解可用于覆盖嵌入的id属性的默认映射,该默认映射不应于映射派生的标识的关系属性。提供者将与该关系对应的嵌入式Id属性视为“只读”,也就是说,在应用程序部分对它们的任何更新都不会传播到数据库。
  • 如果从属使用Id类,则可以使用Column注解覆盖不是关系属性的Id属性的默认映射。
2.4.1.3 派生身份的例子
  • 例子1:父实体有一个简单主键

    • @Entity
      public class Employee{
        @Id 
        long empId;
        
        String empName;
        ...
      }
      
      //案例(a) 依赖实体使用 IdClass作为一个组合键
      public class DependentId{
        String name; // 匹配@Id属性的名称
        
        long emp; // 匹配@Id属性的名称和Employee 主键的类型
      }
      
      
      @Entity
      @IdClass(DependentId.class)
      public class Dependent{
        @Id
        String name;
        
        @Id
        @ManyToOne Employee emp; // join默认映射的id属性
      }
      
      // 查询语句样例
      SELECT d 
      FROM  Dependent d
      WHERE d.name='Joe' AND d.emp.empName='Sam';
      
      
      //案例(b) 依赖实体使用EmbeddedId去代表组合键
      
      @Embeddable
      public class DependentId{
        String name;
        long empPK; // 对应Employee的主键类型
      }
      
      @Entity
      public class Dependent{
       
        @EmbeddedId DependentId id;
        ...;
        // 通过join默认列来进行id属性映射
        @MapsId("empPK")   // 映射嵌入式id的empPK属性
        @ManyToOne Employee emp;
      }
        
      //查询样例
      SELECT d 
      FROM  Dependent d
      WHERE d.id.name='Joe' AND d.emp.empName='Sam';
        
      
      
  • 例子2:父实体使用Id

    • public class EmployeeId{
        String firstName;
        
       	String lastName;
        ...
      }
      
      public class Employee{
        @Id String firstName;
        @Id  String lastName;
        ...
      }
      
      // 案例(a) 依赖实体使用IdClass:
      
      public class DependentId{
        String name; // 匹配名称属性
        
        EmployeeId emp; //匹配名称属性和Employee主键的类型
      }
      
      @Entity
      @IdClass(DependentId.class)
      public class Dependent{
        @Id String name;
        @Id
        @JoinColumns({
          @JoinColumn(name="FK1", referencedColumnNmae="firstName"),
          @JoinColumn(name="FK2", referencedColumnName="lastName")
        })
        @ManyToOne Employee emp;
        
        // 样例查询
        SELECT d
        FROM Dependent d
          
        WHERE d.name='Joe' AND d.emp.firstName='Sam'
      }
      
      //案例(b) : 从属实体使用EmbeddedId, empPK属性的类型与Employee主键的类型相同。EmployeeId类需要被注解为可嵌入的,或在XML描述符中被表示为可嵌入的类。
      
      @Embeddable
      public class DependentId{
        String name;
        EmployeeId empPK;
      }
      
      @Entity
      public class Dependent{
        @EmbeddedId DependentId id;
        ...;
        @MapsId("empPK")
        @JoinColumns({
          @JoinColumn(name="FK1", referencedColumnName="firstName"),
          @JoinColumn(name="FK2", referencedColumnName="lastName")
        })
        @ManyToOne Employee emp;
      }
      //样例查询
      SELECT d
      FROM Dependent d
      WHERE d.id.name='Joe' and d.emp.firstName='Sam';
      //下面语句将会查询同样的结果
      SELECT d
      FROM Dependent d
      WHERE d.id.name='Joe' and d.id.empPK.firstName = 'Sam';
      
      
  • 例子3 父级实体使用EmbeddedId:

    • @Embeddable
      public class EmployeeId{
        String firstName;
        
        String lastName;
      }
      
      @Entity
      public class Employee{
        @EmbeddedId EmployeeId empId;
      }
      
      //案例(a)依赖实体使用IdClass
      
      public class DependentId{
        String name;  // 匹配@Id属性的名称
        EmployeeId emp; // 匹配@Id属性的名称和匹配Employee的embedded id类型
      }
      @Entity
      @IdClass(DependentId.class)
      public class Dependent{
        @Id
        @Column(name="dep_name")//默认列名被重写
        String name;
        
        @Id
        @JoinColumns({
          @JoinColumn(name="FK1", referencedColumnName="firstName"),
          @JoinCOlumn(name="FK2", referencedColumnName="lastName")
        })
        @ManyToOne Employee emp;
      }
      //样例查询
      SELECT d 
      FROM  Dependent d
      WHERE d.name='Joe', and d.emp.empId.firstName='Sam';
      
      //案例(b) 依赖实体使用EmbeddedId:
      
      @Embeddable
      public class DependentId{
        String name;
        EmployeeId empPK; //对应Employee Pk的类型
      }
      
      @Entity
      public class Dependent{
        
        //默认列表将被重写为"name"
        @AttributedOverride(name="name", column=@Column(name="dep_name"))
        @EmbeddedId DependentId id;
        ...;
        @MapsId("empPK")
        @JoinColumns({
          @JoinColumn(name="FK1", referencedColumnName="firstName"),
          @JoinColumn(name="FK2",referencedColumnName="lastName" )
        })
        @ManyToOne Employee emp;
      }
      // 查询样例
      SELECT d
      FROM Dependent d
      WHERE d.id.name='Joe' and d.emp.empId.firstName='Sam';
      // 以下查询结果一致
      SELECT d
      FROM Dependent d
      WHERE d.id.name='Joe' and d.id.empPK.firstName='Sam';
       
      
    • 例子4: 父实体有一个简单主键

      • @Entity
        public class Person{
          @Id String ssn;
          ...;
        }
        //案例(a) 从属实体具有单个主键属性,该主键属性由关系属性映射。MedicalHistory的主键的类型为字符串
        
        @Entity
        public class MedicalHistory{
          // 默认join列名将被重写
          @Id
          @OneToOne
          @JoinColumn(name="FK")
          Person patient;
          ...
        }
        
        //案例查询
        SELECT m
        FROM MedicalHistory  m
        WHERE m.patient.ssn = '123-45-6789';
        
        //案例(b) 从属实体具有与该关系相对应的单个主键属性,主键属性与父实体的主键具有相同的基本类型。这应用于关系属性的MapsId注解指定主键由关系属性映射。
        
        
        @Entity
        public class MedicalHistory{
          
          @Id String id; // 重写是不允许的
          
          //默认join列重写
          @MapsId
          @JoinColumn(name="FK")
          @OneToOne Person patient;
          ...
        }
        
        //查询示例
        SELECT m
        FROM MedicalHistory m WHERE m.patient.ssn='123-45-6789';
        
        
  • 例子 5:父实体使用IdClass, 依赖的主键类与父实体的类型一样

    • public class PersonId{
        String firstName;
        String lastName;
      }
      
      @Entity
      @IdClass(PersonId.class)
      public class Person{
        @Id String firstName;
        @Id String lastName;
        ...
      }
      
      //案例(a) 依赖实体使用IdClass
      
      @Entity
      @IdClass(PersonId.class)
      public  class MedicalHistory{
        @Id
        @JoinColumns({
          @JoinColumn(name="FK1", referencedColumnName="firstName")
          @JoinColumn(name="FK1", referencedColumnName="lastName")
        })
        @OneToOne
        Person patient;
        ...
      }
      // 示例查询
      SELECT m
      FROM MedicalHistory m WHERE m.patient.firstName='Charles';
      
      //案例(b) 依赖实体使用EmbeddedId 和 MapsId 注解,PersonId 类需要被标记为Embeddable或在XML描述符中标记为内嵌属性
      @Entity
      public class MedicalHistory{
        // 所有属性映射关系,AttributeOverride 是不允许的
        @EmbeddedId PersonId id;
        
        ...;
        @MapsId
        @JoinColumns({
          @JoinColumn(name="FK1", referencedColumnName="firstName"),
          @JoinColumn(name="FK2", referencedColumnName="lastName")
        })
        @OneToOne Person patient;
        ...
      }
      //示例查询
      SELECT m
      FROM MedicalHistory m
      WHERE m.patient.firstName = 'Charles';
      
      // 请注意,以下替代查询将产生相同的结果
      SELECT m
      FROM MedicalHistory m
      WHERE m.id.firstName = 'Charles'
        
      
  • 例子 6、父实体使用EmbeddedId, 依赖的主键要与父实体的类型一致

    • @Embeddable
      public class PersonId{
        String firstName;
        String lastName;
      }
      
      @Entity
      public class Person{
        @EmbeddedId PersonId id;
        ...;
      }
      //案例(a) 依赖类使用IdClass
      
      @Entity
      @IdClass(PersonId.class)
      public class MedicalHistory{
        
        @Id
        @OneToOne
        @JoinColumns({
          @JoinColumn(name="FK1", referencedColumnName="firstName"),
          @JoinColumn(name="FK2", referencedColumnName="lastName")
        })
        Person patient;
        ...;
      }
      
      // 案例(b) 依赖类使用EmbeddedId:
      
      @Entity
      public class MedicalHistory{
        // 所有属性必须由这个关系映射, AttributeOverride是不允许的
        @EmbeddedId PersonId id;
        ...;
         
         @MapsId
         @JoinColumns({
           @JoinColumn(name="FK1", referencedColumnName="firstName"),
           @JoinColumn(name="FK2", referencedColumnName="lastName")
         })
         @OneToOne
         Person patient;
        ...
        
      }
      

2.5、Embeddable Classes(可嵌入类)

  • 一个实体可能使用其他细粒度类去表示实体的状态。这些类的实例并不像其实体的实例,它们没有持久化身份的标记,取而代之是只是存在部分实体的状态信息,一个实体可能用嵌入集合作为单个值 的嵌入属性。可嵌入类可能使用Map的键和值。 可嵌入类严格属于它们持有的实体上,它不会再持久实体之间共享,如果尝试去在多个实体中共享可嵌入对象将会报未定义语法错误。
  • 对于实体,可嵌入必须遵循第2.1节中指定的要求,可嵌入类不能被标记为Entity, 可嵌入类必须表标记为Embeddable 或在XML描述中进行标记,对于嵌入对象的访问类型具体描述在2.3节"访问类型"。
  • 一个可嵌入类可能用于表示另一个可嵌入类的状态。
  • 一个可嵌入类(包含带有其他可嵌入类的可嵌入类)可能包含基础类型集合或其他可嵌入类 16 ^{16} 16
  • 一个可嵌入类可能包含对实体的关系或实体的集合,由于可嵌入类的实例没有持久性身份, 这个关系是从引用实体是entity,它包含可嵌入实例并不是可嵌入本身 17 ^{17} 17. 一个可嵌入类可以用于作为嵌入id或作为map的键必须不能包含这个关系。
  • 额外关于可嵌入类要求和限制的描述参见2.6节
  • 备注
    • [16] : 在可嵌入类中直接或间接环形依赖是不允许的
    • [17] : 一个实体不能与另一个实体(或本身)的可嵌入类具有单向关系

2.6、可嵌入类和基本类型的集合

  • 实体或可嵌入类的一个持久字段或属性可能对应基本类型的集合或可嵌入类(”元素集合") ,当此类集合由ElementCollection注解指定时,将通过第11.1.8节中定义的集合表进行映射。如果未为收集值的字段或属性指定ElementCollection注解(或XML等效项),则适用第2.8节的规则
  • 包含在元素集合中的可嵌入类(包含其他可嵌入类的嵌入类)不得包含元素集合,也不得包含于多对一或一个以外的实体的关系一对一的关系。可嵌入类必须在这种关系的拥有方,并且该关系必须通过外键映射进行映射。(请参阅第2.9节)

2.7、Map集合

  • 元素和实体关系的集合可以表示为java.util.Map集合
  • 这个map的键和值都是独立,他们可以是基本类型,可嵌入类或实体
  • ElementCollection,OneToMany 和ManyToMany注解被用于指定一个map作为元素的集合或如下的实体关系:当map的值是基本类型或可嵌入类,那么用于ElementCollection注解,当map的值是实体时,那么将用OneToMany 或ManyToMany注解。
  • 表示为java.util.Map集合的双向关系仅支持在关系的一侧使用Map数据类型

2.7.1、Map 键

  • 如果这个map键类型是基本类型,那么使用MapKeyColumn注解用于指定这个列的映射,如果MapKeyColumn注解不能指定,那么MapKeyColumn注解默认值将会应用,参见第11.1.33.
  • 如果这个map键类型是可嵌入类,那么map的键列的映射将会使用可嵌入类的默认列。(参见11.1.9节,“列注解”),AttributeOverried和AtrributeOverrides注解可以用于重写这些映射,参见11.1.4节或11.1.5节,如果一个可嵌入类用于作为map的键,一个可嵌入类必须实现hashCode和equals方法,且与数据库对应列映射一致。
  • 如果map的键类型是一个实体,MapKeyJoinColumn和MapKeyJoinColumns注解用于指定map的键。 如果引用实体是一个简单主键并且MapKeyJoinColumn注解没有指定,MapKeyJoinColumn 注解的默认值(参见11.1.35)
  • 如果在类型为java.util.Map的关系属性的声明中未使用Java泛型类型,则必须使用MapKeyClass注解指定map键的类型。
  • MapKey注解用于指定特殊情况,其中map键本身就是主键或实体的持久字段或属性,即map的值, 指定MapKey时不使用MapKeyClass注解。

2.7.2、 Map的值

  • 当map的值类型是基本类或可嵌入类,集合表格用于映射map,如果Java泛型类型没有使用, ElementCollection注解中的targetClass元素必须用于指定map值的类型, 这个map默认列映射值衍生规则是根据CollectionTable注解定义(参见11.1.8) ,Column注解用于重写map默认的基本类型,AttributeOverride(s) 和 AssociationOverride(s)注解用于重写可嵌入类的map值映射。
  • 当map的值类型是实体,一个join表用于映射这map多对多的关系,或默认一对一单边关系。如果这个关系是多边的(一对多或多对一关系),默认情况下在实体中被映射map的值,如果java 泛型没有使用,那么OneToMany或ManyToMany中的targetEntity元素用于必须指定map的值,默认映射参见2.10节

2.8、 对应非关系字段或属性的默认映射

  • 如果未使用第11章中定义的映射注解之一对除关系属性以外的持久字段或属性进行注解(或在XML描述符中未指定等效的映射信息),则按顺序应用一下默认映射规则:
    • 如果类型是使用Embeddable注解进行标记的类,则其映射方式与使用Embedded注解对字段或属性进行注解的方式相同,请参见第11.1.15和11.1.16节
    • 如果字段或属性的类型是以下类型之一,则其映射方式与将其注解为Basic时的映射方式相同:Java基本类型和对应的包装类, java.lang.String, java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.time.LocalDate, java.time.LocalDateTime, java.time.OffsetTime, java.time.OffsetDateTime, byte[] , Byte[], char[] Character[] 枚举和任何其他类型实现序列化(Serializable) 参见11.1.6, 11.1.18, 11.1.28和11.1.53节
  • 如果没有注解并且以上规则均不适用,则是错误的。

2.9、实体关系

  • 实体之间的关系可能是一对一、一对多,多对一,或多对多,关系也是多态的
  • 如果两个实体之间存在关联,则以下关系建模注解之一必须应用引用实体的相应持久属性或字段:OneToOne, OneToMany, ManyToOne, ManyToMany, 对于未指定目标类型的关联(例如Java的集合类的泛型类型的地方),有必要指定作为关系目的实体 16 ^{16} 16。等效的XML元素可以用作这些映射注解的替代方法。
  • 这些注解反映了关系数据库架构建模中的常见做法。使用关系建模注解可以完全默认关联到相对关联到相对数据库架构的对象/关系映射,以提供易于开发的功能。第2.10节”关系映射默认值"中对此进行了描述。
  • 关系可以是双向的或单向的,双向关系既有正侧(拥有)和反侧(非拥有),单向关系只有一个拥有方,关系的拥有方确定对数据库中关系的更新,如第3.2.4节所述。
  • 以下规则适用于双向关系:
    • 双向关系的反向必须通过使用OneToOne, OneToMany或ManyToMany注解的maptedBy元素来引用其拥有的一面,mappingBy元素指定关系的所有者的实体中的属性或字段。
    • 一对多/多对一双向关系的多面必须是拥有方,因此无法再ManyToOne注解上指定mappingBy元素。
    • 对于一对一的双向关系,拥有方对应于包含相应外键的方向
    • 对于多对多双向关系,任何一方都可能是拥有方。
  • 关系模型注解约束使用的是cascade=REMOVE规范, 这cascade=REMOVE规范应该只应用于关联指定OneToOne或OneToMany ,应用程序应用cascade=REMOVE去关联其他关系移植性不好
  • 关联被指定为OneToOne或OneToMany时还支持使用orphanRemoval选项,当orphanRemoval生效时,将发生以下行为:
    • 如果从关系中删除了作为关系目标的实体(通过将关系设置为null或从关系集合中删除实体),则删除操作将应用于孤立的实体。当移除操作应用在flush操作的时候,orphanRemoval功能适用用于其父实体主要"拥有"的实体。可移植应用程序不得不依赖于特定的删除顺序,并且不得将已孤立的实体重新分配给另一个关系,也不能尝试将其持久化。如果要孤立的实体是分离的,新增或已删除的实体,则orphanRemoval的语义不适用。
    • 如果移除操作被应用于管理源实体,移除操作将依据3.2.3规则被级联到目标的关系(所以强调的是为这个关系指定cascade=REMOVE是没有必要的)
  • 2.10节,“关系默认映射”,为实体关系定义关系的默认映射。在2.10节描述了额外映射注解(例如列和表映射注解)可能指定重写或进一步优化这个默认映射和映射的策略。
  • 此外,此规范还要支持以下替代映射策略:
    • 外键映射含义就是单边映射一对多的关系,JoinColumn注解或与之对象XML元素必须用于指定没有默认映射,参见11.1.25.
    • JoinTable注解的含义用于单边和双边映射一对一关系,双边多对一或一对多关系和单边多对一关系。JoinTable注解或与之对象XML元素必须用于指定没有默认映射,参见11.1.27.
  • 此映射注解必须指定在关系拥有的一方。映射默认值任何重写都必须与关系模型注解指定的一致。例如,多对一关系映射被指定时,对应这种关系它就不允许在外键上指定一个唯一键约束。
  • 持久层厂商处理关系的对象/关系映射时,包括实体类的元数据中指定的关系的加载和存在到数据库。以及数据库中指定的关系的参照完整性(例如,通过外键约束)
    • 请注意,正是该应用程序负责维护运行时关系的一致性,例如,当应用程序更新时,在运行时要确保双向关系的”一个"和“多个”方面彼此一致。
  • 如果没有从数据库中获取的实体的多值关系的关联实体,则持久性提供程序负责返回空集合作为该关系的值。

2.10、默认关系映射

  • 本节定义了适用于使用OneToOne, OneToMany, ManyToOne和ManyToMany关系模型注解的映射默认值,当使用XML描述符表示关系基数时,将应用相同的映射默认值。

2.10.1、多边OneToOne的关系

  • 假设:

    • 实体A引用了实体B的单个实例
    • 实体B引用了实体A的单个实体
    • 实体被指定为关系拥有者。
  • 如下默认映射应用:

    • 实体A映射到表名称为A
    • 实体B映射到表名称为B
    • 表A包含一个指定表B的外键,这个外键列名称由以下内容组成:关系属性的名称或实体A的字段;"_";表B主键列的名称。外键列与表B的主键具有相同的类型,并且对它具有唯一的键约束。
  • 例子

    • @Entity
      public class Employee{
        
        private Cubicle assignedCubicle;
        
        @OneToOne
        public Cubicle getAassignedCubicle(){
          return assignedCubicle;
        }
        
        public void setAssignedCubicle(Cubicle cubicle){
          this.assignedCubicle = cubicle;
        }
        ...
      }
      
      
      @Entity
      public class Cubicle{
        private Employee residentEmployee;
        
        @OneToOne(mappedBy="assignedCubicle")
       	public Employee getResidentEmployee(){
          return residentEmployee;
        }
        
        public void setResidentEmployee(Employee employee){
          this.residentEmployee = employee;
        }
        ...
      }
      
      
  • 在这个例子中:

    • 实体Employee引用了实体Cubicle的单个实例
    • 实体Cubicle引用了实体Employee的单个实体
    • 实体Employee被指定为关系拥有者。
  • 以下映射默认设置适用:

    • 实体Employee映射到表名称:EMPLOYEE
    • 实体Cubicle映射到表名称:CUBICLE
    • 表EMPLOYEE包含指定CUBICLE的外键,这个外键列名称为ASSIGNEDCUBICLE_<CUBICLE的主键>,其中CUBICLE的主键表示表CUBICLE主键列的名称。外键列与CUBICLE的主键具有相同的类型,并且对它唯一的键约束。

2.10.2、双向ManyToOne /OneToMany 的关系

  • 假设:

    • 实体A引用了实体B的单个实体
    • 实体B引用了实体A的集合
  • 实体A必须是关系的拥有者

  • 以下映射默认设置适用:

    • 实体A映射表名A
    • 实体B映射表名B
    • 表A包含一个表B的外键, 外键列名称由以下内容组成:实体A的关系属性或字段的名称;“_" 表B的主键列名称。外键列名与表B主键类型一样。
  • 例子

    • @Entity
      public  class Employee {
        private Department department;
        
        @ManyToOne
        public Department getDepartment(){
          return department;
        }
        public void setDepartment(Department department){
          this.department = department;
        }
        ....
      }
      
      
      @Entity
      public class Department{
        private Collection<Employee> employees = new HashSet();
        
        @OneToMany(mappedBy="department")
        public Collection<Employee> getEmployees(){
          return employees;
        }
        
        public void setEmployees(Collection<Employee> employees){
          this.employees = employees;
        }
        ...
      }
      
    • 在这个例子中

      • Employee实体引用Department的实体实例对象
      • Department实体引用Employee实体的集合对象
      • Employee实体是关系拥有者。
    • 接下来应用映射默认值:

      • Employee实体映射表名EMPLOYEE
      • Department实体映射表名DEPARTMENT
      • 表EMPLOYEE包含DEPARTMENT的外键,这个外键列名为DEPARTMENT_, 表示表DEPARTMENT主键名称,外键列名和DEPARTMENT主键列具有相同的类型。

2.10.3、单向单值关系

  • 假设:
    • 实体A引用实体B的单例
    • 实体B并没有引用实体A
  • 单边关系只有拥有的一边, 在上述情况下关系拥有者是实体A
  • 单向单值关系模型场景要么是单边OneToOne关系或单边ManyToOne关系
2.10.3.1 单向OneToOne 关系
  • 接下来应用映射默认值:

    • 实体A映射表名A
    • 实体B映射表名B
    • 表A包含引用表B的外键, 外键列名称由以下内容组成 实体A的关系属性和字段的名称;"_";表B主键列名称。 外键列类型与表B的主键类型一样,这是一个唯一主键约束。
  • 例子

    • @Entity
      public class Employee{
        private TravelProfile profile;
        
        @OneToOne
        public TravelProfile getProfile(){
          return profile;
        }
        public void setProfile(TravelProfile profile){
          this.profile = profile;
        }
        ...
      }
      
      @Entity
      public class TravelProfile{
        ...
      }
      
      
    • 在这个例子

      • Employee实体引用TravelProfile实体单例
      • TravelProfile实体没有引用Employee实体
      • Employee是这个关系拥有者
    • 接下来应用映射默认值:

      • Employee实体映射表名EMPLOYEE
      • TravelProfile实体映射表名为TRAVELPROFILE.
      • EMPLOYEE包含TRAVELPROFILE表的外键,这个外键列名为PROFILE_, 表示TRAVELPROFILE表主键列名,外键列名与TRAVELPROFILE的主键的类型一致, 这个是唯一键约束
2.10.3.2 、单向 ManyToOne 关系
  • 接下来应用映射默认值:

    • 实体A映射表A
    • 实体B映射表B
    • 表A包含表B的外键, 外键列名称由以下内容组成:实体A的关系属性或字段的名称;"_" ;表B主键列名, 外键列类型与表B的主键类型一样。
  • 例子:

    • @Entity
      public class Employee{
        private Address address;
        
        @ManyToOne
        public Address getAddress(){
          return address;
        }
        
        public void setAddress(Address address){
          this.address = address;
        }
        ...
      }
      @Entity
      public class Address{
        ...
      }
      
    • 在这个例子中:

      • Employee实体引用Address的实体单例
      • Address实体并没引用Employee实体
      • Employee实体使用关系拥有者
    • 接下来应用映射默认值:

      • Employee映射表名EMPLOYEE
      • Address实体映射表名ADDRESS。
      • EMPLOYEE表包含ADDRESS表的外键,这个外键列名为ADDRESS_ , 表示ADDRESS表主键列名称,外键列名和ADDRESS主键类型是一致的。

2.10.4、双向ManyToMany关系

  • 假设:

    • 实体A引用实体B的集合
    • 实体B引用实体A的集合
    • 实体A是关系拥有者。
  • 接下来应用映射默认值:

    • 实体A映射表名A
    • 实体B映射表名B
    • 这里是一个join表关系且名称为A_B(关系拥有者写在前面)。这个join表有两个外键列。一个外键列引用表A且它有与表A主键列相同类型,这个外键名称由以下内容组成:实体B关系属性或字段名称;"_“表A主键列名称。另一个外键列引用表格B且它有与表B主键列相同类型,这个外键名称有以下内容组成:实体A关系属性或字段名称;”_";表B主键列名称。
  • 例子

  • @Entity
    public class Project{
      
      private Collection<Employee> employees;
      
      @ManyToMany
      public Collection<Employee> getEmployees(){
        return employees;
      }
      
      public void setEmployees(Collection<Employees> employees){
        this.employees = employees;
      }
      ...
    }
    
    
    @Entity
    public class Employee{
      
      private Collection<Project> projects;
      
      @ManyToMany(mappedBy="employees")
      public Collection<Project> getProjects(){
        return projects;
      }
      public void setProjects(Collection<Project> projects){
        this.projects = projects;
      }
      ...
    }
    
  • 在这个例子中:

    • Project实体引用包含Employee实体的集合
    • Employee实体引用包含Project实体的集合对象
    • Project实体是关系的拥有者
  • 接下来应用映射默认值:

    • Project实体映射表名为PROJECT
    • Employee实体映射表名为EMPLOYEE
    • join表的名称为PROJECT_EMPLOYEE( 拥有关系名称写在前面), 这个join表有两个外键列, 一个外键列引用PROJECT 表并且与PROJECT的主键的类型一致。 这个外键表名称为PROJECTS_ , 表示 PROJECT表的主键名称,另一个外键引用EMPLOYEE表并且与EMPLOYEE的主键的类型一致,这个外键表名称为EMPLOYEE_, 表示EMPLOYEE表的主键名称。

2.10.5、单向多值关系

  • 假设:
    • 实体A引用集合实体B
    • 实体B并不引用实体A
  • 单向关系只有一个拥有方,在上面情况下必须是实体A
  • 单向多值关系模型要么指定为单向OneToMany关系或 单向 ManyToMany 关系
2.10.5.1 单向OneToMany关系
  • 以下映射默认设置适用:

    • 实体A映射表名A
    • 实体B映射表名B
    • join表时候名称为A_B(拥有关系者的名称写在前面)。join表有两个外键列,一个外键列引用表A并且与表A的主键类型一致。外键列名称由以下内容组成:实体A名称;"_";表A的主键名称,另一个外键引用表B并且表B的主键类型一致,外键列名称由以下内容组成:实体B名称;"_";表B的主键名称
  • 例子

    • @Entity
      public class Employee{
        private Collection<AnnualReview> annualReviews;
        
        @OneToMany
        public Collection<AnnualReview> getAnnualReviews(){
          return annualReviews;
        }
        
        public void setAnnualReviews(Collection<AnnualReview> annualReviews){
          this.annualReviews = annualReviews;
        }
        ...
      }
      
      @Entity
      public class AnnualReview{
        ...
      }
      
    • 在这个实例中:

      • Employee实体引用AnnualReview实体的集合
      • AnnualReview实体不引用Employee实体
      • Employee是关系拥有者
    • 以下映射默认设置适用:

      • Employee实体映射表名EMPLOYEE
      • AnnualReview实体映射表名ANNUALREVIEW
      • join表名名称为EMPLOYEE_ANNUALREVIEW(关系拥有者的名称写前面),join表有两个外键列,一个外键列名引用EMPLOYEE且与EMPLOYEE主键类型一致,外键列名为EMPLOYEE_, EMPLOYEE_ 表示EMPLOYEE主键名称,另一个外键引用。 另一个外键列引用ANNUALREVIEW且与ANNUALREVIEW主键类型一致,外键列名为ANNUALREVIEW_, ANNUALREVIEW_ 表示ANNUALREVIEW主键名称. 在引用表ANNUALREVIEW的外键上存在唯一的键约束。
2.10.5.2、 单向 ManyToMany 关系
  • 以下映射默认设置适用:

    • 实体A映射表名A
    • 实体B映射表名B
    • join表名名称为A_B(拥有者名称写前面), join表有两个外键列, 一个外键列引用A表且表A的主键类型一致。外键名称由以下内容组成:实体A名称;"_";A的主键名称, 另一个外键引用表B且与表B主键类型一致。外键名称由以下内容组成:实体A名称;"_";B的主键名称
  • 例子

    • @Entity
      public class Employee{
        private Collection<Patent> patents;
        
        @ManyToMany
        public Collection<Patent> getPatents(){
          return patents;
        }
        
        public void setPatents(Collection<Patent> patents){
          this.patents = patents;
        }
        ...
      }
      
      @Entity
      public class Patent{
        ...
      }
      
    • 在这个例子中:

      • Employee实体引用Patent实体的集合对象
      • Patent没有引用Employee实体
      • Employee实体是关系拥有者
    • 以下映射默认设置适用:

      • Employee实体映射表名为EMPLOYEE
      • Patent映射表名称为PATENT
      • join表名称为EMPLOYEE_PATENT( 拥有者关系名称写在前面),join表有两个外键列,一个外键列引用EMPLOYEE表且与EMPLOYEE主键类型一致。这个外键名称为EMPLOYEE_ , 表示EMPLOYEE表的主键, 其他外键列引用PATENT表且与PATENT主键类型一致。这个外键列名称为PATENTS_ , 表示PATENT的主键名称。

2.11 、Inheritance(继承)

  • 实体可能继承其他实体类,实体是支持继承的,多态关联和多态查询
  • 抽象类和普通类都可以作为实体,它们可以用@Entity注解标记映射成实体和并做为实体查询
  • 实体类可以扩展非实体类,同样非实体类也可以扩展实体类。
  • 在以下各节中将进一步描述这些概念

2.11.1、抽象实体类

  • 一个抽象类可以做为实体,抽象实体类仅不同于普通实体类它不会直接被实例化, 一个抽象时态可以映射为实体和作为查询的目标(它将操作和/或检索其具体子类的实例)

  • 抽象实体类使用Entity注解进行标记,或在XML描述符中表示为实体。

  • 如下例子展示在实体继承情况下抽象实体的使用

  • 例子: 抽象类作为实体

    • @Entity
      @Table(name="EMP")
      @Inheritance(strategy=JOINED)
      public abstract class Employee{
        
        @Id protected Integer empId;
        @Version protected Integer version;
        @ManyToOne protected Address address;
        ...
      }
      
      
      
      @Entity
      @Table(name="FT_EMP")
      @DiscriminatorValue("FT")
      @PrimaryKeyJoinColumn(name="FT_EMPID")
      public class FullTimeEmployee extends Employee{
        // 继承 empId 但是映射本类的FT_EMP.FT_EMPID
        // 继承 version ,映射 EMP.VERSION
        // 继承 address 映射 EMP.ADDRESS 外键
        // 默认 FT_EMP.SALARY
        protected Integer salary;
        ...
      }
      
      
      
      @Entity
      @Table(name="PT_EMP")
      @DiscriminatorValue("PT")
      // PK列(主键)是PT_EMP.EMPID  由于PrimaryKeyJoinColumn默认值
      public class PartTimeEmployee extends Employee{
        protected Float hourlyWage;
        ...
      }
      

2.11.2、映射超类

  • 一个实体类可能继承超类并且提供实体持久状态和映射信息, 但是它自己本身不是实体, 通常,映射超类的目标是被定义状态并且有映射信息,它一般关联多个实体。

  • 映射超类不像实体类,它不具有可查询性且禁止把它作为参数传递给EntityManger 和Query而进行操作。映射超类定义持久关系必须单向的。

  • 抽象类和普通类都可能被指定为映射超类, 可以通过MappedSuperclass注解(或在XML描述符元素mapped_superclass)标记为映射超类

  • 指定为映射超类的类没有为其定义单独的表。其映射信息将应用于从其继承的实体。

  • 可以以与实体相同的方式映射指定为映射超类的类,除了映射将仅应用于其子类外, 因为映射超类本身不存在任何表。当应用到超类上,这个继承映射将会应用到超类上下文表中。可以在超类中使用AttributeOverriede和AssociationOverride (或与之对应XML元素)进行映射信息的重写。

  • 所有其他实体映射默认应用等同于一个类设计为作为映射的超类

  • 下面例子阐述定义一个普通类作为映射超类

  • 例子: 普通类作为映射超类

    • @MappedSuperclass
      public class Employee{
        @Id protected Integer empId;
        @Version protected Integer version;
        @ManyToOne @JoinColumn(name="ADDR")
        protected Address address;
        public Integer getEmpId() { ... }
      	public void setEmpId(Integer id) { ... } 
        public Address getAddress() { ... }
      	public void setAddress(Address addr) { ... }
      }
      //定义表名为FTEMPLOYEE
      @Entity
      public class FTEmployee extends Employee{
        // 继承 empId 映射 FTEMPLOYEE.EMPID
        // 继承 version 映射 FTEMPLOYEE.VERSION
        // 继承 address 映射 FTEMPLOYEE.ADDR 外键
        // 默认是 FTEMPLOYEE.SALARY
        protected Integer salary;
        
        public FTEmployee(){}
        public Integer getSalary(){...}
        public void setSalary(Integer salary){...}
      }
      
      @Entity
      @Table(name="PT_EMP")
      //重写映射关系
      @AssociationOverride(name="address", joincolumns=@JoinColumn(name="ADDR_ID"))
      public class PartTimeEmployee extends Employee{
        // 继承 empId 映射 PT_EMP.EMPID
        // 继承 version 映射 PT_EMP.VERSION
        // 重写 address 映射 PT_EMP.ADDR_ID 外键
        @Column(name="WAGE")
        protected Float hourlyWage;
        public PartTimeEmployee(){}
        
        public Float getHourlyWage(){...}
        public void setHourlyWage(Float wage){...}
      }
      

2.11.3、实体继承层次结构中的非实体类

  • 实体类可以有非实体超类,它可以普通类或抽象类

  • 非实体超类仅用于行为的继承,非实体超类的状态不是持久性的,继承非实体超类的实体任何状态继承都是非持久性。非持久状态是不能够被实体管理器进行管理,任何在超类上注解都会被忽略。

  • 非实体类不能作为参数调用EntityManger或Query接口(也包含非实体继承实体的类)且无法承受任何映射信息

  • 下面例子阐述使用非实体类作为实体的超类

  • 例子:非实体 超类

    • public class Cart {
        protected Integer operationCount; // transient state
        public Cart() {operationCount=0;}
        public Integer getOperationCount(){return operationCount;}
        public void incrementOperationCount(){operationCount++;}
      }
      
      
      @Entity
      public class ShoppingCart extends Cart{
        Collection<Item> items = new Vector<Item>();
        public ShoppingCart(){super();}
        ...
        @OneToMany
         public Collection<Item> getItems() {return items;}
        public void addItem(Item item){
          items.add(item);
          incrementOperationCount();
        }
      }
      

2.12、继承映射策略

  • 类层次结构的映射是通过元数据指定的。
  • 有三种基本策略应用于 映射一个类或类层次结构映射数据库
    • 每个类层次都是单表
    • join子类策略,其中子类特有的字段被映射到一个单独的表,而不是父类共有的字段,然后执行join以实例化该子类。
    • 每个普通实体类映射一张表
  • 为了支持每个类层次映射单表的策略和join子类策略,需要实现的方式:
    • 在此版本中,对每个具体类继承映射策略的表的支持是可选的。使用此映射策略的应用程序将不可移植。
    • 本规范不要求在单个实体继承层次结构内支持继承策略的组合。

2.12.1、每个继承层次对应单表

  • 在这个策略中,层次结构中所有类都映射一个单表,该表具有用作"区分符列"的列,即其值标识该行表示的实例所属的特定子类的列。
  • 此映射策略为实体之间的多态关系以及跨类层次结构的查询提供了良好的支持。
  • 但是,它有一个缺点,那就是要求与特定于子类的状态相对应的列可以为空。

2.12.2、Join子类策略

  • 在join子类策略中,单表表示类层次结构的root类,每个子类都可以通过分离表表示且包含指定子类的这些字段(不是继承与超类) 也就这些列表示他们主键的列,子类表的主键列名称用作超类表的主键的外键。
  • 这个策略对于实体间多态关系提供支持。
  • 但它有一个缺点是需要执行一个或多个join操作以实例化子类的实例。在较深的类层次结构中,这可能导致无法接受的性能。 遍历类层次结构的查询同样需要join。

2.12.3、每个具体类表策略

  • 在这个映射策略中,每个类都映射一个单独表,这个类所有属性,包含继承属性都会映射到该表列
  • 这个策略有以下缺点:
    • 对多态关系支持很弱
    • 它通常要求针对打算在类层次结构范围内进行的查询发出SQL UNION查询(或每个子类单独的SQL查询)

2.13、数据库对象命名

  • 许多注解和注解元素包含数据库对象的名称或为了数据对象设置默认名称。

  • 关于引用数据库对象的名称解释,本规范要求以下内容。这些名称包括表名称,列名称和其他数据库元素,这些名称也包含来自默认设置的名称(例如,表名默认来自实体名称或一个列名默认来自于字段或属性名称)

  • 缺省情况下,必须将数据库对象的名称视为无界标识符,并按原样传递给数据库。

  • 举个例子,假设英语语言环境,则必须将以下内容作为无限制的标识符传递给数据库,以便对于所有符合SQL标准对“常规标识符"的要求(无限制)的数据库,它们都将被视为等效。标识符)和 “定界标识符”[2]:

    • @Table(name="Customer") 
      @Table(name="customer") 
      @Table(name="cUsTomer")
      
  • 同样,以下内容必须被视为等同:

    • @JoinColumn(name="CUSTOMER") @ManyToOne Customer customer;
      @JoinColumn(name="customer") @ManyToOne Customer customer;
      @ManyToOne Customer customer;
      
  • 要指定分隔标识符,必须使用以下方法之一:

    • 通过在对象/关系xml映射文件的persistence-unit-defaults元素内指定 元素,可以将用于持久性单元的所有数据库标识符指定为带分隔符的标识符,如果指定了元素,则不能覆盖它。
    • 可以按名称指定将数据库对象的名称解释为带分隔符的标识符,如下所示:
      • 使用注解,通过将名称用双引号引起来将名称指定定界标识符,从而对内部引号进行转义,例如@Table(name="\“customer\”")
      • 使用XML时,使用双引号将名称指定为带分隔的标识符,例如, <table name="&quot;customer&quot"
  • 以下注解包含其值与数据库标识符的名称相对应的元素,并且上述规则适用于这些元素,包括他们的使用嵌套在其他注解的使用范围内时:

    • EntityResult(discriminatorColumn 元素)
    • FieldResult(column 元素)
    • ColumnResult(name 元素)
    • CollectionTable(name,catalog,schema 元素)
    • Column(name, columnDefinition,table 元素)
    • DiscriminatorColumn(name, columnDefinition 元素)
    • ForeignKey(name,foreignKeyDefinition 元素)
    • Index(name, columnList 元素)
    • JoinColumn(name,referencedColumnName, columnDefinition, table 元素)
    • JoinTable(name,catalog, schema 元素)
    • MapKeyColumn(name, columnDefinition,table 元素)
    • MapKeyJoinColumn(name, referencedColumnName, columnDefinition, table 元素)
    • NamedStoredProcedureQuery(procedureName 元素)
    • OrderColumn(name, columnDefinition 元素)
    • PrimaryKeyJoinColumn(name, referencedColumnName, columnDefinition 元素)
    • SecondaryTable(name, catalog, schema 元素)
    • SequenceGenerator(sequenceName, catalog, schema 元素)
    • StoredProcedureParameter(name 元素)
    • Table(name,catalog,schema 元素)
    • TableGenerator(table, catalog, schema, pkColumnName, valueColumnName 元素)
    • UniqueConstraint(name, columnNames元素)
  • 以下XML元素和类型包含其值对应于的元素或属性上面规则适用的数据库标识符的名称:

    • entity-mappings(schema, catalog 元素)
    • persistence-unit-defaults(schema, catalog 元素)
    • collection-table(name,catalog,schema 属性)
    • column(name,table,column-definition 属性)
    • column-result(name属性)
    • discriminator-column(name, column-definition 属性)
    • entity-result(discriminator-column 属性)
    • field-result(column 属性)
    • foreign-key(name, foreign-key-definition 属性)
    • index(name 属性, column-list 元素)
    • join-column(name, referenced-column-name, column-definition, table 属性)
    • join-table(name, catalog, schema 属性)
    • map-key-column(name,column-definition,table属性)
    • map-key-join-column(name, referenced-column-name, column-definition,table 属性)
    • named-stored-procedure-query(procedure-name 属性)
    • order-column(name, column-definition 属性)
    • primary-key-join-column(name, referenced-column-name, column-definition 属性)
    • secondary-table(name, catalog, schema 属性)
    • sequence-generator(sequence-name, catalog, schema 属性)
    • stored-procedure-parameter(name 属性)
    • table(name, catalog, schema属性)
    • table-generator(table, catalog, schema, pk-column-name, value-column-name 属性)
    • unique-constraint(name属性, column-name 元素)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值