映射集合属性

集合属性也非常常见,例如每个人的考试成绩是一个典型的Map结构。集合属性份两种,一种是单纯的集合,例如LIst、Set或者数组。另一种是Map结构的集合属性,每个属性值都有对应的key映射。Hibernate要求持久化集合值字段必须声明为接口,这是因为当程序持久化某个实例时,Hibernate会自动把程序中的集合实现类替换成Hibernate自己的集合实现类。

两个持久化对象不能共享同一个集合元素的引用。不管使用哪一类集合,都是用@ElementCollection注解标注。fetch属性可以设置抓取策略,TerchType.EAGER还是FetchType.LAZY两个值。targetClass属性指定集合属性中集合元素的类型。由于集合属性总是要保存到另一个数据表中,所以保存集合属性的数据表必须包含一个外键列,用于参照到主键。该外键列使用@JoinColumn进行映射。


@CollectionTable注解映射保存集合属性的表。

  • name:属性可以指定保存集合属性的数据表的名。

  •  catalog:指定保存集合属性到指定catelog中,没指定就去默认的。

  •  schema:指定保存集合属性的数据表放入指定schema中,没指定就去默认的。

  •  indexes:为持久化类所映射的表设置索引。该属性值是一个@Index注解数组。

  • joinColumns:属性值为@JoinColumn数组,每个@JoinColumn映射一个外键列。

  • uniqueConstraints:为持久化类所映射的表设置唯一约束。该属性值是一个UniqueConstraint注解数组。


@JoinColumn注解专门用于定义外键列。

  • columnDefinition:指定Hibernate使用该属性指定的SQL片段创建外键。

  • name:指定外键列的列明。

  • insertable:指定是否在insert语句中。

  • updatable:是否在update语句中。

  • nullable:是否能为空。

  • table:指定该键所在数据表的表名。

  • unique:指定是否为该列增加唯一约束。

  • referencedColumnName:指定该列所参照的主键列的列名。


当集合元素为基本类型、字符串、日期或其他复合类型,由于这些几何元素都是从属于持久化对象的,因此应该讲映射外键的@JoinColumn的nullable设置为false。如果要映射到带索引的集合,如Map,数组,List时,需要为几何元素所在的数据表指定一个索隐裂,用于保存数组索引,List的索引,或者Map集合的key索引。用于映射索引列的注解有如下两个:

@OrderColumn:用于定义LIst,数组的索引列。

@MapKeyColumn:用于映射Map集合的索引列。

如果程序需要显式地指定Map key的类型,可以使用@MapKeyClass注解,它的value属性可以指定类型。All in All,集合元素的类型大致可分为如下几种:

集合元素是基本类型。其包装类,字符串,日期的,使用@ElementCollection和@Column。

集合元素是组件(非持久化实体的复合类型),使用@ElementCollection映射集合属性,shiyongable修饰非持久化实体的复合类。

集合元素是关联的,使用OneToMany或者ManyToMany注解。


List集合属性

List是有序的,因此需要一个索引列。下面的持久化类中有一个schools的集合属性,该属性对应多个学校。集合属性只能是List接口,而不能是ArrayList这些。但该集合属性必须使用实现类完成初始化。

@Entity
@Table(name="persons_inf")
public class persons {
	@Id @Column(name="person_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private Integer id;
	private String name;
	private int age;
	
	@ElementCollection(targetClass=String.class)
	@CollectionTable(name="school_inf",joinColumns=@JoinColumn(name="person_id",nullable=false))
	@Column(name="school_name")
	@OrderColumn(name="list_order")
	private List<String> Schools=new ArrayList<>();
	
}

这段代码中注释了一个集合属性schools。注意到这个schools一开始是List接口,但是必须显式地初始化,否则添加数据会空指针异常。这个属性一共有四个注释。

@ElementCollection表名这是一个集合属性(因为是基本类型,不是组件,不需要使用Embeddable)。

@CollectionTable用于映射集合属性表,name指明了集合属性表的名字,joinColumn属性的值是一个@JoinColumn数组,用于映射外键列。也就是说指明了person_id是school_inf表的外键,与这个表的主键对应。

@Column指定了用于存放这个集合属性的数据列的列名。

@OrderColumn用于映射List集合的索引列,因为List是有序的。

public class HibernateUtil {
	private static SessionFactory sessionFactory;

