【JavaEE】经典JAVA EE企业应用实战-读书笔记17

1-NN-N关联关系中,在实体中增加了一个Set集合属性,记录关联实体。

JPA允许使用Map集合来记录关联实体,使用Map集合时,Map集合的key类型通常是关联实体的主键类型,JPA提供了@MapKey来修饰Map集合。

下面是一个1-N的例子,此时Address实体没有采用Integer类型的主键,而是采用AddressPk类型的主键。AddressPk是一个满足主键要求的复合类,省略代码。此时1-N双向关联中AddressN的一端,需要增加Person类型是属性,用于记录关联实体,并使用#ManyToOne来修饰,省略代码。Person类是1-N双向关联中1的一端,使用Map来记录与之关联的多个关联实体。除了使用@OneToMany来修饰该Map属性之外,使用@MapKey来修饰,@MapKey必须指定一个name属性,属性值为当前实体的关联实体中标识属性的属性名。

public class Person{
  @OneToMany(cascade=CascadeType.ALL,mappedBy=”person”,targetEntity=Address.class)
  @MapKey(name=”pk”)
  private Map<AddressPk,Address> addresses=new HashMap<AddressPk,Address>();
}
对于1-N N-N 关联,当程序通过当前实体获取关联实体时,系统默认根据关联实体主键的升序来检索关联实体,如果需要改变默认的排序规则,可以将 @OrderBy @OneToMany @ManyToMany 结合使用。

@OrderBy可以指定一个value属性,该属性形如propertyName ASCpropertyName DESC的字符串。

public class Person{
  @OneToMany(...)
  @OrderBy(“detail DESC”)
  private Set<Address> address=new HashSet<Address>();
}

下面介绍继承关系的映射

本例包括PersonEmployeeManagerCustomer一共4个实体。

Person派生了EmployeeCustomer,而Employee又派生了Manager

EmployeeManager之间存在双向的N-1关联,EmployeeCustomer之间存在双向的1-N关联。

Person实体包括一个Address复合属性

public class Address{
  private String detail;
  private String zip;
  private Stirng country;
  // 省略其他构造器和方法
}

对于类与类之间的继承关系,JPA提供了3中映射策略

1)整个类层次对应一张表

2)连接子类的映射策略

3)每个具体类对应一张表

JPA对第三种没有提供很好的支持。

 

整个类层次对应一张表的映射策略

在这种策略下,上面的4个实体都存储在一张数据表中,包含很多列,这些数据列是所有实体的全部属性的总和。

另外为该表额外增加一列,使用该列来区分每行记录到底是哪个列的实例——这个列被称为辨别者列(discriminator

需要使用@DiscriminatorColumn来配置辨别者列,包括列的名称、类型等信息。

属性如下

1)columnDefinition:非必须,指定JPA使用该属性值指定的SQL片段来创建辨别者列

2)name:非必须,指定辨别者列的名称,默认值是DTYPE

3)discriminatorType:非必须,指定辨别者列的数据类型支持

DiscriminatorType.CHAR:是字符类型

DiscriminatorType.INTEGER:是整数类型

DiscriminatorType.STRING:是字符串类型

4)length:非必须,指定辨别者列的字符长度

JPA的整个类层中,使用@DiscriminatorColumn修饰整个继承树的根父类即可

除此之外,还需要使用@DiscriminatorValue来修饰每个子类,需要指定一个value属性即可,该value属性指定不同实体在辨别者列上的值。

@Entity
  @DiscriminatorColumn(discriminatorType=DiscriminatorType.STRING,name=”person_type”)
  @DiscriminatorValue(value=”普通人”)
  @Table(name=”person_table”)
  public class Person{
  ...
}

@Entity
@DiscriminatorValue(value=”普通人”)
public class Employee extends Person{
  ...
}

缺点:使用这个类层次对应一张表的继承映射策略时,器类中增加的属性映射的字段都不可有非空约束

当使用继承映射时,程序在persistence.xml文件中可以只列出一个实体类:PersonJPA会自动识别Person的所有子类,并负责把他们映射成实体

 

