映射文件的结构和属性
一个映射文件(mapping file)由一个根节点<hibernate-mapping>和多个<class>节点组成,
首先看看根节点<hibernate-mapping>支持什么属性:
1 <hibernate-mapping 2 schema="schemaName" (1) 3 catalog="catalogName" (2) 4 default-cascade="cascade_style" (3) 5 default-access="field|property|ClassName" (4) 6 default-lazy="true|false" (5) 7 auto-import="true|false" (6) 8 package="package.name" (7) 9 />
这8个属性都是可选的,下面解释几个重要的属性,
default-lazy:延迟加载,默认为true,Hibernate通过默认加载提升性能,通常建议设置为true。在后面的配置属性和集合映射时也可以指定lazy值
auto-import:是否允许查询语句中使用非全限定类名,默认为true,当一个映射文件中有两个持久化类,包名不同,类名相同时,需要设置auto-import为false才能区分。
package:设置该值之后,映射文件就不再需要指定全限定类名
再看看<class>标签支持什么样的属性,
1 <class 2 name="ClassName" (1) 3 table="tableName" (2) 4 discriminator-value="discriminator_value" (3) 5 mutable="true|false" (4) 6 schema="owner" (5) 7 catalog="catalog" (6) 8 proxy="ProxyInterface" (7) 9 dynamic-update="true|false" (8) 10 dynamic-insert="true|false" (9) 11 select-before-update="true|false" (10) 12 polymorphism="implicit|explicit" (11) 13 where="arbitrary sql where condition" (12) 14 persister="PersisterClass" (13) 15 batch-size="N" (14) 16 optimistic-lock="none|version|dirty|all" (15) 17 lazy="true|false" (16) 18 entity-name="EntityName" (17) 19 check="arbitrary sql check condition" (18) 20 rowxml:id="rowid" (19) 21 subselect="SQL expression" (20) 22 abstract="true|false" (21) 23 node="element-name" 24 />
一对<class>就代表一个持久化实体类,即数据库中的表,在<class>元素下,通常还会有<id>和<property>子元素,用来构成表的各个字段,<id>通常用来映射主键,<property>则用来映射普通字段,另外还可以通过<set> <map>等映射集合字段,下面一一讲解。
映射主键(<id...>
Hibernate建议为持久化类定义一个标识属性(identifier property),如private int id, 标识属性映射数据库底层的主键字段(primary key column )。在映射文件中,标识属性用<id>表示,<id>标签支持以下属性:
1 <id 2 name="propertyName" (1) 3 type="typename" (2) 4 column="column_name" (3) 5 unsaved-value="null|any|none|undefined|id_value" (4) 6 access="field|property|ClassName"> (5) 7 node="element-name|@attribute-name|element/@attribute|." 8 9 <generator class="generatorClass"/> 10 </id>
unsaved-value:指定刚创建尚未保存时的标识属性值,用来区分已经加载到session但未再次持久化的实例,Hibernate 3中已经不需要指定这个属性。
上面几个属性通常只需要指定 name, type, column这三个属性,在泛型中,type属性也不需要指定,Hibernate会通过反射获取字段类型
在Hibernate中,提供了一个逻辑主键生成器,在映射文件中通过<id>元素的子元素<generator>表示,可以为每个持久化类生成唯一逻辑主键,
主键生成器<generator>的class属性支持以下值,代表不同种类的主键生成器策略,
1 <generator class="increment|identity|sequence|hilo|seqhilo|uuid|guid|native|assigned|select|foreign" />
其中最常用的identiry,适合DB2,MySQL, MSSQL等支持自增长字段的数据库,可以返回long, short, int等。
sequence,适合DB2,Oracle等。
映射普通属性(<property...>
<property>标签支持以下属性,
1 <property 2 name="propertyName" (1) 3 column="column_name" (2) 4 type="typename" (3) 5 update="true|false" (4) 6 insert="true|false" (4) 7 formula="arbitrary SQL expression" (5) 8 access="field|property|ClassName" (6) 9 lazy="true|false" (7) 10 unique="true|false" (8) 11 not-null="true|false" (9) 12 optimistic-lock="true|false" (10) 13 generated="never|insert|always" (11) 14 node="element-name|@attribute-name|element/@attribute|." 15 index="index_name" 16 unique_key="unique_key_id" 17 length="L" 18 precision="P" 19 scale="S" 20 />
通常情况下只需指定name, column, type的值, 另外部分属性解释如下
update和insert:输入属性值不需要Hibernate生成,则指定为false
formula=(sql):直接写SQL获取数据,括号不能少
lazy:是否延迟加载,默认为false
下面演示一下formula的用法,
如果我们在实体类Person.java中有一个普通属性 private String fullContent;
1 private String fullContent;
在映射文件中,这个属性的值是通过formula的方式获取的,则映射文件应该这么写,
1 ... 2 <property name="fullContent" formula="(select concat(p.name,p.age) from person_inf p where p.person_id = person_id)" /> 3 ...
这样,我们在测试类中如果要获取这个属性值的话,就会取formula的值
1 Person p = (Person)sess.get(Person.class, 3); 2 System.out.println(p.getFullContent());
上面的例子只是得到一个实体类的属性字段值,但是在数据库层面并没有生成这个字段,
要想数据库也生成这个字段,则要通过<property>标签的generated属性来指定, 例如
1 ... 2 <property name="fullContent" column="full_content" type="string" generated="insert" /> 3 ...
同时,需要有一个数据库的触发器配合使用才能实现上面的功能,
1 drop database test; 2 create database test; 3 use test; 4 create table person_inf 5 ( 6 id auto_increment primary key, 7 title varchar(255) not null, 8 content varchar(255) 9 full_content varchar(255) 10 ); 11 DELEMITER | 12 create trigger t_full_content_gen BEFORE INSERT ON person_inf 13 FOR EACH ROW BEGIN 14 set person_inf.full_content=concat(person_inf.title, person_inf.content); 15 END; 16 | 17 DELIMITER ;
先执行上面的SQL脚本,然后才执行java程序,另外需要将hibernate.cfg.xml的hbm2ddl.auto改为update,避免重建数据库时丢失触发器。
之后,便可以像上面那样访问这个属性了,
1 Person p = (Person)sess.get(Person.class, 3);
2 System.out.println(p.getFullContent());
映射集合属性
在一个实体类中,有标志属性,普通属性,经常还会有集合属性,集合属性中会包含多个值,多个值与实体类形成一对多的关联关系。
例如Set集合,List集合,Map集合等等,比如下面这样的实体类定义,
1 public class SetPerson { 2 private int id; 3 private String name; 4 private int age; 5 private Set<String> schools = new HashSet<String>(); 6 ...
这里的schools就是一个集合属性。
Hibernate要求集合属性必须声明为接口而不是实现类,可以是java.util.Set, java.util.List, java.util.Map等。
集合属性将会保存在单独的表中,当实体类对象保存时,集合属性也会自动持久化(即保存到单独表中去),当实体类对象删除时,集合属性也会自动删除;当集合从一个实体类对象传递到另一个持久化对象时,集合记录也会从一个表转移到另一个表。
-集合标签
常用的集合属性在映射文件中的元素有 <list>, <set>, <map>, <array>, <bag>,<idbag>, <rimitive-array>等,这些集合属性元素标签也有自己的属性,部分属性解释如下:
table:表名(集合属性元素需要单独存在一张表里)
lazy:默认为true,延迟加载
inverse:反转集合属性与实体类关联的控制权,这个对于Set之类的无序集合在性能提升上有用
sort:有序集合排序属性
order-by:也是指定排序,不过是通过SQL语句实现
-外键
由于集合元素和实体类是一个从属关系,所以在集合元素中需要有一个外键,与实体类的标志属性相关联。这个外键在映射文件中通常用<key>标签标识,<key>标签也有自己的属性,如column, on-delete, property-ref,update,unique等等。
-索引
对于有序集合,都需要指定一个索引列,无序集合(Set,bag)则不需要。例如在
<list-index>,List或者数组集合中用来表示索引,
<map-key>,映射Map集合,基本数据列的索引
<map-key-many-to-many>, 映射Map集合,实体引用类索引
<composite-map-key>, 映射Map集合,复合数据类型的索引
-数据列
在映射文件中的集合元素,通常还会有一个数据列,根据不同的数据类型,在映射文件中的标签可能不同,通常会有以下几种
<element> 基本数据类型,或者其包装类型
<composite-element>复合类型
<one-to-many>或<many-to-many>引用类型
下面,将针对List, Set, Map, SortedMap等不同的集合属性具体说明每种集合属性的特性
List集合属性
List是有序集合, 因此需要一个索引列来表示集合的次序。 下面演示List集合属性的用法,
首先定义一个持久化实体类ListPerson, 类中定义了一个List集合属性 schools.
1 package hib; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class ListPerson { 7 private int id; 8 private String name; 9 private int age; 10 private List<String> schools = new ArrayList<String>(); 11 12 public int getId() { 13 return id; 14 } 15 public void setId(int id) { 16 this.id = id; 17 } 18 public String getName() { 19 return name; 20 } 21 public void setName(String name) { 22 this.name = name; 23 } 24 public int getAge() { 25 return age; 26 } 27 public void setAge(int age) { 28 this.age = age; 29 } 30 31 public List<String> getSchools() { 32 return this.schools; 33 } 34 public void setSchools(List<String> schools) { 35 this.schools = schools; 36 } 37 38 }
对于这个持久化类, 其映射文件如下, 在映射文件中,我们定义了外键标签<key>用来关联集合属性和持久化类,
定义了索引列<list-index>来记录集合属性元素的次序,定义了数据列<element>来保存集合元素的值
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 4 5 <hibernate-mapping package="hib"> 6 <class name="ListPerson" table="Person_inf"> 7 <id name="id" column="person_id" type="int"> 8 <generator class="identity" /> 9 </id> 10 <property name="name" type="string" /> 11 <property name="age" type="int" /> 12 <list name="schools" table="school"> 13 <!-- 外键列--> 14 <key column="personid" not-null="true"/> 15 <!-- 索引列 --> 16 <list-index column="list_order"/> 17 <!-- 数据列,这里的type属性其实可以省略,因为在实体类中使用了泛型,Hibernate会通过反射取得元素数据类型--> 18 <element type="string" column="school_name" /> 19 </list> 20 </class> 21 </hibernate-mapping>
下面写一个测试类用来测试上面的持久化类,
1 package hib; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.HashSet; 6 import java.util.List; 7 import java.util.Map; 8 import java.util.Set; 9 import java.util.SortedSet; 10 import java.util.TreeSet; 11 12 import org.hibernate.Session; 13 import org.hibernate.SessionFactory; 14 import org.hibernate.Transaction; 15 import org.hibernate.cfg.Configuration; 16 import org.hibernate.tool.hbm2ddl.SchemaExport; 17 18 public class PersonManager { 19 private static void exec(String resource, Object obj) { 20 // 打开线程安全的session 21 Configuration conf = new Configuration().configure(); 22 conf.addResource("hib/"+resource); 23 // 用Configuration创建SessionFactory 24 SessionFactory sf = conf.buildSessionFactory(); 25 // 用SessionFactory打开Session 26 Session sess = sf.openSession(); 27 Transaction tx = sess.beginTransaction(); 28 sess.save(obj); 29 tx.commit(); 30 sess.close(); 31 sf.close(); 32 } 33 private static void listTest() { 34 ListPerson lp = new ListPerson(); 35 lp.setAge(20); 36 lp.setName("xiao ming"); 37 List<String> schools = new ArrayList<String>(); 38 schools.add("小学"); 39 schools.add("中学"); 40 lp.setSchools(schools); 41 exec("ListPerson.hbm.xml", lp); 42 } 43 44 public static void main(String[] args) { 45 listTest(); 46 } 47 }
PersonManager类中定义了一个通用方法private static void exec(String resource, Object obj)用来保存持久化类实例,
在方法listTest()中,实例化了一个持久化类ListPerson, 并且实例化了一个集合属性schools并赋值。
运行PersonManager.java之后,发现数据库中多了两张表,person_inf和school,且两张表通过personid关联(即在school表中增加了外键约束如下:FK_48amgp8tko414xxqs1q1vaat4)。
eclipse中执行结果,
1 Hibernate: drop table if exists Person_inf 2 Hibernate: drop table if exists school 3 Hibernate: create table Person_inf (person_id integer not null auto_increment, name varchar(255), age integer, primary key (person_id)) 4 Hibernate: create table school (personid integer not null, school_name varchar(255), list_order integer not null, primary key (personid, list_order)) 5 Hibernate: alter table school add constraint FK_48amgp8tko414xxqs1q1vaat4 foreign key (personid) references Person_inf (person_id) 6 十二月 29, 2016 11:11:30 下午 org.hibernate.tool.hbm2ddl.SchemaExport execute 7 INFO: HHH000230: Schema export complete 8 Hibernate: insert into Person_inf (name, age) values (?, ?) 9 Hibernate: insert into school (personid, list_order, school_name) values (?, ?, ?) 10 Hibernate: insert into school (personid, list_order, school_name) values (?, ?, ?)
mysql中的数据,
总共有两张表
MariaDB [information_schema]>
MariaDB [information_schema]> select table_name from tables where table_schema =
'test';
+------------+
| table_name |
+------------+
| person_inf |
| school |
+------------+
2 rows in set (0.00 sec)
实体类表
MariaDB [(none)]> use test;
Database changed
MariaDB [test]>
MariaDB [test]> select * from person_inf;
+-----------+-----------+------+
| person_id | name | age |
+-----------+-----------+------+
| 1 | xiao ming | 20 |
+-----------+-----------+------+
1 row in set (0.00 sec)
MariaDB [test]>
集合属性表
MariaDB [test]> select * from school;
+----------+-------------+------------+
| personid | school_name | list_order |
+----------+-------------+------------+
| 1 | 小学 | 0 |
| 1 | 中学 | 1 |
+----------+-------------+------------+
2 rows in set (0.00 sec)
数组集合属性
Hibernate对数组和List的处理非常相似,只是前者长度不可变,后者可变而已,也因此数组集合一般是无法实现延迟加载的(长度不可变)。
将上面的list集合修改成数组集合,代码如下,
持久化类,
1 package hib; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class ArrayPerson { 7 private int id; 8 private String name; 9 private int age; 10 11 public int getId() { 12 return id; 13 } 14 public void setId(int id) { 15 this.id = id; 16 } 17 public String getName() { 18 return name; 19 } 20 public void setName(String name) { 21 this.name = name; 22 } 23 public int getAge() { 24 return age; 25 } 26 public void setAge(int age) { 27 this.age = age; 28 } 29 30 public String[] getSchools() { 31 return schools; 32 } 33 public void setSchools(String[] schools) { 34 this.schools = schools; 35 } 36 37 private String[] schools; 38 }
映射文件,
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 4 5 <hibernate-mapping package="hib"> 6 <class name="ArrayPerson" table="Person_inf"> 7 <id name="id" column="person_id" type="int"> 8 <generator class="identity" /> 9 </id> 10 <property name="name" type="string" /> 11 <property name="age" type="int" /> 12 <array name="schools" table="school"> 13 <!-- 外键列--> 14 <key column="personid" not-null="true"/> 15 <!-- 索引列 --> 16 <list-index column="list_order"/> 17 <!-- 数据列,这里的type属性其实可以省略,因为在实体类中使用了泛型,Hibernate会通过反射取得元素数据类型--> 18 <element type="string" column="school_name" /> 19 </array> 20 </class> 21 </hibernate-mapping>
在测试类PersonManager.java中,新家一个测试方法arrayTest(),
1 private static void arrayTest() { 2 ArrayPerson ap = new ArrayPerson(); 3 ap.setAge(20); 4 ap.setName("xiao ming"); 5 String[] schools = new String[] {"小学","中学","大学"}; 6 ap.setSchools(schools); 7 exec("ArrayPerson.hbm.xml", ap); 8 }
执行PersonManager.java, 看到hibernate生成的sql如下,
1 ... 2 Hibernate: insert into person_inf (name, age) values (?, ?) 3 Hibernate: insert into school (personid, list_order, school_name) values (?, ?, ?) 4 Hibernate: insert into school (personid, list_order, school_name) values (?, ?, ?) 5 Hibernate: insert into school (personid, list_order, school_name) values (?, ?, ?)
mysql中的数据,
person_inf表,
school表,
Set集合属性
Set集合与List集合不同,Set是无序集合,因此映射文件中就没有像list那样的索引列<list-index>。作为一个与持久化实例管理的集合属性,Set集合也是需要一个<key>外键来关联的,Set集合演示如下,
持久化类,
1 package hib; 2 3 import java.util.ArrayList; 4 import java.util.HashSet; 5 import java.util.List; 6 import java.util.Set; 7 8 public class SetPerson { 9 private int id; 10 private String name; 11 private int age; 12 13 public int getId() { 14 return id; 15 } 16 public void setId(int id) { 17 this.id = id; 18 } 19 public String getName() { 20 return name; 21 } 22 public void setName(String name) { 23 this.name = name; 24 } 25 public int getAge() { 26 return age; 27 } 28 public void setAge(int age) { 29 this.age = age; 30 } 31 32 public Set<String> getSchools() { 33 return this.schools; 34 } 35 public void setSchools(Set<String> schools) { 36 this.schools = schools; 37 } 38 private Set<String> schools = new HashSet<String>(); 39 40 }
映射文件,
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 4 5 <hibernate-mapping package="hib"> 6 <class name="SetPerson" table="Person_inf"> 7 <id name="id" column="person_id" type="int"> 8 <generator class="identity" /> 9 </id> 10 <property name="name" type="string" /> 11 <property name="age" type="int" /> 12 <set name="schools" table="school"> 13 <!-- 外键列--> 14 <key column="personid" not-null="true"/> 15 <!-- 数据列,这里的type属性其实可以省略,因为在实体类中使用了泛型,Hibernate会通过反射取得元素数据类型--> 16 <element type="string" column="school_name" not-null="true" /> 17 </set> 18 </class> 19 </hibernate-mapping>
在PersonManager.java中新增一个测试方法setTest(),
1 private static void setTest() { 2 SetPerson sp = new SetPerson(); 3 sp.setAge(20); 4 sp.setName("xiao ming"); 5 Set<String> schools = new HashSet<String>(); 6 schools.add("小学"); 7 schools.add("中学"); 8 sp.setSchools(schools); 9 exec("SetPerson.hbm.xml", sp); 10 }
运行PersonManager.java,查看mysql,能得到前面类似的结果
bag集合元素映射
<bag>可以映射list,映射Set,甚至映射Collection,但是不论映射成哪种集合,<bag>都会将它们映射为无序集合。
<bag>集合对应的表没有主键,在映射文件中通过<key>与持久化类关联,用<element>保存元素值,下面演示<bag>集合映射成Collection集合属性,
持久化类,
1 package hib; 2 3 import java.util.ArrayList; 4 import java.util.Collection; 5 import java.util.List; 6 7 public class BagPerson { 8 private int id; 9 private String name; 10 private int age; 11 12 public int getId() { 13 return id; 14 } 15 public void setId(int id) { 16 this.id = id; 17 } 18 public String getName() { 19 return name; 20 } 21 public void setName(String name) { 22 this.name = name; 23 } 24 public int getAge() { 25 return age; 26 } 27 public void setAge(int age) { 28 this.age = age; 29 } 30 31 public Collection<String> getSchools() { 32 return schools; 33 } 34 public void setSchools(Collection<String> schools) { 35 this.schools = schools; 36 } 37 38 private Collection<String> schools = new ArrayList<String>(); 39 }
映射文件, 注意在无序集合中<bag>标签中,可以添加一个order-by属性,直接以SQL实现,这个排序将会添加进hibernate生成的SQL中,通过数据库来实现排序,而不是在程序运行内存中排序。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 4 5 <hibernate-mapping package="hib"> 6 <class name="BagPerson" table="Person_inf"> 7 <id name="id" column="person_id" type="int"> 8 <generator class="identity" /> 9 </id> 10 <property name="name" type="string" /> 11 <property name="age" type="int" /> 12 <bag name="schools" table="school" order-by="school_name asc"> 13 <!-- 外键列--> 14 <key column="personid" not-null="true"/> 15 <!-- 数据列,这里的type属性其实可以省略,因为在实体类中使用了泛型,Hibernate会通过反射取得元素数据类型--> 16 <element type="string" column="school_name" not-null="true"/> 17 </bag> 18 </class> 19 </hibernate-mapping>
在PersonManager.java中,添加一个新的测试方法bagTest(),
1 private static void bagTest() { 2 BagPerson sp = new BagPerson(); 3 sp.setAge(20); 4 sp.setName("xiao ming"); 5 Set<String> schools = new HashSet<String>(); 6 schools.add("小学"); 7 schools.add("中学"); 8 sp.setSchools(schools); 9 exec("BagPerson.hbm.xml", sp); 10 }
执行PersonManager.java后会发现school表中只有两列,外键列和数据列,没有索引列,因为<bag>是无序集合,
Map集合属性
Map集合属性使用<map>标签,需要使用<key>列作为外键列和持久化类关联。Map是有序集合,因此还需要<may-key>类作为Map集合的索引。Map集合对应的表将以外键列和索引列作为联合主键。下面演示Map集合用法,
1 package hib; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.List; 6 import java.util.Map; 7 8 public class MapPerson { 9 private int id; 10 private String name; 11 private int age; 12 13 public int getId() { 14 return id; 15 } 16 public void setId(int id) { 17 this.id = id; 18 } 19 public String getName() { 20 return name; 21 } 22 public void setName(String name) { 23 this.name = name; 24 } 25 public int getAge() { 26 return age; 27 } 28 public void setAge(int age) { 29 this.age = age; 30 } 31 32 33 public Map<String, Float> getScores() { 34 return scores; 35 } 36 public void setScores(Map<String, Float> scores) { 37 this.scores = scores; 38 } 39 40 private Map<String, Float> scores = new HashMap<String, Float>(); 41 42 }
映射文件,
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 4 5 <hibernate-mapping package="hib"> 6 <class name="MapPerson" table="Person_inf"> 7 <id name="id" column="person_id" type="int"> 8 <generator class="identity" /> 9 </id> 10 <property name="name" type="string" /> 11 <property name="age" type="int" /> 12 <map name="scores" table="score"> 13 <!-- 外键列--> 14 <key column="person_id" not-null="true"/> 15 <!-- 索引列 --> 16 <map-key column="subject" type="string"/> 17 <!-- 数据列,这里的type属性其实可以省略,因为在实体类中使用了泛型,Hibernate会通过反射取得元素数据类型--> 18 <element column="grade" type="float" /> 19 </map> 20 </class> 21 </hibernate-mapping>
在PersonManager.java中新增一个测试方法mapTest(),
1 private static void mapTest() { 2 MapPerson mp = new MapPerson(); 3 mp.setAge(20); 4 mp.setName("xiao ming"); 5 Map<String, Float> scores = new HashMap<String, Float>(); 6 scores.put("语文", new Float(100.01)); 7 scores.put("数学", new Float(130.05)); 8 mp.setScores(scores); 9 exec("MapPerson.hbm.xml", mp); 10 }
执行PersonManager.java, mysql中score表的数据如下,subject将作为map集合索引, person_id + subject 将作为表的联合主键。
SortedSet和SortedMap集合映射
使用这两个集合作为集合实现类时,需要为<set>或者<map>标签指定sort属性,sort支持unsorted, natural, 以及通过java.utilComparator自定义排序。
下面演示一个SortedSet的用法,
持久化类,
1 package hib; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 import java.util.SortedSet; 6 import java.util.TreeSet; 7 8 public class SortedPerson { 9 private int id; 10 private String name; 11 private int age; 12 13 public int getId() { 14 return id; 15 } 16 public void setId(int id) { 17 this.id = id; 18 } 19 public String getName() { 20 return name; 21 } 22 public void setName(String name) { 23 this.name = name; 24 } 25 public int getAge() { 26 return age; 27 } 28 public void setAge(int age) { 29 this.age = age; 30 } 31 32 public SortedSet<String> getTrainings() { 33 return trainings; 34 } 35 public void setTrainings(SortedSet<String> trainings) { 36 this.trainings = trainings; 37 } 38 39 private SortedSet<String> trainings = new TreeSet<String>(); 40 }
映射文件, 需要在<set>上添加sort属性,
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 4 5 <hibernate-mapping package="hib"> 6 <class name="SortedPerson" table="person_inf"> 7 <id name="id" column="person_id" type="int"> 8 <generator class="identity" /> 9 </id> 10 <property name="name" type="string" /> 11 <property name="age" type="int" /> 12 <set name="trainings" table="training" sort="natural"> 13 <!-- 外键列--> 14 <key column="person_id" not-null="true"/> 15 <!-- 数据列 --> 16 <element column="training_name" type="string" not-null="true"/> 17 </set> 18 </class> 19 </hibernate-mapping>
在PersonManager.java中添加测试方法sortedSetTest(),
1 private static void sortedSetTest() { 2 SortedPerson ssp = new SortedPerson(); 3 ssp.setAge(20); 4 ssp.setName("xiao ming"); 5 SortedSet<String> trainings = new TreeSet<String>(); 6 trainings.add("Wild Java Camp"); 7 trainings.add("Sun SCJP"); 8 ssp.setTrainings(trainings); 9 exec("SortedPerson.hbm.xml", ssp); 10 }
执行PersonManager.java,发现mysql中training表记录已经按照element列按字母排序,
数据库SQL排序
上面这种排序是在程序运行内存中实现的,之后再将已排序记录持久化到数据库中。
如果我们不希望用程序内心来排序,而是希望通过数据来排序,则可以在<set> <map> <bag>中添加order-by属性。
这种方式是通过继承LinkedHashSet或者LinkedHashMap来实现的,因此确保你的JDK版本至少大于1.4
例如我们可以在上面的SetPerson.hbm.xml中,在<set>标签中添加order-by属性,
1 <set name="schools" table="school" order-by="school_name asc">
集合属性的性能分析
延迟加载
Hibernate对集合属性默认采用延迟加载策略,即lazy="true". 因为集合属性很可能有成千上万条数据,而持久化类在实例化的时候并不一定会马上用关联的集合属性。
修改属性(增删改)的性能
集合可分两类
有序集合:集合元素可根据key或者index访问
无序集合:集合元素只能遍历
对于有序集合,因为有联合主键存在(主键已被索引),因此可以直接定位到相应元素进行修改,效率高。
对于无序集合,只有外键,没有索引,也就没有联合主键,也就没法按主键索引定位元素(可能可以建立索引,但性能可能非常差),修改这种集合性能将会很低。
因此对于<bag>这种集合,即没有索引字段,同时还不允许元素重复,hibernate无法直接判断元素是否重复,当需要修改集合属性值时,只能先删除全部集合,再重新创建集合,因此性能是最差的。
所以,对于有序集合属性的增,删,改将会有比较好的性能。(数组也是有序集合,但数组属性不支持延迟加载(因为长度固定),所以数组属性性能其实很低的)
延迟更新
对于多对多关联,值数据而言,如果改变Set集合的某个元素,hibernate并不能立即更新该元素对应的数据行。对于Set而言,只有在插入和删除操作时改变才有效。
反向集合(inverse="true")
修改Set集合属性性能并不高,但Hibernate还是处处采用了Set集合,这一方面是因为Set更贴近集合语意,
另一方面,在1-N关联中,1的一端通常都带有inverse="true"属性,即这种关联关系不再由一的一端维护,更新操作将在N的一端处理,这种情况下,无序考虑集合性能。
映射数据库对象
最后,还要来看看映射文件映射数据库对象,数据库对象指的是触发器,存储过程等等。Hibernate中使用<database-object>标签来映射数据库对象,用<create><drop>等标签标识具体的数据库操作,
下面演示映射创建数据表(DDL语句),
首先要定义一个映射文件,映射文件中可以用<create><drop>等指定具体的操作,然后直接将sql语句写在标签中。还可以添加一些<dialect>之类的标签指定更多属性,例如只在MySQL中有效,
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> 4 5 <hibernate-mapping> 6 <database-object> 7 <create>create table test(t_name varchar(255));</create> 8 <drop></drop> 9 </database-object> 10 </hibernate-mapping>
此映射文件不需要持久化类,我们直接在测试类中初始化一个Configuration对象并加载配置文件,打开一个SessionFactory,此映射文件就会生效,
在PersonManager.java中,新增测试方法dbObject()
1 private static void dbObject() { 2 //加载数据触发器来创建表 3 Configuration conf = new Configuration().configure(); 4 conf.addResource("hib/DbObject.hbm.xml"); 5 SessionFactory sf = conf.buildSessionFactory(); 6 }
注意,通过这种方式创建表,好一定要将hibernate.cfg.xml中的hbm2ddl.auto设置为create才能生效
1 <property name="hbm2ddl.auto">create</property>
执行PersonManager.java将会创建一张test表,
除了上面根据配置文件生成表的方法之外,还可以使用hibernate自带工具SchemaExport(可直接命令行执行), 使用工具的代码如下,
映射文件保持不变,在PersonManager.java中,将dbObject()方法修改为,
1 private static void dbObject() { 2 //加载数据触发器来创建表 3 Configuration conf = new Configuration().configure(); 4 conf.addResource("hib/DbObject.hbm.xml"); 5 //SessionFactory sf = conf.buildSessionFactory(); 6 7 SchemaExport se = new SchemaExport(conf); 8 se.setFormat(true).setOutputFile("new.sql").create(true, true); 9 10 }
注意如果通过SchemaExport的方式创建表,hibernate.cfg.xml中的hbm2ddl.auto就不需要强制设置为create了,update也行。
执行PersonManger.java,也能创建test表。