值属性映射

前文说过,实体(和值对象)可以拥有两种类型的属性:

  • 值属性Attribute:类型为简单值或值对象,或它们的某种类型的集合/数组。它们是实体的内在组成部分。
  • 关联属性Association:类型为实体,或实体的某种类型的集合/数组。它们代表实体的外在关系。

区分这两者非常关键。

值属性可以是单值的,也可以是多值的。下面分别论述。

一、单值值属性映射

单值的值属性在数据库中没有自己对应的表,它们的值被持久化到实体对象对应的数据表的列中。

单值值属性分类两类:

1. 简单值属性

简单值属性用@Basic逻辑注解,包括各种基本类型及其对象包装类型、日期/时间类型,字符串,枚举,布尔值等等。

@Basic是默认的注解,意味着需要持久化的简单值属性可以标注、也可以不标注@Basic注解。

@Basic注解有一个optional属性,指定字段是否接受空值。

单值的简单值属性可以添加@Column物理注解,用来指定映射的数据列名称、是否接受空值、是否唯一等等。

下面是一些例子:

@Entity
@Table(name = "buyers")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
public abstract class Buyer extends BaseEntity {

    @Basic(optional = false)
    @Column(name = "buyer_name", nullable = false, unique = true)
    private String name;
  ...
}

2. 值对象属性

值对象属性用@Embedded逻辑注解。

如果值对象类已经注解为@Embeddable,则无需再在实体属性上添加@Embedded注解。

如果值对象本身是多属性的,那么,它的各个属性会扁平化后存储到实体类对应的数据表的多个列上。例如订单Order对象对应到数据库的orders数据表,而订单类有个代表送货地址的shippingAddress属性,其类型是值对象Address,它本身有省、市、详细地址等多个属性,那么Address的每个属性值都会作为单独的列存储到orders数据表中。

下面是一些例子:


@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
    @Embedded
    private Address shippingAddress;
}

可以在值对象中为每个属性定义映射元数据。和实体一样,值对象可以有值属性和关联属性,单值和多值的都可以。

因为值对象中的每个属性会扁平化地存放在它所属的实体对应的数据表中,那么就可能导致一个问题:值对象中的属性持久化列名可能和它所属实体的其他简单值属性或其他值对象属性的下级属性列名同名,导致冲突。这个时候可以通过在值对象属性上添加@AttributeOverride逻辑注解,将值对象中的属性映射到另外的列名字,避免冲突。下面是个例子:

@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
    @Embedded
    @AttributeOverride(name = "value", column = @Column(name = "total_price"))
    private Money totalPrice;
}

@Embeddable
public class Money {
    private BigDecimal value = BigDecimal.ZERO;
}

根据Money类的定义,它含有一个名为value的属性,默认会映射到所属实体表的value列。我不喜欢它在订单表中以value为列名(虽然在这个例子中没造成任何命名冲突),所以在Order类的映射中,我通过@AttributeOverride注解,将属性value重新映射到total_price列。这样表达性好得多。

二、多值值属性映射

多值值属性,是一个ListSetSortedSetMap或数组类型的属性,其中包含的元素类型是简单值或值对象。

多值值属性统一用@ElementCollection逻辑注解标识。并且在数据库中用单独的数据表存放其内容。这个表的名字和其他属性可以用@CollectionTable物理注解进行映射。

1. 简单值的多值属性

下面是简单值的多值属性的例子:

@Entity
@DiscriminatorValue("P")
public class PersonalBuyer extends Buyer {

    @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<>();
}

这个例子中:

  • @ElementCollection逻辑注解标识这是个多值的值属性。
  • @CollectionTable物理注解指明用来存储内容的数据表名称是contact_infos,这个表中有一个外键列buyer_id,指向实体所在的buyers表的主键。。
  • 这个多值属性是一个Map,所以要分别针对它的keyvalue指定映射注解。
  • 这个Mapkey是枚举类型ImType。通过@MapKeyColumn物理注解指定key在数据库中对应的列名是im_type。通过逻辑注解@MapKeyEnumerated(EnumType.STRING)告诉JPA,在数据库中存储枚举的字符串名称而不是序号。
  • 这个Mapvalue是个字符串。通过物理注解@Column(name = "im_value")指定表中存储value的列名为im_value
  • 最终结果是:用数据库表contact_infos来存储集合的内容。其中im_type列存储Mapkeyim_value列存储Mapvalue,还有一个外键列buyer_id,指向值对象所属的实体的表buyers的主键列。
  • 上述注解中,只有@ElementCollection逻辑注解是必须的,其余各项都有默认值。

2. 值对象的多值属性

下面是多值的值对象属性的例子:

@Entity
@Table(name = "buyers")
public abstract class Buyer extends BaseEntity {
    @ElementCollection
    @CollectionTable(name = "shipping_addresses", joinColumns = @JoinColumn(name = "buyer_id"))
    private Set<Address> shippingAddresses = new HashSet<>();
}

在这个例子中:

  • @ElementCollection逻辑注解标识这是个多值的值属性。
  • @CollectionTable物理注解指明用来存储Address内容的数据表名称是shipping_addresses,值对象Address的各个属性分别存储到数据表shipping_addresses各自的列中。shipping_addresses表中还有一个外键列buyer_id,指向实体所在的buyers表的主键。
  • 这个集合属性的类型是Set。这表明集合中的元素不可重复且不保证排列顺序。Set根据其中的元素的equals()方法来判断两个元素是否代表相同的元素。根据Set的定义,不可以包含相同的元素。

results matching ""

    No results matching ""