继承映射
继承是面向对象的本质特征之一。JPA
支持继承映射,这是JPA
这类以对象为中心的持久化方案相对于JDBC
、MyBatis
等以数据为中心的持久化方案的关键优势之一。被继承的基类可以是实体,也可以不是实体。下面分别论述,
实体继承
领域模型之中很多实体之间存在着继承关系。典型例子是银行账户,就有信用账户和储蓄账户之分。两者之间即有共性,又有个性。因此可以建模为一个基类(账户)和两个子类(信用账户和储蓄账户)。在基类中定义共同的属性和行为,而在子类中定义特有的属性和机制。
在本范例项目中,买家(Buyer
)也有两种类型:个人买家(PersonalBuyer
)和机构买家(OrgBuyer
)。前者是自然人而后者是政府机构或企事业单位。这里也存在着继承关系:个人买家(PersonalBuyer
)和机构买家(OrgBuyer
)两个子类继承共同基类Buyer
。
继承映射的代码范例:
@Entity
@Table(name = "buyers")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
public abstract class Buyer extends BaseEntity {
private String name;
private String mobileNo;
private String wiredNo;
private String email;
@ElementCollection
@CollectionTable(name = "shipping_addresses", joinColumns = @JoinColumn(name = "buyer_id"))
private Set<Address> shippingAddresses = new HashSet<>();
}
@Entity
@DiscriminatorValue("P")
public class PersonalBuyer extends Buyer {
@Enumerated(EnumType.STRING)
private Gender gender;
@ElementCollection
@CollectionTable(name = "contact_infos", joinColumns = @JoinColumn(name = "buyer_id"))
@MapKeyEnumerated(EnumType.STRING)
@MapKeyColumn(name = "im_type")
@Column(name = "im_value")
private Map<ImType, String> imInfos = new HashMap<>();
}
@Entity
@DiscriminatorValue("O")
public class OrgBuyer extends Buyer {
@Column(name = "business_license_no")
private String businessLicenseNo;
private String taxNo;
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "contact_name")),
@AttributeOverride(name = "gender", column = @Column(name = "contact_gender")),
@AttributeOverride(name = "mobileNo", column = @Column(name = "contact_mobile_no")),
@AttributeOverride(name = "email", column = @Column(name = "contact_email"))
})
private ContactInfo contactInfo;
}
说明如下:
- 通过在抽象基类
Buyer
上添加Inheritance
逻辑注解建立一棵继承树。这个注解有一个属性strategy
,用来定义继承策略。此处采用的策略是单表策略InheritanceType.SINGLE_TABLE
,就是将所有基类子类的共有属性和特有属性全部映射到同一张数据表。 - 可选的
@DiscriminatorColumn
物理注解定义鉴别列。由于整棵继承树都持久化到同一张表,JPA
通过在表中添加一个鉴别列来区分存储的是具体哪一个子类。这个注解的name
和discriminatorType
属性分别定义鉴别列的名称和类型。默认的列名是DTYPE
而类型是DiscriminatorType.STRING
。 - 每个子类分别通过
@DiscriminatorValue
逻辑注解定义本类型对应的鉴别列的值。在本例子中,PersonalBuyer
的鉴别列存储的值是字符串P
而OrgBuyer
的鉴别列存储的值是字符串O
。 - 基类和每个子类都必须分别添加逻辑注解
@Entity
。@Entity
注解不可继承。 - 在基类
Buyer
中定义共同属性name
,mobileNo
,wiredNo
,email
和shippingAddresses
,由所有的子类继承。在子类PersonalBuyer
中定义个人买家特有属性gender
和imInfos
;在子类OrgBuyer
中定义机构买家特有属性businessLicenseNo
,taxNo
和contactInfo
。 - 继承的层级理论上是没有限制的。当然现实中一般只是两层,最多不要超过三层。
通过继承映射,JPA
支持多态关联和多态查询。
多态关联
多态关联是指一个实体关联到另一个/一组实体,而后者的类型(在多值的情况下是元素类型)是一个实体基类。
本项目中,每个订单Order
关联到一个买家Buyer
,这个买家可能是个人买家也可能是机构买家。
@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
@ManyToOne
private Buyer buyer;
public Buyer getBuyer() {
return buyer;
}
public void setBuyer(Buyer buyer) {
this.buyer = buyer;
}
}
当创建订单的时候,如果通过setBuyer()
方法传递给订单实体的是一个个人买家,那么将来获取订单的时候,通过getBuyer()
方法获取到的买家就是个人买家类型。机构买家同理。JPA
自动处理与类型有关的一切,不需要开发者编写多余的代码。
多态查询
多态查询是指以实体基类或子类为查询目标的查询。
我们可以针对基类或个别子类分别做查询。以JPA
查询语言jpql
为例(jpql
将在后面的章节中详细论述)。
针对个人买家的查询例子:
select b from PersonalBuyer b where b.gender = :gender
针对机构买家的查询例子:
select b from OrgBuyer b where b.businessLicenseNo = :businessLicenseNo
针对买家抽象基类的查询的例子:
select b from Buyer b where name = :name
最后一个查询既包含符合查询条件的个人买家,也包含符合条件的机构买家。
由机构买家所下的订单的查询例子:
select o from Order o join o.buyer b where TYPE(b) = OrgBuyer
这个查询将查找出买家类型为机构买家的所有订单。
非实体继承
非实体继承是指子类是实体,但基类不是实体的继承类型。
如果基类被注解为@MappedSuperclass
,那么这个基类中定义的所有持久化属性,都会分别持久化到所有实体子类的相应的表的字段中(每个实体子类对应的表中都包含这些属性映射的列)。本项目中,所有实体都继承自共同基类BaseEntity
,通过这个基类定义了所有实体的一些共同属性。其定义如下:
@MappedSuperclass
public abstract class BaseEntity implements Serializable {
@Id
@GeneratedValue
private int id;
@Version
private int version;
private LocalDateTime created;
@Column(name = "last_updated")
private LocalDateTime lastUpdated;
@Transient
private boolean isNew = true;
}
上面的BaseEntity
由于有@MappedSuperclass
注解,它的id
、version
、created
和lastUpdated
属性都会作为持久化属性被所有的实体子类继承。isNew
属性由于注解为@Transient
,不会被子类持久化。
@MappedSuperclass
注解的基类包含的持久化属性,既可以是值属性,也可以是关联属性;既可以是单值属性,也可以是多值属性。除了不能作为查询和关联目标之外,几乎可以作为实体一样看待。
如果基类没有添加@MappedSuperclass
注解,那么基类中定义的任何属性都不会持久化到数据库,即使在属性级别添加了映射元数据也没有任何作用。