连接子类的映射策略

这种策略不是JPA继承映射策略的默认策略,因此需要在根类中使用@Inheritance指定映射策略,属性如下

1)InheritanceType.SINGLE_TABLE:整个类层次对应一张表的映射策略

2)InheritanceType.JOINED:连接子类的映射策略

3)InheritanceType.TABLE_PER_CLASS:每个具体类对应一张表的映射策略

采用这种策略时,父类实体保存在父类表中,而子类实体则由父类表和子类表共同存储。因为子类实体也是一个特殊的父类实体,因此必然也包括了父类实体的属性,于是将子类与父类公有的属性保存在父类表中,而子类增加的属性则保存在子类表中。

无需使用辨别者列,只要在根类中指定@InheritanceJOINED即可。

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name=”person_table”)
public class Person{
  ...
}

这样生成的子类表中都有一个根表的idJPA底层使用这个id来查询数据,拼接成一个记录,这种拼接在底层通过多表的join完成。

缺点:对于类继承层次较深的继承树来说,可能导致性能低下

 

每个具体类对应一张表的映射策略

在这种策略下,子类增加的属性可以有非空约束——即父类实例的数据保存在父类表中,子类实例的数据则保存在子表中。

与连接子类映射策略不同的是,子类实例的数据仅保存在子类表中,在父类表中没有任何记录。子类表的字段比父类表的字段要多,因为子类表的字段等于父类属性加子类增加的属性总和。

在这种策略下,如果单从数据库来看,几乎难以看出他们之间存在的继承关系,只是多个实体之间的主键值具有某种连续性——因此不能让数据库为各数据表自动生成主键值。因此,不能使用GenerationType.IDENTITYGenerationType.AUTO这两种主键生成策略。

在继承树的根类中需要使用@Inheritance修饰,并且设置指定strategy=InheritanceType.TABEL_PER_CLASS

@Entity
@Inheritance(strategy=InheritanceType.TABEL_PER_CLASS)
@Table(name=”person_table”)
public class Person{
  @Id
  private Integer id;
}

但在这种策略下,如果执行多态查询,也需要跨越多个数据表进行查询。

 

JPA特殊的用法

1)JPA的实体不仅可以是具体类,也可以是抽象类。以抽象类作为实现类的实体被称为抽象实体

2)JPA的实体可以继承非实体,这种父类被称为非实体父类;反过来,非实体类也可以继承实体类。

 

抽象实体

如果使用@Entity修饰一个抽象类,这个抽象类会变成一个抽象实体。与具体实体的区别在于:抽象实体的实现类是抽象的,因此程序无法创建抽象实体的实例。如果EntityManager对抽象实体进行查询,将会转换为对该抽象实体的所有子类的查询——这就是典型的多态查询

如果Person是一个抽象类

@Entity
@DiscriminatorColumn(discriminatorType=DiscriminatorType.STRING.name=”person_type”)
@DiscriminatorValue(value=”普通人”)
@Table(name=”person_table”)
public abstract class Person{
  ...
}
上面的例子中采用了整个类层对应一张表的继承策略,因此程序定义Employee Customer 时只需分别添加 @DiscriminatorValue 修饰,并指定不同的 value 即可。

虽然JPA可以把抽象实体当成普通实体来使用,但由于抽象实体本身不能创建实例,因此JPA无法直接创建、保存抽象实体。当程序需要创建、保存抽象实体时,只能从它的子类实体着手。

 

非实体父类

当应用试图让一个JPA实体继承一个非实体父类时,就需要使用@MappedSuperclass修饰该父类——这个父类可以是具体类,也可以是抽象类。

采用非实体父类,一样可以使用@Inheritance@DiscriminatorColumn@DiscriminatorValue修饰,但因为不再是实体类,因此不能使用@Entity修饰,而是使用@MappedSuperclass修饰并无须指定任何属性

@MappedSuperclass
@Inheritance(strategy=InheritanceType.JOINED)
public class Person{
  ...
}