	public static SessionFactory getSessionFactory() {
		Configuration conf = new Configuration();
		conf.configure();
		ServiceRegistry sr = new ServiceRegistryBuilder().applySettings(
				conf.getProperties()).buildServiceRegistry();

		SessionFactory sf = conf.buildSessionFactory(sr);
		return sf;
	}

	public static Session getSession() {
		return getSessionFactory().openSession();
	}
}
public class test {

	public static void main(String[] args) throws Exception {
		createAndStorePersons();
	}

	private static void createAndStorePersons() {
		Session session = HibernateUtil.getSession();
		Transaction tx = session.beginTransaction();
		
		persons a = new persons();
		a.setAge(20);
		a.setName("xiaoming");
		a.getSchools().add("elementary school");
		a.getSchools().add("middle school");
		session.save(a);
		tx.commit();
		session.close();
	}
}

执行完此代码我们会看到数据库中多了两个表。对于同一个持久化对象而言,它所包含的集合元素的索引不会重复,因此List集合属性可以使用关联持久化对象的外键和集合索引列作为联合主键共同作用确定一个特定的school_inf行。例如person_id=1,list_order=2的数据。


数组属性

Hibernate对数组与List的处理方式类似。实际上List与数组非常像,只是list长度可变而数组长度不能变。我们只需要将上面例子中的集合属性修改为private String[] schools;即可。


Set集合属性

Set最大的特点就是无须,不可重复,因此Set无须使用OrderColumn注解索引。剩下三个注解基本相同。由于Set没有索引,因此只能使用外键跟元素列作为联合主键,但前提是元素不能为空。如果为空,则没有主键。


Map集合属性

Map集合属性需要使用@ElementCollection映射集合属性,使用@CollectionTable映射保存集合属性的表,使用@Column映射保存Map value的数据列。除此之外,还要使用@MapKeyColumn映射保存Map key的数据列。这个就相当于List的OrderColumn。

@ElementCollection(targetClass=Float.class)
@CollectionTable(name="score_inf",joinColumns=@JoinColumn(name="kid_id",nullable=false))
@MapKeyColumn(name="subject_name",nullable=false)
@MapKeyClass(String.class)
@Column(name="mark",nullable=false)
	private Map<String,Float> scores=new HashMap<>();

我们发现score_inf表中以kid_id和subject_name一起作为联合主键,并且都不能为空。


集合属性的性能分析

对于集合属性通常采用延迟加载策略,等到系统需要使用集合属性时才从数据库中加载关联的数据。Hibernate对集合属性默认使用延迟加载。不过我们也可以为@ElementCollection添加fetch=FetchType.EAGER来取消延迟加载。

有序集合拥有一个由外键和索引组成的联合主键,这种情况下集合属性的更新是非常高效的,因此找到某一行数据非常迅速。而无须集合由外键和其它元素字段组成联合主键,或者根本就没有主键,因此效率可能会差,尤其是含有大数据文件时。Set集合在update时不会立即更新元素,只有在插入和删除时改变才有效。数组无法使用延迟加载!

集合属性表里的记录完全从属于主表的实体,当主表的记录被删除时,集合属性表里丛书与该记录的数据也会被删除;Hibernate无法直接加载、查询集合属性表中的记录,只能先加载主表,然后通过主表去获取集合属性对应的记录。


有序集合映射

Hibernate还支持SortedSet和SortedMap两个有序集合。当需要映射这两种集合时,需要使用Hibernate提供的@SortMatural或者@SortComparator注解。前者是对集合元素采用自然排序,后者是定制排序,因此必须制定value属性值。该属性值是Comparator实现类。使用Sorted集合属性与List区别不大。需要使用@ElementCollection、@CollectionTable、@Column和@SortNatural(因为没有索引)。因为有了SortNatural标注,所以从表中元素顺序与插入顺序无关,似乎是按首字母。我们也可以不适用@SortNatural,而使用@OrderBy来自己设定排列顺序,比如说:@OrderBy("trainning_name desc")表名trainning_name这一列,也就是从表的主要内容列按照降序排列。


映射数据库对象

如果希望在映射文件中创建和删除触发器、存储过程等数据库对象,Hibernate提供了database-object元素来满足需求。这种功能目前无法通过注解实现,只能在xml中配置。