hibernate中的集合类型
引入:
Hibernate可以持久化以下java集合的实例, 包括java.util.Map, java.util.Set, java.util.SortedMap, java.util.SortedSet, java.util.List, 和任何持久实体或值的数组。类型为java.util.Collection或者java.util.List的属性还可以使用"bag"语义来持久。
警告:用于持久化的集合,除了集合接口外,不能保留任何实现这些接口的类所附加的语义(例如:LinkedHashSet带来的迭代顺序)。所有的持久化集合,实际上都各自按照 HashMap, HashSet, TreeMap, TreeSet 和 ArrayList 的语义直接工作。更深入地说,对于一个包含集合的属性来说,必须把Java类型定义为接口(也就是Map, Set 或者List等),而绝不能是HashMap, TreeSet 或者 ArrayList。 存在这个限制的原因是,在你不知道的时候,Hibernate暗中把你的Map, Set 和 List 的实例替换成了它自己的关于Map, Set 或者 List 的实现。 (所以在你的程序中,谨慎使用==操作符。)(译者说明: 为了提高性能等方面的原因,在Hibernate中实现了几乎所有的Java集合的接口 。)
集合实例在数据库中根据指向对应实体的外键而得到区别。这个外键被称为集合的关键字 。在Hibernate配置文件中使用<key> 元素来映射这个集合的关键字。
集合可以包含几乎所有的Hibernate类型, 包括所有的基本类型, 自定义类型,实体类型和组件。 集合不能包含其他集合。这些被包含的元素的类型被称为集合元素类型。 集合的元素在Hibernate中被映射为<element>, <composite-element>, <one-to-many>, <many-to-many> 或者 <many-to-any>。
set集合类型用的比较多。
这里举得例子是部门和员工的关系。我们知道部门和员工是一对多的关系。
员工类的源代码如下:
public class Employee { private int id; private String name; private Department depart; @Override public String toString() { return "id=" + this.id + " name=" + this.name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Department getDepart() { return depart; } public void setDepart(Department depart) { this.depart = depart; } }
员工的映射文件:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="hibernate.collections"> <class name="Employee" > <id name="id"> <generator class="native"/> </id> <property name="name" unique="true"/> <many-to-one name="depart" column="depart_id"/> </class> </hibernate-mapping>
充:hibernate不允许多的这一端放弃对关系的维护。即many-to-one没有inverse=“true”这样的配置。
部门类的源代码如下:
public class Department { private int id; private String name; private Set<Employee> emps; // private List<Employee> emps; // private Map<String, Employee> emps; // private Employee[] emps; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set<Employee> getEmps() { return emps; } public void setEmps(Set<Employee> emps) { this.emps = emps; } }
部门类的映射文件department.hbm.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="hibernate.collections"> <class name="Department"> <id name="id"> <generator class="native" /> </id> <property name="name"/> <set name="emps" inverse="true"> <key column="depart_id"/> <one-to-many class="Employee" /> </set> </class> </hibernate-mapping>
测试代码:
public class Main { public static void main(String[] args){ Department depart = new Department(); depart.setName("depart"); Employee e1 = new Employee(); e1.setName("e1"); e1.setDepart(depart); Employee e2 = new Employee(); e2.setName("e2"); e2.setDepart(depart); Set<Employee> emps = new HashSet<Employee>(); emps.add(e1); emps.add(e2); depart.setEmps(emps); Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); s.save(e1); s.save(e2); s.save(depart); tx.commit(); } }
执行后hibernate执行的sql语句如下:
以上用的是set集合类型,它具有这样的特性:无重复和没有顺序。
但有时候我们需要记住元素的顺序,这时就可以采用list来保存元素。同样,在hibernate中有相应的List标签与之对应。他可以在保存的时候记住保存的顺序。例如,部门可以对其员工加入的先后进行记录。读取时,就会按照先后的顺序来获得。
只需在deparment类中把集合类型改用List,同时修改deparment.hbm.xml文件中的set标签:
<list name="emps">
<key column="depart_id" />
<list-index column="order_col" /> //该子元素用于记录保存的顺序,在对象模型中不存在,hibernate会在表中加入一列order_col
<one-to-many class="Employee" />
</list>
测试代码只需把set改为是有List即可,其他部分不变。
hibernate帮助生成的表结构为:
+---+-------+-----------+-----------+
| id |name |depart_id|order_col|
+----+------+-----------+-----------+
| 1 | e1 | 1 | 0 |
| 2 | e2 | 1 | 1 |
+----+------+-----------+-----------+
有时候,我们虽然在java代码中试用了List集合类型,但我们保存时并不需要记住保存的先后顺序,这时可以用bag标签代替。
<bag name="employees" order-by="id desc"> //不关心顺序,与java代码中的list对应。
<key column="depart_id"/>
<one-to-many class="Employee"/>
</bag>
查看表结构为:
+----+------+-----------+
| id | name | depart_id |
+----+------+-----------+
| 1 | e1 | 1 |
| 2 | e2 | 1 |
+----+------+-----------+
map在java中也是一种常用的集合类型。假设员工的名字是不一样的,可以把员工的名字作为键,员工作为map的值。
department类修改为:
public class Department {
private int id;
private String name;
private Map<String, Employee> emps;
}
department.hbm.xml配置文件修改:
<map name="emps">
<key column="depart_id" />
<map-key type="string" column="name"/>
<one-to-many class="Employee" />
</map>
测试代码只需把集合类型做相应的改变即可,生成的表结构为:
department表;
+----+--------+
| id | name |
+----+--------+
| 1 | depart |
+----+--------+
employee表:
+----+------+-----------+
| id | name |depart_id|
+----+------+-----------+
| 1 | e1 | 1 |
| 2 | e2 | 1 |
+----+------+-----------+
介绍了这么多的集合类型,那么我们在实际运用中该如何选呢?
简单使用原则:大部分情况下使用set,需要保证集合中的顺序用list,想用java.util.List又不需要保证顺序用bag。
这些集合类都是Hibernate实现的类和JAVA中的集合类不完全一样,set,list,map分别和java中的set,list,map接口对应,bag映射成java的list;这些集合的使用和java集合中对应的接口基本一致;
注意:在java的实体类中集合只能定义成接口不能定义成具体类,因为集合会在运行时被替换成Hibernate的实现。为了实现懒加载 ,hibernate把java中的集合类都重新给予了实现。你在java中使用的集合类,在保存时hibernate内部进行了替换,使用的是内置的hibernate集合。所以你在程序中用你的集合类进行强制类型转换会出错。
比如下面代码:
static void query(int id) { String hql = "from deparment"; Session s = HibernateUtil.getSession(); Transaction tx = s.beginTransaction(); Department depart = (Department)s.get(Department.class, id); System.out.println(); System.out.println(depart.getEmps()); }
在主函数中调用该段代码打印输出:{e1=id=1 name=e1, e2=id=2 name=e2}
将以上代码的打印语句改为:System.out.println((HashMap) depart.getEmps());
把它进行强制类型转换,再运行则会报错 。
出错信息为:Exception in thread "main" java.lang.ClassCastException: org.hibernate.collection.PersistentMap cannot be cast to java.util.HashMap
正如上面所指出的,hibernate对java中的集合类有自己的替代实现,对于hashmap是org.hibernate.collection.PersistentMap,该类继承了java中的Map类。在我们调用session的save语句后,hibernate会将实际的HashMap进行替换,实际上程序使用的是hibernate的自身实现。所以在程序中,再用HashMap进行强制类型转换就会报错。 但是用Map就可以,因为persistentMap是Map的子类。即执行:System.out.println((Map) depart.getEmps());就不会报错。所以在程序中尽量使用java接口,这也符合面向接口编程的道理。