上面的Person类有如下特征

1)在底层数据库中并没有对应的数据表

2)不能使用EntityManager执行保存、更新和删除

3)不能使用Query执行查询

 

当使用@MappedSuperclass映射非实体父类时,这个非实体父类不会生成对应的数据表,如果这个非实体父类和其他实体存在N-11-1关联关系,那他的所有子类都可能需要增加额外的外键列。

默认情况下,增加的外键列的列名完全相同——因此都是从父类继承得到的。如果希望非实体父类增加的外键列具有不同的定义,就需要使用@AssociationOverride@AssociationOverrides

@AssociationOverride属性说明

name:必须项,指定重定义子类的哪个属性。该属性应该是从非实体父类那里继承得到的,记录关联实体的属性

joinColumns:必须项,用于对外键列进行重定义

下面是一个非实体父类

@MappedSuperclass
@Inheritance(strategy=InheritanceType.JOINED)
public class Person{
  ...
  @ManyToOne(fetch=FetchType.EAGER,targetEntity=Address.class,cascade=CascadeType.ALL)
  @JoinColumn(name=”address_id”,nullable=true)
  private Address address;
  ...
}

默认情况下,Person的所有子类对应的表中都会增加的外键列的列名是address_id

如果希望子类映射的表具有不同名的外键列,例子如下

@Entity
@AssociationOverride(name=”address”,joinColumns=@JoinColumn(name=”employee_address”))
public class Employee extends Person{
  ...
}


上面的例子将会将address_id的名字改为employee_address

 

当实体处于JPAEntityManager管理之下,实体可以在新建、托管、脱管、被删除这4个状态之间迁移。当实体在状态迁移时,会自动触发生命周期相关的回调事件。

 

@PrePersist:保存实体之前回调

@PostPersist:保存实体之后回调

@PreRemove:删除实体之前回调

@PostRemove:删除实体之后回调

@PreUpdate:更新实体之前回调

@PostUpdate:更新实体之后回调

@PostLoad:加载实体之后回调

对于他们修饰的方法,要求返回值void,无参数,方法名不以ejb开头

 

JPA允许将生命周期方法从实体类中抽取出来,放在专门的类中定义,被称为监听器

与在实体类定义的方法不同,监听器中的方法需要一个Object类型的形参,该形参代表当前持久化操作正在操作的实体。

监听器类写好之后,就可以在多个实体中重用这个监听器类,提供了@EntityListeners注解,可指定一个value属性,属性值是由多个监听器类组成的数组

public class PersonPersistListener{
  @PrePersist
  public void prePersist(Object entity){
    if(entity.getClass()==Person.class){
      ...
    }
  }

  @PreUpdate
  public void preUpdate(Object entity){
    ...
  }
}

Person类中使用

@Entity
@Table()
@EntityListeners(PersonPersistListener.class)
public class Person{
  ...
}

在某些时候如果需要为所有实体指定监听器,可以通过添加默认监听器来完成,可以在orm.xml文件的<persistence-unit-defaults>元素中添加<entity-listeners>子元素来定义,该元素也可以在<entity>中使用

<entity-listeners>元素内可以配置多个<entity-listener>,每个<entity-listener>配置一个实体监听器。其中的class属性用于指定监听器的实现类

<entity-listener>内包含<pre-persist>等子元素,其中的method-name属性用于指定监听器的方法名称

orm.xml

<persistence-unit-defaults>
  <entity-listeners>
    <entity-listener class=”com.kingdz.listener.DefaultPersistListener”>
      <pre-persist method=”prePersist”/>
      <pre-update method=”preUpdate”/>
      ...
    </entity-listener>
  </entity-listeners>
</persistence-unit-defaults>


排除监听器

@ExcludeDefaultListeners:用于排除所有默认的监听器

@ExcludeSuperclassListeners:用于排除在父类上指定的监听器。假如子类实体继承了@Entity修饰的实体或继承了@MappedSuperclass修饰的非实体父类,而且父类指定了监听器,但子类又不需要监听器,可以使用这个注解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值