Hibernate提供本地SQL查询来完善HQL不能涵盖所有的查询特性。例如,可以通过下面的程序完成插入操作:
注意,本地SQL语句中,gg_department是表名,而不是HQL语句中的类名。
其实,HQL也支持删除和更新的操作(不支持插入操作),例如,下面的例子演示了用HQL进行删除:
Hibernate提供本地SQL查询来完善HQL不能涵盖所有的查询特性。例如,可以通过下面的程序完成插入操作:
注意,本地SQL语句中,gg_department是表名,而不是HQL语句中的类名。
其实,HQL也支持删除和更新的操作(不支持插入操作),例如,下面的例子演示了用HQL进行删除:
QBC查询就是通过使用Hibernate提供的Query By Criteria API来查询对象,这种API封装了SQL语句的动态拼装,对查询提供了更加面向对象的功能接口,下面通过几个Demo来介绍QBC的简单使用方法。
|
|
|
|
|
|
Hibernate提供了以下集中检索对象的方式:
本文主要介绍HQL检索方式。
HQL(Hibernate Query Language)是面向对象的查询语言,它和SQL查询语言有些相似。在Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。它有如下功能:
在查询语句中设定各种查询条件;
支持投影查询;
支持分页查询;
支持连接查询
支持分组查询,允许使用HAVING和GROUP BY关键字;
提供内置聚集函数,如sum(),min()和max();
支持子查询;
支持动态绑定参数;
能够调用用户定义的SQL函数或标准的SQL函数。
HQL检索方式包括以下步骤:
1.通过Session的createQuery()方法创建一个Query对象,它包括一个HQL查询语句,HQL语句中可以包含命名参数;
2.动态绑定参数;
3.调用Query相关方法执行查询语句。
Query接口支持方法链编程风格,它的setXxx()方法返回自身实例,而不是void类型。
HQL VS SQL:
HQL查询语句是面向对象的,Hibernate负责解析HQL查询语句,然后根据对象-关系映射文件中的映射信息,把HQL查询语句翻译成相应的SQL语句。HQL查询语句中的主体时域模型中的类及类的属性。
SQL查询语句是与关系数据库绑定在一起的。SQL查询语句中的主体时数据库表及表的字段。
绑定参数:
Hibernate的参数绑定机制依赖于JDBC API中的PreparedStatement的预定义SQL语句功能。
HQL的参数绑定有两种形式:
1.按参数名绑定:在HQL查询语句中定义命名参数,命名参数以”:”开头;
2.按参数位置绑定:在HQL查询语句中用”?”来定义参数位置。
Hibernate可以通过setEntity()方法把参数与一个实体对象绑定,还可以通过setParameter()方法来绑定任意类型的参数。
例如,可以通过下面的代码使用基于位置的参数来查询employee表中salary>1000,email字段中包含字符A,且属于部门号为1的部门的记录,查询结果按照salary进行排序:
在控制台打印的sql语句为:
或者使用基于参数名的参数实现相同的功能:
HQL主要通过下面两个方法来实现分页查询:
setFirstResult(int firstResult):设定从哪一个对象开始检索,参数firstResult指定这个对象在查询结果中的索引位置。索引位置的起始值为0,默认情况下,Query从查询结果中的第一个对象开始检索。
setMaxResults(int maxResults):设定一次最多检索出的对象的数目。默认情况下,Query和Criteria接口检索出查询结果中的所有的对象。
下面的代码实现了将每5条记录分为一页,查询第4页的记录的功能:
可以在映射文件中定义命名查询语句,实现将HQL语句的外置化,以实现通过不修改源码达到修改功能的效果。下面进行演示:
在Employee.hbm.xml文件中定义命名查询语句,其中query节点和class节点并列:
通过session.getNamedQuery()方法查询,参数为上述query节点的name属性值:
投影查询,即希望查询的结果仅包含实体的部分属性。通过SELECT关键字实现。下面的代码只希望查询指定部门员工的email,salary和dept属性。
这时候,返回的list中的元素是Object数组,其中每一个Object数组中存放了一条记录对应的属性值。
遍历元素为Object数组的list有点麻烦,可以通过另一种方式将查询的属性值存放在一个Employee对象中:先在Employee类中定义一个包含指定属性的构造器,然后通过下面的程序可以查询对应的属性:
Employee.java
|
|
注意构造器中属性顺序需和HQL语句中的属性顺序一致。
报表查询用于对数据分组和统计,与SQL一样,HQL利用GROUP BY关键字对数据分组,用HAVING关键字对分组数据设定约束条件。
在HQL查询语句中可以调用以下聚集函数:
count()
min()
max()
sum()
avg()
下面的代码演示了报表查询,根据department对employee进行分组,然后查询部门内最低工资大于3000的部门的最低工资和最高工资。
迫切左外连接:
在HQL中通过LEFT JOIN FETCH关键字表示迫切左外连接检索策略。
list()方法返回的集合存放实体对象的引用,每个Department对象关联的Employee集合都被初始化,存放所有关联的Employee的实体对象。
查询结果中可能会包含重复元素,可以通过一个HashSet来过滤重复元素,也可以在HQL语句中使用DISTINCT关键字来过滤。
下面的例子测试了迫切左外连接:
这种情况下将查询出所有的26条记录(包含重复的department),如果将注释行打开,或者将hql改为”SELECT DISTINCT d FROM Department d LEFT JOIN FETCH d.emps”,可以过滤掉重复元素:
左外连接:
HQL使用LEFT JOIN关键字表示左外连接查询。
list()方法返回的集合中存放的是对象数组类型。(存放了一个Department对象和一个Employee对象)。
由于list()方法返回的集合中存放的是一个Department对象和一个Employee对象构成的对象数组,所以不能用hashset来去重,因为每一个对象数组其实是不一样的。
如果需要去重,可以通过SELECT关键字使list()方法返回的集合中仅包含Department对象,然后用DISTINCT来去重。
可以根据配置文件来决定Employee集合的检索策略。默认情况下是lazy=true,所以当不使用Employee集合时是不会初始化Employee集合的。但是实际上,当查询Department时,Employee集合已经被查出来了,但是没有被初始化,非要等到使用Employee集合的时候再重新去查一遍。所以通常情况下,如果需要使用到表的左外连接,建议使用迫切的左外连接,因为反正查department的时候都要把employee集合都查出来,不如直接一次初始化完毕。
可以看到,在查询department后已经将employee都查出来了,但是没有初始化employee集合。
下面的程序演示了使用SELECT DISTINCT关键字去重。但是,每当使用到employee集合,还要重新select一遍。
迫切内连接:
HQL使用INNER JOIN FETCH关键字表示迫切内连接,也可以省略INNER关键字。
list()方法返回的集合中存放Department对象的引用,每个Department对象的Employee集合都被初始化,存放所有关联的Employee对象。
如果希望list()方法返回的集合仅包含Department对象,可以在HQL查询语句中使用SELECT关键字。
内连接:
INNER JOIN关键字表示内连接,也可以省略INNER关键字。
list()方法返回的集合中存放的每个元素对应查询结果的一条记录,每个元素都是对象数组类型。
(迫切)内连接和(迫切)左外连接的区别仅在于,(迫切)内连接不会返回从表中没有信息与主表对应的记录。例如,在上面的例子中,有的部门员工数为0,即该部门不存在记录department.id=employee.dept_id,但是(迫切)左外连接仍然会返回该记录,但是(迫切)内连接则不会。如下:
打印的depts.size()不再是上面的10,而是8,因为有6号部门和10号部门没有员工:
如果用SELECT来查询employee,道理也和上面讲述的时一样的,加FETCH会立即初始化department,不加FETCH则会等到使用department时才分别初始化对应的department。
检索策略追求的两个目标是:(以客户和订单的例子说明)
类级别可选的检索策略包括立即检索和延迟检索,默认为延迟检索。
立即检索: 立即加载检索方法指定的对象。
延迟检索: 延迟加载检索方法指定的对象。在使用具体的属性时,再进行加载。
类级别的检索策略可以通过在映射文件中的<class>元素的lazy属性进行设置,默认为true。
如果程序加载一个对象的目的是为了访问它的属性,可以采取立即检索。
如果程序加载一个持久化对象的目的是仅仅为了获得它的引用,可以采用延迟检索。注意延迟检索可能会出现懒加载异常。
无论<class>元素的lazy属性是true还是false, 仅是只对Session的load方法起作用,因为Session的get方法及Query的list方法在类级别总是使用立即检索策略。若<class>元素的lazy属性为true或取默认值,Session的load()方法不会执行查询数据表的SELECT语句,仅返回代理类对象的实例,该代理类实例有如下特征:
在映射文件中,用<set>元素来配置一对多关联及多对多关联关系,<set>元素有下面三个属性。
lazy属性:主要决定orders集合被初始化的时机。即到底是在加载Customer对象时就被初始化,还是在程序访问orders集合时被初始化。
batch-size属性:用来为延迟检索策略或立即检索策略设定批量检索的数量。批量检索能减少SELECT语句的数目,提高延迟检索或立即检索的运行性能。
fetch属性:
若取值为”select”或”subselect”,用来指定初始化orders的查询语句的形式,当fetch属性为”subselect”时,假定Session缓存中有n个orders集合代理类实例没有被初始化,Hibernate能够通过带子查询的select语句,来批量初始化n个orders集合代理类实例,此时,batch-size属性将被忽略,子查询中的select语句为查询CUSTOMERS表OID的SELECT语句。
若取值为”join”,则指定orders集合被初始化的时机。若把fetch设置为 “join”,则在检索Customer对象时,会采用迫切左外连接策略来检索所有关联的Order对象,此时,lazy属性将被忽略。迫切左外连接检索策略的优点在于使用更少的select语句。注意,Query的list方法会忽略映射文件中配置的迫切左外连接检索策略,而依旧采用延迟加载策略。
lazy属性和fetch属性的可能组合如下图所示:
关于延迟检索和增强延迟检索:
和<set>一样,<many-to-one>节点也有一个lazy属性和一个fetch属性,如下图所示:
且同上述一样,若fetch设为join,那么lazy属性将被忽略。
下面总结一下各种检索策略:
n-n(多对多)的关联关系必须通过连接表实现。下面以商品种类和商品之间的关系,即一个商品种类下面可以有多种商品,一种商品又可以属于多个商品种类,分别介绍单向的n-n关联关系和双向的n-n关联关系。
如果仅使用两张数据表,是不能实现n-n的关联关系的,如下图:
商品ITEM_AA属于商品种类CATEGORY_AA,但是如果商品ITEM_AA又同时属于商品种类CATEGORY_BB呢,两张数据表无法实现这种关系,所以我们需要使用到连接表,如下图所示:
我们添加一张连接表,其中的每一条记录表示某一个商品和某一个商品种类的对应关系。
单向的n-n关联关系的域模型和关系数据模型如下图所示:
下面来实现这种关系,首先新建两个类:
生成并编辑映射文件:
Category.hbm.xml
Item.hbm.xml
与1-n关联关系类似,在映射时,必须在category类的映射文件中设置set节点。但是不同的是,在set节点中指定的表为连接表CATEGORIES_ITEMS,而不再是所关联的类Item所对应的表,而且,在set节点中设置的是many-to-many节点,而不再是one-to-many。many-to-many节点的class属性指定了items集合中存放的对象类型是Item,colume属性指定了连接表CATEGORIES_ITEMS中参照ITEMS表的外键是ITEM_ID。
现在随便一个测试程序,发现除了生成了表CATEGORIES和表ITEMS,还生成了连接表CATEGORIES_ITEMS,表结构为:
下面测试save操作和get操作:
单向n-n关联关系的save操作:
运行程序,根据控制台打印的sql语句,发现会先往CATEGORIES表和ITEMS表分别插入两条记录,然后再往连接表CATEGORIES_ITEMS插入四条记录,这是符合我们的预期的:
单向n-n关联关系的get操作:
同之前的1-n、1-1中类似,先查询category会使用懒加载机制,不会立即加载items,只有使用到items的时候才会加载。当加载items的时候,是通过内连接连接中间表CATEGORIES_ITEMS进行查询的:
在双向的n-n关联关系中,两端的类都需要使用集合属性,即Category中要包含一个Set<Item>,Item中也要包含一个Set<Category>,,且在数据库中同样需要使用连接表连接。其域模型和关系数据模型如下:
下面编写代码,首先创建两个类:
生成并编辑映射文件:
Category.hbm.xml
Item.hbm.xml
大部门内容和单向n-n中类似,只是需要在Item类中添加字段
并且需要在Item类的映射文件中像Category映射文件中那样配置set节点。此外,还有一个很重要的地方,就是需要在Category和Item的其中一端的映射文件中的set节点中设置inverse=”true”属性,以放弃维护关联关系,否则两端都会维护默认关系,这在某些情况下会导致错误,下面会演示。
现在生成数据表,表结构和单向n-n中一样:
下面测试save操作和get操作:
双向n-n关联关系的save操作:
运行程序,和单向n-n中一样,一共打印8条insert语句,并成功插入记录。但是,现在我们移除Item.hbm.xml文件中的inverse=”true”属性,再运行程序,会抛出异常。这是因为在默认情况下n-n的两端都会维护关联关系,当执行上述四条save代码后,category要维护关联关系,往连接表中添加4条记录,然后item也要维护关联关系,往连接表中添加相同的4条记录,会导致连接表中主键重复,解决方法就是在Item.hbm.xml文件中设置inverse=”true”属性。
双向n-n关联关系的get操作:
双向n-n关联关系的get操作和单向n-n中一样,包含懒加载机制和内连接。
补充一下,双向n-n中两端的映射文件的字段对应如下图所示:
Hibernate的一对一关联关系,分为基于外键的一对一关联关系和基于主键的一对一关联关系。在这篇文章中,我们以部门和部门经理的例子来说明,一个部门对应唯一一个部门经理,一个部门经理也对应唯一一个部门。
在基于外键的一对一关联关系中,一端通过一个主键以外的字段关联另一端的主键,如下图所示:
在基于主键的一对一关联关系中,一端直接通过主键关联另一端的主键,并通过另一端的主键生成自己的主键,如下图所示:
下面进行详细说明。
对于基于外键的1-1关联关系,外键可以存放在任意一端,例如在本例中,外键既可以存放在department一端,也可以存放在manager一端,我们假设存放在department一端。在需要存放外键的一端,增加many-to-one节点,并且为many-to-one节点添加unique=”true”属性,来表示为1-1关联,添加unique=”true”属性了以后,不同的department就不能关联同一个manager了。在不存放外键的一端,需要使用one-to-one节点,并且在该节点中添加property-ref属性来指定存放外键一端的除主键以外的字段来作为关联字段。代码如下,首先创建两个类:
映射文件:
Department.hbm.xml
Manager.hbm.xml
运行一个空的test程序,可以生成数据库表managers和departments:
下面测试基于外键的1-1关联关系的save和get操作:
基于外键的1-1关联关系的save操作:
这段代码可以正常插入记录。和n-1关联关系中一样,如果先保存了存放外键一端的对象,后保存被外键关联的一端的对象,即如果先执行session.save(department);,后执行session.save(manager);,虽然同样可以正确保存,但是会多出一条update语句用于维护关联关系,所以通常建议先插入没有外键一端的对象,后插入有外键一端的对象。
基于外键的1-1关联关系的get操作:
同n-1关联关系一样,当查询存放外键的一端的对象department的时候,使用懒加载机制,即不会立即加载它关联的另一端的对象manager,而只有等到要使用manager的时候,才会发送select语句加载。那么同样也有可能发生懒加载异常。运行结果如下图所示:
值得注意的是,当要使用到manager对象时,是通过左外连接查询到manager对象的,连接条件是dept.manager_id = mgr.manager_id,这是正确的,因为我们在Manager.hbm.xml中的one-to-one节点中配置了property-ref=”mgr”,指定关联字段为department的mgr字段。如果没有设置property-ref属性,那么默认关联的字段为department的id字段,例如,我们去掉property-ref=”mgr”的设置,运行testGet()方法,则会打印如下的sql语句(连接条件是dept.dept_id = mgr.manager_id),这显然是不符合需求的。
还有一个注意点,就是当首先查询不存放外键的一端的对象时,即manager,由于其中没有设置外键关联到department,所以会使用左外连接查询,一并查询出另一端的对象,即department,而且已经完成了初始化。如下所示:
运行结果:
关于基于外键的一对一关系,还有一点值得注意,不能在两端都使用外键映射为1-1,例如下面这种情况,表department表和manager都分别设置了外键manager_id和department_id,那么当一条manager记录单向关联了一条department记录,而这条department记录却关联向另一条manager记录,就会出现问题,如下图所示:
基于主键的1-1映射策略,是指一端的主键生成器使用foreign策略,表明根据“对方”的主键来生成自己的主键,自己并不能独立生成主键。<param>子节点指定使用当前持久化类的哪一个属性来作为“对方”。例如:
采用foreign主键生成器策略的一端使用one-to-one元素映射关联属性,需要在one-to-one节点中设置constrained=”true”,以指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(即“对方”)所对应的数据库表的主键。另一端同样使用one-to-one节点映射关联关系。
下面我们仍以department和manager的例子进行测试,首先新建两个类:
映射文件:
Department.hbm.xml
Manager.hbm.xml
生成的数据库表如下:
managers
departments
可以看到,表departments是根据主键DEPT_ID来关联表managers的。
下面测试save和get方法:
基于主键的1-1关联关系的save操作:
和之前不同的是,不论是先执行session.save(department);,还是先执行session.save(manager);,效果都是一样的,都只有两条insert语句,不会有update语句,而且都会先执行insert into managers,后执行insert into departments,如下图:
这是因为,现在department是根据主键关联manager,主键是不能像外键那样先被置为null然后进行update修改的,所以不论哪一个语句放在前面,都会先等到manager记录插入后,再插入department记录。
基于主键的1-1关联关系的get操作:
基于主键的1-1和基于外键的1-1十分相似,在查询department时都使用懒加载机制,可能会抛出懒加载异常,在查询manager时都会使用左外连接,但不同的是,我们在Manager.hbm.xml文件的one-to-one节点中没有设置property-ref属性,即默认department中关联manager的字段是department的id,这正是我们在基于主键的1-1关系中希望的,所以可以看到,左外连接的连接条件是dept.dept_id = mgr.manager_id:
在领域模型中,类与类之间最普遍的关系就是关联关系。在 UML 中,关联是有方向的。以 Customer 和 Order 为例:一个用户能发出多个订单, 而一个订单只能属于一个客户。从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联。
单向 n-1 关联只需从 n 的一端可以访问 1 的一端。
域模型:从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性,而在 Customer 类中无需定义存放 Order 对象的集合属性。
关系数据模型: ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键。(外键)
Hibernate 使用
例如,在Customer和Order的例子中,首先创建两个类文件:
生成hibernate映射文件:
Customer.hbm.xml
Order.hbm.xml
先随便运行一个程序,来生成数据库表:
customers表
orders表
单向n-1的save操作:
运行程序,可以成功插入记录,并且在控制台只会打印三条insert语句:
但是,如果注释掉倒数4,5,6行的代码,而使用最后的三行代码,即先保存order1和order2,再保存customer,同样也可以成功插入,但是除了会输出三行insert语句,还会输出两行update语句。如下图:
这是因为在先插入order记录时,无法确定外键值customer_id,只能先置为null,所以只能等customer记录插入后,再额外发送 UPDATE 语句去更新customer_id。所以,建议先插入1的那一端,即customer,后插入n的这一端,即order。
单向n-1的get操作:
在n-1的get操作中,使用了懒加载机制。如果查询的是order对象,则默认情况下不会立即查找对应的customer对象,而只有等到需要使用这个customer对象时,才会发送select语句查询该customer对象。那么当然,如果在使用该对象之前,session被关闭了,也会抛出懒加载异常。
单向n-1的update操作:
单向n-1的delete操作:
在不设定级联关系的情况下,且 1 这一端的对象有 n 的对象在引用,则不能直接删除 1 这一端的对象。我们可以在customer映射文件的set节点中设置级联属性为级联删除,就可以直接删除1这一端的对象:
除了级联删除之外,还有其他的级联属性,如下图所示:
但是在开发时并不建议设定级联属性,而建议使用手工的方式来处理。
双向 1-n 与双向 n-1 是完全相同的两种情形。
双向 1-n 需要在1的一端可以访问n的一端,反之亦然。
域模型:从Order到Customer的多对一双向关联需要在Order类中定义一个Customer属性,而在Customer类中需定义存放Order对象的集合属性。
关系数据模型: ORDERS表中的CUSTOMER_ID参照CUSTOMER表的主键。(外键)
几个注意点:
在customer类中定义集合属性时,通常把它初始化为集合实现类的一个实例,这样可以提高程序的健壮性,避免应用程序访问取值为null的集合的方法抛出NullPointerException。
Hibernate 使用
|
|
生成Hibernate映射文件:
Order.hbm.xml(和单向n-1相同)
Customer.hbm.xml
生成的数据库表的结构和单向n-1中相同,现在来测试各种方法:
双向1-n的save操作:
当我们使用倒数第4,5,6行代码进行插入,即先保存customer,再保存order1和order2,这不同于单向n-1中的操作,虽然插入都是成功的,但是此处除了会打印三条insert语句,还会打印两条update语句:
这是因为, 由于是双向的关联关系,所以 1 的一端和 n 的一端都需要维护关联关系。当先插入customer对象后,customer中的set集合里面的order的id也还未知,会先被置为null,所以当order1和order2被插入后,会多出两条update语句。那么可以推测出,如果注释掉倒数第4,5,6行代码,而执行最后三行代码,那么除了有三条insert语句,还会打印出4条update语句,因为order1和order2先各维护一次,customer会再维护两次。如下图:
那么,如果我们不希望两端都维护关联关系,该怎么办呢?
解决办法是,在hibernate的配置文件中可以通过设置inverse属性来决定是由双向关联的哪一方来维护表和表之间的关系。inverse = false的为主动方,inverse = true 的为被动方。由主动方负责维护关 联关系。在没有设置inverse属性的情况下,默认父子两边都维护父子关系。
在双向 1-n 关系中,将 n 方设为主控方将有助于性能改善,而如果将 1 方设为主控方会额外多出update语句。这好比如果要国家元首记住全国人民的名字不太现实,但要让全国人民知道国家元首,就容易得多。
例如,现在我们在Customer.hbm.xml映射文件中设置set节点为:
然后在test方法中先插入customer,再插入order1和order2,则只会打印三条insert语句,而不会打印update语句:
双向1-n的get操作:
与单向n-1的get操作类似,在双向1-n的get操作中,如果先加载了customer对象,在使用它的orders集合之前,是不会加载orders集合的,这使用了懒加载机制,那么同样,也有可能抛出懒加载异常。
双向1-n的update操作:
可以通过1这一端的customer来更新n那一端的order。
双向1-n的delete操作:
同单向n-1中一样,在不设定级联关系的情况下,且 1 这一端的对象有 n 的对象在引用,则不能直接删除 1 这一端的对象。
此外,我们还可以在customer映射文件的set节点中设置order-by属性, 在查询时对集合中的元素进行排序,order-by 中使用的是表的字段名, 而不是持久化类的属性名。例如,对orders集合根据orderName进行降序排列:
最后补充一下,关于双向1-n的两个类的映射文件中,属性的对应关系,如下图所示:
建立域模型和关系数据模型有着不同的出发点。
域模型(面向对象设计):由程序代码组成,通过细化持久化类的的粒度可提高代码的可重用性,简化编程。
关系数据模型(数据库设计):在没有数据冗余的情况下,应该尽可能减少表的数目,简化表之间的参照关系,以便提高数据的访问速度。
在这篇文章中我们以Worker(工人)和Pay(薪酬)之间的关系为例进行说明:
Hibernate 把持久化类的属性分为两种:
值(value)类型:没有OID,不能被单独持久化,生命周期依赖于所属的持久化类的对象的生命周期。
实体(entity)类型:有OID,可以被单独持久化,有独立的生命周期。
显然在上述例子中Worker为实体类型,Pay为值类型。如果我们将这两个类根据组成关系映射为数据库中的一张表而不是两张表,那么单表查询的速度是要快于多表查询的。
Hibernate 使用 <component> 元素来映射组成关系, 该元素表明 pay 属性是 Worker 类一个组成部分, 在 Hibernate 中称之为组件。
下面进行测试,首先新建两个java类:
生成映射文件Worker.hbm.xml,并在映射文件中映射组成关系:
编写测试程序:
运行程序,发现生成了一张数据表worker:
并且成功插入了记录:
在Java中,代表时间和日期的类型包括:java.util.Date和java.util.Calendar。此外在JDBC API中还提供了3个扩展了java.util.Date类的子类:java.sql.Date,java.sql.Time和java.sql.Timestamp,这三个类分别和标准SQL类型中的 DATE,TIME和TIMESTAMP类型对应。
在标准SQL中,DATE类型表示日期,TIME 类型表示时间,TIMESTAMP 类型表示时间戳,同时包含日期和时间信息。
通常的映射方法为:
因为java.util.Date是java.sql.Date,java.sql.Time和java.sql.Timestamp的父类,所以java.util.Date可以对应标准SQL类型中的DATE,TIME和TIMESTAMP。所以通常在持久化类中将时间日期类型设置为java.util.Date。
然后通过Hibernate映射文件的property节点的type属性进行映射,例如:
其中 timestamp,date,time 既不是 Java 类型,也不是标准 SQL 类型,而是Hibernate映射类型。关于Hibernate映射类型,我们在介绍映射文件的时候提到过,它是连接Java类型和SQL类型的桥梁,三者之间的关系如下:
现在测试一下,首先在News.java中指定字段Date为java.util.Date类型:
然后在映射文件中映射为time类型:
编写测试程序:
运行程序,发现生成的news表中的DATE确实为time类型:
并且在控制台输出了java类型为java.sql.Time:
POJO类和关系数据库之间的映射可以用一个XML文档来定义。
通过POJO类的数据库映射文件,Hibernate可以理解持久化类和数据表之间的对应关系,也可以理解持久化类属性与数据库表列之间的对应关系。在运行时 Hibernate 将根据这个映射文件来生成各种SQL语句,映射文件的扩展名为 .hbm.xml。
下面对各个节点进行介绍
hibernate-mapping
class
在介绍下面的节点之前先来介绍一下映射对象标识符:
increment 标识符生成器
increment标识符生成器由Hibernate以递增的方式为代理主键赋值,Hibernate会先读取NEWS表中的主键的最大值,而接下来向NEWS表中插入记录时,就在max(id)的基础上递增,增量为 1。
适用范围:
由于increment生存标识符机制不依赖于底层数据库系统,因此它适合所有的数据库系统。
适用于只有单个Hibernate应用进程访问同一个数据库的场合, 在集群环境下不推荐使用它。(并发问题)
OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常 。
identity 标识符生成器
identity标识符生成器由底层数据库来负责生成标识符,它要求底层数据库把主键定义为自动增长字段类型。
适用范围:
由于identity生成标识符的机制依赖于底层数据库系统,因为要求底层数据库系统必须支持自动正常字段类型,这样的数据库包括:DB2,Mysql,MSSQLServer,Sybase等,不包括oracle。
OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。
sequence 标识符生成器
sequence标识符生成器利用底层数据库提供的序列来生成标识符。
Hibernate在持久化一个News对象时,先从底层数据库的news_seq序列中获得一个唯一的标识号,再把它作为主键值 。
适用范围:
由于sequence生成标识符的机制依赖于底层数据库系统的序列,因此, 要求底层数据库系统必须支持序列。支持序列的数据库包括: DB2, Oracle 等,不包括Mysql。
OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。
hilo 标识符生成器
hilo标识符生成器由Hibernate按照一种“high/low”算法生成标识符。
Hibernate在持久化一个News对象时,由Hibernate负责生成主键值。hilo 标识符生成器在生成标识符时,需要读取并修改HI_TABLE表中的NEXT_VALUE值。
适用范围:
由于hilo生存标识符机制不依赖于底层数据库系统,因此它适合所有的数据库系统。
OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。
native 标识符生成器 (通常使用的方式)
native标识符生成器依据底层数据库对自动生成标识符的支持能力,来选择使用identity,sequence或hilo标识符生成器。
适用范围:
由于native能根据底层数据库系统的类型,自动选择合适的标识符生成器,因此很适合于跨数据库平台开发。
OID必须为long,int或short类型,如果把OID定义为byte类型,在运行时会抛出异常。
property
派生属性:
并不是持久化类的所有属性都直接和表的字段匹配,持久化类的有些属性的值必须在运行时通过计算才能得到,这种属性称为派生属性,下面我们使用property的formular属性来测试派生属性:
首先在News.java中添加一个desc字段:
|
|
然后在News的映射文件中映射派生属性:
测试:
运行结果: