在1-N、N-N关联关系中,在实体中增加了一个Set集合属性,记录关联实体。
JPA允许使用Map集合来记录关联实体,使用Map集合时,Map集合的key类型通常是关联实体的主键类型,JPA提供了@MapKey来修饰Map集合。
下面是一个1-N的例子,此时Address实体没有采用Integer类型的主键,而是采用AddressPk类型的主键。AddressPk是一个满足主键要求的复合类,省略代码。此时1-N双向关联中Address是N的一端,需要增加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 ASC、propertyName DESC的字符串。
public class Person{
@OneToMany(...)
@OrderBy(“detail DESC”)
private Set<Address> address=new HashSet<Address>();
}
下面介绍继承关系的映射
本例包括Person、Employee、Manager、Customer一共4个实体。
Person派生了Employee和Customer,而Employee又派生了Manager。
Employee和Manager之间存在双向的N-1关联,Employee和Customer之间存在双向的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文件中可以只列出一个实体类:Person,JPA会自动识别Person的所有子类,并负责把他们映射成实体
连接子类的映射策略
这种策略不是JPA继承映射策略的默认策略,因此需要在根类中使用@Inheritance指定映射策略,属性如下
1)InheritanceType.SINGLE_TABLE:整个类层次对应一张表的映射策略
2)InheritanceType.JOINED:连接子类的映射策略
3)InheritanceType.TABLE_PER_CLASS:每个具体类对应一张表的映射策略
采用这种策略时,父类实体保存在父类表中,而子类实体则由父类表和子类表共同存储。因为子类实体也是一个特殊的父类实体,因此必然也包括了父类实体的属性,于是将子类与父类公有的属性保存在父类表中,而子类增加的属性则保存在子类表中。
无需使用辨别者列,只要在根类中指定@Inheritance为JOINED即可。
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name=”person_table”)
public class Person{
...
}
这样生成的子类表中都有一个根表的id,JPA底层使用这个id来查询数据,拼接成一个记录,这种拼接在底层通过多表的join完成。
缺点:对于类继承层次较深的继承树来说,可能导致性能低下
每个具体类对应一张表的映射策略
在这种策略下,子类增加的属性可以有非空约束——即父类实例的数据保存在父类表中,子类实例的数据则保存在子表中。
与连接子类映射策略不同的是,子类实例的数据仅保存在子类表中,在父类表中没有任何记录。子类表的字段比父类表的字段要多,因为子类表的字段等于父类属性加子类增加的属性总和。
在这种策略下,如果单从数据库来看,几乎难以看出他们之间存在的继承关系,只是多个实体之间的主键值具有某种连续性——因此不能让数据库为各数据表自动生成主键值。因此,不能使用GenerationType.IDENTITY和GenerationType.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-1、1-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
当实体处于JPA的EntityManager管理之下,实体可以在新建、托管、脱管、被删除这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修饰的非实体父类,而且父类指定了监听器,但子类又不需要监听器,可以使用这个注解