1. 如果没有对持久化类的相应字段设置public的setter和getter方法,而要持久化这个字段的话,可以在映射文件的property标签中设置access属性为field(默认为property)。如以下映射文件中的name字段:
<class name="bean.Customer" table="CUSTOMERS">
<id name="id" column="ID">
<generator class="increment"/>
</id>
<property name="name" column="NAME" access="field"/>
</class>
2. 在映射文件中,property标签的name属性所映射的字段,在持久化类中不一定是存在的,一般情况下该属性映射到持久化类中相应的getter和setter方法。还是以上述代码的name属性为例,持久化类中可能没有name字段,而是有对应的getName和setName方法。持久化类如下:
public class Customer {
private String firstName;
private String lastName;
public String getName() {
return firstName + " " + lastName;
}
//这里的name参数默认是用空格分隔的firstName加lastName的形式
public void setName(String name) {
this.firstName = name.split(" ")[0];
this.lastName = name.split(" ")[1];
}
}
3. Hibernate的主键生成策略
配置文件基本格式:
<id name="id" column="ID">
<generator class="xxxxx"/>
</id>
1)increment
这种策略Hibernate以递增的方式生成主键。每次插入数据之前先读取当前最大的ID,然后加1插入到下一条数据中。所以如果有多个Hibernate应用程序访问数据库,可能出现同时读取当前最大ID的情况,因而这种策略只在一个Hibernate应用程序访问数据库时可以有效工作。更准确的说,同一进程中如果有多个sessionFactory访问同一个数据库,也可能导致插入操作失败。
① increment不依赖于底层数据库,因此适用于所有的数据库系统。
② 适用于只有单个Hibernate应用程序访问同一个数据库的场合,不推荐在集群环境下使用。
③ ID必须为long、int或short类型。
2)identity
由底层数据库负责生成主键。要求数据库把主键定义为自动增长字段类型,如mysql,应该把主键定义为auto_increment类型,SQL SERVER中应该定义为IDENTITY类型。
① identity策略依赖底层数据库,因此底层数据库必须支持自动增长数据类型。常见的数据库包括:DB2、MySql、Ms SQL Server、Sybase等。
② ID必须是long、int或short类型。
3)sequence
由底层数据库提供的序列生成主键。配置文件格式为
<id name="id" column="ID">
<generator class="sequence">
<param name="sequence">xxxxx</param>
</generator>
</id>
① 要求底层数据库支持序列,常见数据库包括Oracle、DB2、SAP DB等。
② ID必须是long、int或short类型。
4)hilo
该策略由Hibernate按照一种high/low算法来生成标示符,它由数据库的特定表中的字段读取high值。配置文件格式如下
<id name="id" column="ID">
<generator class="hilo">
<param name="table">hi_value</param>
<param name="column">next_value</param>
<param name="max_lo">100</param>
</genarator>
</id>
① hilo策略不依赖底层数据库系统,因此它适用于所有数据库系统。
② ID必须是long、int或short类型。
③ high/low算法生成的标示符只能在一个数据库中保证唯一。
④ 当用户为Hibernate自行提供数据库连接,或者Hibernate通过JTA,从应用服务器的数据源获得数据库连接的情况下无法使用hilo。因为这不能保证hilo在新的数据库连接的事务中访问配置文件中指定的hi_value表。如果数据库支持序列,可以使用seqhilo生成器。例如,在Oracle数据库中
<id name="id" column="ID">
<generator class="seqhilo">
<param name="sequence">hi_value</param>
<param name="max_lo">100</param>
</genarator>
</id>
seqhilo从名为hi_value的序列中读取high值。
5)native
native策略依赖底层数据库对自动生成标示符的支持能力,来选择使用identity、sequence或者hilo标示符生成器。
① native策略适合于跨数据库平台开发。即同一个Hibernate应用程序需要连接多种数据库系统的场合。
② ID必须是long、int或short类型。
6)assigned
开发者编写程序负责生成主键,插入数据的时候hibernate不对主键做任何处理。
<id name="id" column="ID">
<generator class="assigned">
</id>
4. 新建态的对象持久化过程的注意事项
新建态的对象先将属性set完成之后再save为持久化对象。因为如果新建对象先调用save方法持久化,然后再set对象属性会造成额外次数的数据库访问。例如:
1)正确方式(访问一次数据库):
Customer customer = new Customer();
customer.setAddress("address again");
session.save(customer);
transaction.commit();
Hibernate生成的数据库访问语句为:
Hibernate:
select
max(ID)
from
CUSTOMERS
Hibernate:
insert
into
CUSTOMERS
(NAME, EMAIL, PASSWORD, PHONE, ADDRESS, SEX, IS_MARRIED, DESCRIPTION, IMAGE, BIRTHDAY, REGISTERED_TIME, ID)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2)错误方式(多次访问数据库):
Customer customer = new Customer();
session.save(customer);
customer.setAddress("address again");
transaction.commit();
Hibernate生成的数据库访问语句为:
Hibernate:
select
max(ID)
from
CUSTOMERS
Hibernate:
insert
into
CUSTOMERS
(NAME, EMAIL, PASSWORD, PHONE, ADDRESS, SEX, IS_MARRIED, DESCRIPTION, IMAGE, BIRTHDAY, REGISTERED_TIME, ID)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate:
update
CUSTOMERS
set
NAME=?,
EMAIL=?,
PASSWORD=?,
PHONE=?,
ADDRESS=?,
SEX=?,
IS_MARRIED=?,
DESCRIPTION=?,
IMAGE=?,
BIRTHDAY=?,
REGISTERED_TIME=?
where
ID=?
5. 一对多双向关联关系配置
这里主要演示部分配置文件的示例,客户(Customer)和订单(Order)是一种一对多的关系。Customer类中有Set<Order>类型的属性orders,Order类中有Customer类型的属性customer。
Order.hbm.xml文件的部分内容如下:
<many-to-one
name="customer"
column="CUSTOMER_ID"
cascade="all"
class="Customer全类名"
/>
Customer.hbm.xml文件的部分内容如下:
<set name="orders" cascade="all" inverse="true">
<key column="CUSTOMER_ID" />
<one-to-many calss="Order全类名" />
</set>
注意事项:
1)映射一对多双向关联时,应该在<set>中把inverse属性设置为“true”,这样能提高性能。
2)建立或者删除两个对象的双向关联时,应同时修改关联对象两方的相应属性(本例中为customer和orders)。
① 建立关联
customer.getOrders().add(order);
order.setCustomer(customer).
② 删除关联
customer.getOrders().remove(order);
order.setCustomer(null);
6. 映射组成关系
假设Customer类中包含家庭地址和公司地址两部分,这两部分的分别由省、市、街道组成,Customer类的部分代码如下:
private String homeProvince;
private String homeCity;
private String homeStreet;
private String compProvince;
private String compCity;
private String compStreet;
//省略getter和setter方法
显然从以上代码抽象出新的Address类,并把Customer类中的相应属性替换为homeAddress和compAddress可以提高代码重用。
Address类代码如下:
public class Address {
private String province;
private String city;
private String street;
private Customer customer;//该属性对应Customer.hbm.xml文件中<component>标签的子标签<parent>的name属性
//省略getter和setter方法
}
Customer类部分代码如下:
private Address homeAddress;
private Address compAddress;
//省略getter和setter方法
另一方面,从数据库设计角度考虑,在不增加数据冗余的情况下,为了减少频繁的多表联查,没有建立与Address类对应的数据库表,而是在CUSTOMERS表中设置列HOME_PROVINCE、HOME_CITY、HOME_STREET、COMP_PROVINCE、COMP_CITY、COMP_STREET。此时Customer.hbm.xml文件中的映射关系应该如下设置。
<component name="homeAddress" class="bean.Address">
<parent name="customer"/><!-- name属性取值为Address类中类型为Customer的属性 -->
<property name="province" column="HOME_PROVINCE"/>
<property name="city" column="HOME_CITY"/>
<property name="street" column="HOME_STREET"/>
</component>
<component name="compAddress" class="bean.Address">
<parent name="customer"/><!-- name属性取值为Address类中类型为Customer的属性 -->
<property name="province" column="COMP_PROVINCE"/>
<property name="city" column="COMP_CITY"/>
<property name="street" column="COMP_STREET"/>
</component>
注意事项:
① Address类和Customer类不是简单的一对一或一对多、多对一、多对多关系,Address是由Customer类的组成部分(province、city、street)抽象出来的(而前面所讲的Order不是Customer的组成部分),二者构成组成关系。
② 定义Address类时除了基本的属性(province、city、street)外,还要定义它的父项属性customer,Customer.hbm.xml映射文件中会映射到这个父项属性。
③ hibernate映射文件用<component>标签映射组成关系。
④ <component>标签可以嵌套,上文从Customer类中抽象出了Address组件,如果Address类中还可以抽象出其他组件,那么在新组件的类中定义address属性,然后hibernate映射文件的<component>标签中嵌套新组件的<component>标签即可。
7. 用户自定义映射类型
如果想把持久化类中Integer类型的属性(例如phone)映射为数据库中的varchar类型,需要自定义新的映射类型。首先新建一个类(例如IntegerUserType),它实现Hibernate的UserType接口。该类的实现内容较多,这里不再列举,参见其他网络资料。完成之后配置文件的映射方式如下:
<property name="phone" type="IntegerUserType的全类名" column="PHONE" />
注意事项:本处只列举了一个自定义映射类型映射一个java属性的情况,实际一个自定义类型可以映射多个持久化类的属性,自行参考UserType实现类的网络资料。
8. Hibernate检索策略
1)class级别(即hbm.xml中的<class>标签)的检索策略默认为lazy="false",因为一般情况下一个对象被load下来之后是要直接使用的,lazy属性设置为"true"没有什么意义。另外lazy属性的设置只对load方法有意义,对get方法不起作用。
2)集合检索策略默认为lazy="true",以因为在一对多或多对多的映射关系中加载一个对象的时候,并不总是要使用它的集合属性,而当集合属性包含大量数据的时候进行加载是会对性能产生极大影响的。
① 与class级别的检索策略不同,集合检索策略对load和get方法同样有效。
② 持久化类的集合属性只能是set、list、map等接口而不能是具体的实现类,因为延迟加载情况下Hibernate赋给持久化类集合的初始值是Hibernate自身对set、list、map等接口的实现类。
3)<set>元素的outer-join属性有三个取值"true"、"false"、"auto",默认值为"auto"。其值设置为true时,加载持久化对象生成采用左外连接方式查询的sql语句访问数据库。其值设置为false时,会先从数据库加载持久化类的对象,然后根据对象的id加载set集合中的对象。
① outer-join属性并不会像延迟加载那样减少检索的数据量,而是通过减少数据库访问次数提高性能,在检索数据量方面它等同于lazy="false"时的情况。
② outer-join和lazy属性不能同时设置为"true",因为采用左外连接的加载方式必然一次检索出所有数据,就不可能再实现“延迟加载”。
③ hibernate中只有一个<set>的outer-join属性可以设置为true。
4)<many-to-one>和<one-to-one>的outer-join属性可以设置为auto、true、false三个值。
① auto:如果“一”的一方配置文件中class标签的lazy属性设置为true,那么“多”的一方加载的时候,相应的属性延迟加载;否则采用迫切左外连接检索策略。
② true:“多”的一方加载的时候总是采用迫切左外连接检索策略。
③ false:始终不采用迫切左外连接的加载策略。如果“一”的一方配置文件中class标签的lazy属性设置为true,那么“多”的一方加载的时候,相应的属性延迟加载;否则采取立即加载策略。
注意事项:对于多对一或者一对一关联,应该优先考虑使用外连接检索策略,因为它能减少数据库访问次数,而且加载的数据量也不是很大(被动加载的一方是“一”);当然如果总是只利用到主动加载的对象的属性的话,也可以将被动加载的一方class标签的lazy属性设置为true。
5)Hibernate对迫切左外连接检索的限制
① 如果同一个类的映射文件中有多个<set>元素,只允许有一个<set>元素的outer-join属相设置为true;
② Hibernate只允许一条sql语句中包含一个x对多(一对多或多对多)的迫切左外连接。
③ Hibernate不限制一条sql语句中x对一(一对一或多对一)的迫切左外连接书目。
9. 实例化投影查询的结果
投影查询通过”select“关键字实现,查询结果中仅包含实体的部分属性。例如以下投影查询的HQL语句:
List list = session.createQuery("select c.name, c.homeAddress.province from Customer c where c.name like "T%").list();
查询结果的集合中包含若干对象数组(Object[])类型的元素,这些关系类型的数据不符合面向对象的思想,可以用预先定义的CustomerRow对象封装查询数据。CustomerRow采用JavaBean形式,其属性与select语句查询的实体属性一一对应,适用于需要经常查询实体的某些固定属性组合的情形。CustomerRow类的源程序如下:
public class CustomerRow implements Serializable {
private String name;
private String province;
/**必须提供用于初始化所有属性的构造方法*/
public CustomerRow(String name, String province) {
super();
this.name = name;
this.province = province;
}
/**省略访问对象属性的getter和setter方法*/
}
建立以上封装对象之后,查询语句更改如下:
List list = session.createQuery("select new CustomerRow(c.name, c.homeAddress.province) from Customer c where c.name like "T%").list();
此时查询结果的集合中包含若干CustomerRow类型的对象(需要从Object显式转换为CustomerRow类型)。
10. 优化投影查询的性能
有如下两条查询语句:
from Customer c inner join c.orders;
select c.name, o.orderNumber from Customer c inner join c.orders ;
以上语句对应的SQL语句差不多,因此查询出的数据也差不多。关键区别在于前者返回的是Customer和Order的持久化对象,他们位于Session的缓存中。后者返回关系数据,不会占用session缓存。查询操作一般涉及大量数据,而且很多时候都不涉及数据的修改或删除。如果采用第一种形式会导致大量的持久化对象位于session中,而且session还必须负责这些对象与数据库的同步。而采用第二种方式能提高查询性能,只要应用程序不在引用这些数据,他们所占用的内存就会被释放。
对于第二种情况的HQL语句,还可以用上一节描述的JavaBean封装查询结果,使用面向对象的方式使用查询结果。封装了的对象没有映射到Hibernate,它不是持久化类,也就不会被放入session缓存中。
11. 继承关系的映射
假设有People、Student和Teacher三个类,People是父类,Student和People是它的子类。代码如下:
People类:
public class People
{
/*父类所拥有的属性*/
private String id;
private String name;
private String sex;
private String age;
private Timestamp birthday;
/*get和set方法*/
}
Student类:
public class Student extends People
{
/*学生独有的属性*/
private String cardId;//学号
public String getCardId()
{
return cardId;
}
public void setCardId(String cardId)
{
this.cardId = cardId;
}
}
Teacher类:
public class Teacher extends People
{
/*Teacher所独有的属性*/
private int salary;//工资
public int getSalary()
{
return salary;
}
public void setSalary(int salary)
{
this.salary = salary;
}
}
继承关系的映射方式有如下三种:
① 一个子类对应一张表
该方案是使继承体系中每一个子类都对应数据库中的一张表,这个表的信息是完备的,包含了父类的公共字段和子类独有的字段。这种策略用<union-subclass>标签来定义子类。People.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>
<class name="com.suxiaolei.hibernate.pojos.People" abstract="true">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<property name="name" column="name" type="string"></property>
<property name="sex" column="sex" type="string"></property>
<property name="age" column="age" type="string"></property>
<property name="birthday" column="birthday" type="timestamp"></property>
<!-- union-subclass作为class的子元素时extends属性可以忽略
<union-subclass name="com.suxiaolei.hibernate.pojos.Student" table="student">
<property name="cardId" column="cardId" type="string"></property>
</union-subclass>
<union-subclass name="com.suxiaolei.hibernate.pojos.Teacher" table="teacher">
<property name="salary" column="salary" type="integer"></property>
</union-subclass>
-->
</class>
<union-subclass name="com.suxiaolei.hibernate.pojos.Student"
table="student" extends="com.suxiaolei.hibernate.pojos.People">
<property name="cardId" column="cardId" type="string"></property>
</union-subclass>
<union-subclass name="com.suxiaolei.hibernate.pojos.Teacher"
table="teacher" extends="com.suxiaolei.hibernate.pojos.People">
<property name="salary" column="salary" type="integer"></property>
</union-subclass>
</hibernate-mapping>
People类有多少个子类就需要配置多少个<union-subclass>标签,<union-subclass>标签的name属性指定子类的全限定名称,table属性指定对应的数据库表,extends属性指定父类的全限定名称。如果<union-subclass>标签位于<class>标签内部extends属性可以不设置。<class>标签的abstract属性如果设置为true则不会生成表结构,如果设置成false会生成表结构,但是不会插入数据。
② 使用一张表表示所有继承体系下的类的属性的并集
这种策略是使用<subclass>标签实现的。因为类继承体系下会有多个子类,要把多个类的信息存放在一张表中,必须有某种机制来区分哪些记录是属于哪个类的。Hibernate中的这种机制就是在表中添加一个字段,用这个字段的值来进行区分。在表中添加的这个标识列用<discriminator>标签来实现。将继承体系中的所有信息都表示在同一张表中后,这个类没有的属性会被自动赋值为null。文件People.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>
<class name="com.suxiaolei.hibernate.pojos.People" table="people">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<discriminator column="peopleType" type="string"></discriminator>
<property name="name" column="name" type="string"></property>
<property name="sex" column="sex" type="string"></property>
<property name="age" column="age" type="string"></property>
<property name="birthday" column="birthday" type="timestamp"></property>
<subclass name="com.suxiaolei.hibernate.pojos.Student" discriminator-value="student">
<property name="cardId" column="cardId" type="string"></property>
</subclass>
<subclass name="com.suxiaolei.hibernate.pojos.Teacher" discriminator-value="teacher">
<property name="salary" column="salary" type="string"></property>
</subclass>
</class>
</hibernate-mapping>
<discriminator>标签用来在表中创建一个标识列,column属性用来指定标识列的列名,type指定标识列的类型。<subclass>标签用于配置子类信息,有多少个子类就要配置多少个<subclass>标签。name指定子类的全限定名称,discriminator-value指定该子类对应的标识列的取值,extends用法与<union-subclass>中extends的用法一致。
③ 每一个子类使用一张表存储它们特有的属性,然后与父类对应的表以一对一主键关联的方式关联起来。
这种策略使用<joined-subclass>标签来定义子类。父类、每一个子类分别对应一张数据库表。父类对应的数据库表中,存储所有记录的公共信息,,子类对应的数据库表中,只定义子类特有的属性映射的字段。子类对应的数据表与父类对应的数据表,通过一对一主键关联的方式关联起来。People表存储了所有子类的信息,但是只包含子类信息的公共部分,要查询子类的特有信息,要通过People记录的主键,到子表中查找具有相同主键值的记录。People.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>
<class name="com.suxiaolei.hibernate.pojos.People" table="people">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<property name="name" column="name" type="string"></property>
<property name="sex" column="sex" type="string"></property>
<property name="age" column="age" type="string"></property>
<property name="birthday" column="birthday" type="timestamp"></property>
<joined-subclass name="com.suxiaolei.hibernate.pojos.Student" table="student">
<key column="id"></key>
<property name="cardId" column="cardId" type="string"></property>
</joined-subclass>
<joined-subclass name="com.suxiaolei.hibernate.pojos.Teacher" table="teacher">
<key column="id"></key>
<property name="salary" column="salary" type="integer"></property>
</joined-subclass>
</class>
</hibernate-mapping>
<joined-subclass>标签需要一个<key>标签,用于指定子类与父类之间是通过哪个字段关联的。