Pro JPA2 第五章(集合映射)
5.1关系和元素集合
映射集合实际上存在三种可以存储的对象:映射实体的,可嵌入的和基本类型的集合.
当源实体用友一个包含目标实体类型的实例集合时,称之为一个多值关系.然儿,可嵌入的集合和基本类型的集合不是关系,它们只是元素的集合,因而称之为元素集合(element collection).关系定义了独立实体之间的关联,而元素集合包含了依赖于引用实体的对象,并且只能通过包含它们的实体进行检索.
关系和元素集合之间的一个世纪区别在于用来表示它们的注解.关系至少要求采用关系注解,@OneToMany或者@ManyToMany,而一个元素集合是由@ElementCollection注解来标识.@Embeddable public class VacationEntry { @Temporal(TemporalType.DATE) private Calendar startDate; @Column(name="DAYS") private int daysTaken; } @Entity public class Employee { @Id private int id; private long salary; @ElementCollection(targetClass=VacationEntry.class) private Collection vacationBookings; @ElementCollection private Set<String> nickNames; }
在Employee中没有映射任何额外的元数据,回顾一下,存储在集合中的元素不是实体,所以它们不具有任何映射表.嵌入对象应该与引用它们的实体存储在同一个表中,但是如果有一个嵌入对象的集合,那么如何在单行中存储多个可能映射的对象?类似地,对于基本类型不能把每个呢成String都映射到Employee表中第一列,并期望在单个行中存储多个字符串.为此,元素集合需要一个单独的表,称之为集合表(Collection table). 没个集合表必须有一个引用包含它的实体表的联结列.结合表中的其他列用于映射可嵌入元素的特性,或者如果该元素是一个基本类型,那么映射基本元素的状态.
我们使用@CollectionTable注解来指定集合表,它允许我们指定表的名称和联结列.
使用@AttributeOverride注解来重写列名.@Entity public class Employee { @Id private int id; private long salary; @ElementCollection(targetClass=VacationEntry.class) @CollectionTable(name="VACATION",joinColumns=@JoinColumn(name="EMP_ID")) @AttributeOverride(name="daysTaken",column=@Column(name="DAYS_ABS")) private Collection vacationBookings; @ElementCollection @Column(name="NICKNAME") private Set<String> nickNames; }
EMPLOYEE实体表和包含重写的映射集合表
5.2 使用不同的集合类型
可以使用的集合类型由:Collection,Set,List和Map.下面将一个一个的介绍.- 5.2.1 Set或者Collection
当不关注底层的实现且普通的Collection方法足够用于访问它所存储的实体时,可以使用Collection.
Set可以防止插入重复的元素. 5.2.2 List
- 通过实体或元素的特性排序
对List中实体或元素排序的最常见的方法是:根据特定的实体或元素特性的比较,制定一条排序规则,如果List是一个关系,那么特性通常是目标实体的主键.
在@OrderBy注解中指示用于排序的特性.如果List是一个关系并且引用实体,其中实体由不带字段或者属性的@OrderBy指定或者根本不指定,那么List将根据List中实体的主键排序.
@Entity public class Department { @OneToMany(mappedBy="department") @OrderBy("name ASC,id DESC") private List<Employee> employees; }
其中,@OrderBy注解中包含ASC不是必需的,默认情况下就是升序.
- 通过实体或元素的特性排序
5.2.3 Map
- 键和值
虽然基本类型,可嵌入类型或实体类型都可以作为Map键,但是,他们必需遵循键的基本规则,他们必须是可比的,并且可适当地相应hashCode()方法,以及在必要时相应equals()方法.他们也应该是唯一的.
当键是基本的或可嵌入的类型时,它们直接存储在所引用的表中.根据映射的类型,它可以是目标实体表,联接表或者集合表. 以基本类型为键
@Entity public class Employee { @Id private int id; private long salary; @ElementCollection @CollectionTable(name="EMP_PHONE") @MapKeyColumn(name="PHONE_TYPE") @Column(name="PHONE_NUM") private Map<String,String> phoneNumbers; }
@MapKeyColumn用来指示在集合表中存储基本键的列.当未指定直接时,存储键的列名为所映射的集合特性,再附件”_KEY”后缀.
EMPLOYEE_ID和PHONE_TYPE列的主键限制.
使用枚举类型来代替String类型.public enmu PhoneType { Home,Mobile,Work } @Entity public class Employee { @Id private int id; private long salary; @ElementCollection @CollectionTable(name="EMP_PHONE") @MapKeyEnumerated(EnumType.STRING) @MapKeyColumn(name="PHONE_TYPE") @Column(name="PHONE_NUM") prvate Map<PhoneType,String> phoneNumbers; }
以String为键的Map的一对多关系
@Entity public class Department{ @Id private int id; @OneToMany(mappedBy="department") @MapKeyColumn(name="CUB_ID") private Map<String,Employee> employeesByCubicle; }
以String为键的Map的多对多关系
@Entity public class Department { @Id private int id; private String name; @ManyToMany @JoinTable(name="DEPT_EMP", joinColumns=@JoinColumn(name="DEPT_ID"), inverseJoinColumns=@JoinColumn(name="EMP_ID")) @MapKeyColumn(name="CUB_ID") private Map<String,Employee> employeesByCubicle; }
EMPLOYEE和DEPARTMENT实体表以及DEPT_EMP联接表.
以实体特性为键
当实体的一对多或多对多关系集合表示为一个Map时,它通常以目标实体类型的某些特性为键,以实体特性为键实际上是一个以基本类型为键的特例,其中映射是一个关系,而且键的基本类型是目标实体上特性的类型.我们可以使用@MapKey注解来指定目标实体上作为键的特性.@Entity public class Department { @OneToMany(mappedBy="department") @MapKey(name="id") private Map<Integer,Employee> employees; }
- 以可嵌入的类型为键
这个不推荐使用. - 以实体为键
这个不推荐使用. 非型化Map
如果不想使用Map< KeyType,ValueType >的类型化参数版本,那么将使用无参样式的Map来定义它.@Entity public class Department { @OneToMany(targetEntity=Employee.class,mappedBy="department") @MapKey private Map employees; }
以String为键的非类型化String元素集合.
@Entity public class Employee { @Id private int id; private long salary; @ElementCollection(targetClass=String.class) @ColectionTable(name="EMP_PHONE") @MapKeyColumn(name="PHONE_TYPE") @MapKeyClass(String.class) @Column(name="PHONE_NUM") private Map phoneNumbers; }
映射规则
使用Map的一些基本规则:- 当使用非类型化Map时,利用关系的@MapKeyClass和targetEntity/targetClass元素,以及元素集合映射来指定类.
- 当Map以目标实体的特性为键时,将@MapKey用于一对多或多对多的关系Map.
- 使用@MapKeyJoinColumn来重写实体键的联结列.
- 当值为基本类型的元素集合时,将@Column重写用于存储值的列.
- 当键为基本类型时,使用@MapKeyColumn来重写存储键的值.
- 如果需要进一步地限定基本的时间或枚举类型的键,那么使用@MapKeyTemporal和@MapKeyEnumerated.
使用带”key.”或”value.”前缀的@AttributeOverride,以分别为Map键或值重写其可嵌入特性类型的列.
Map 映射 键注解 值注解 Map< Basic,Basic > @ElementCollection @MapKeyColumn
@MapKeyEnumerated
@MapKeyTemporal@Column Map< Basic,Embeddable > @ElementCollection @MapKeyColumn
@MapKeyEnumerated
@MapKeyTemporal由可嵌入对象映射,@AttributeOverride
@AssociationOverrideMap< Basic,Entity > @OneToMany
@ManyToMany@MapKey
@MapKeyColumn
@MapKeyEnumerated
@MapKeyTemporal由实体映射 Map< Embeddable,Basic > @ElementCollection Mapped by embeddable,@AttributeOverride @Column Map< Embeddable,Embeddable > @ElementCollection 由可嵌入对象映射,@AttributeOverride 由可嵌入对象映射,@AttributeOverride
@AssociationOverrideMap< Embeddable,Entity > @OneToMany
@ManyToMany由可嵌入对象映射 由实体映射 Map< Entity,Basic > @ElementCollection @MapKeyJoinColumn @Column Map< Entity,Entity > @OneToMany
@ManyToMany@MapKeyJoinColumn 由实体映射
- 键和值
- 5.2.1 Set或者Collection
最佳实践
- 当使用一个List时,如果没有明确指定任何顺序,不要假设它会自动排序,数据库结果会影响List顺序.
- 一般可以通过它们自身的特性之一给对象排序.使用@OrderBy注解总是最佳方法.
- Map类型很有帮助,但是它们很复杂.如果达到了一定的层次,使用Map将会非常好.
- 如果List一样,Map的首选和最有效的使用方法是以目标对象的特性为键,因此以基本的特性类型为键的实体Map是最为常见的 和最有用的.
- 避免在Map中使用嵌入对象,尤其是作为键,因为它们的标识通常是没有定义的
- 不能保证在集合中会支持重复或null值,即使可能也是不推荐的.