Hibernate性能优化之抓取策略和调用存储过程

1.Hibernate抓取策略



一、前言



转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/42705585

使用hibernate一年多了,一直觉得他是一个很好用的持久层框架,在处理含有多个复杂的关联关系的数据表时,hibernate提供的各种关联映射可以让我们用少量的代码快速、便捷的去维护各种外键关系,当然他的核心还是允许我们以“面向对象”的方式去操作数据表,因为我们的Java语言就是面向对象的,所以我们使用ORM的持久层框架应该更容易理解和上手,他还有许许多多的优点,比如我们使用HQL可以无视数据库的移植等等,同样的,这样一系列的封装也就导致了性能的下降,所以我们在实际项目中使用hibernate的话,优化是必不可少的工作,所以从本篇blog开始我就总结一下我所用到的和了解到的hibernate优化技术,首先是抓取策略。



二、抓取策略详解与应用



在hibernate官方文档的第21章中,我们可以看到“性能提升”首先介绍的就是抓取策略,


下面看一下文档中对抓取策略的概述:“当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate可以使用抓取策略获取关联对象。抓取策略可以在O/R映射的配置文件中声明,也可以在特定的HQL或条件查询(Criteria Query)中重载声明”

简单分析一下,实体间的关联关系导航,举个例子,学生和班级是多对一的关系,我要通过学生对象去查找该学生所对应的班级对象,这就是一个最简单的导航。下面就通过“学生”和“班级”这两个实体通过代码来演示一下如何使用抓取策略。

Student:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. import java.util.Date;  
  4.   
  5. public class Student {  
  6.   
  7.     private int id;  
  8.     private String name;  
  9.     private String sex;  
  10.     private Date birthday;  
  11.     private Classroom cla;  
  12.   
  13.     public int getId() {  
  14.         return id;  
  15.     }  
  16.   
  17.     public void setId(int id) {  
  18.         this.id = id;  
  19.     }  
  20.   
  21.     public String getName() {  
  22.         return name;  
  23.     }  
  24.   
  25.     public void setName(String name) {  
  26.         this.name = name;  
  27.     }  
  28.   
  29.     public String getSex() {  
  30.         return sex;  
  31.     }  
  32.   
  33.     public void setSex(String sex) {  
  34.         this.sex = sex;  
  35.     }  
  36.   
  37.     public Date getBirthday() {  
  38.         return birthday;  
  39.     }  
  40.   
  41.     public void setBirthday(Date birthday) {  
  42.         this.birthday = birthday;  
  43.     }  
  44.   
  45.     public Classroom getCla() {  
  46.         return cla;  
  47.     }  
  48.   
  49.     public void setCla(Classroom cla) {  
  50.         this.cla = cla;  
  51.     }  
  52.   
  53. }  
Student.hbm.xml:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping package="com.wl.entity">  
  6.     <class name="Student" table="t_student">  
  7.         <id name="id" column="stu_id">  
  8.             <generator class="native" />  
  9.         </id>  
  10.         <property name="name" />  
  11.         <property name="sex" />  
  12.         <property name="birthday" type="date" />  
  13.         <many-to-one name="cla" column="cid"/>  
  14.     </class>  
  15. </hibernate-mapping>  

Classroom:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. public class Classroom {  
  4.   
  5.     private int id;  
  6.     private String name;  
  7.   
  8.     public int getId() {  
  9.         return id;  
  10.     }  
  11.   
  12.     public void setId(int id) {  
  13.         this.id = id;  
  14.     }  
  15.   
  16.     public String getName() {  
  17.         return name;  
  18.     }  
  19.   
  20.     public void setName(String name) {  
  21.         this.name = name;  
  22.     }  
  23.   
  24. }  

Classroom.hbm.xml:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping package="com.wl.entity">  
  6.     <class name="Classroom" table="t_classroom">  
  7.         <id name="id" column="cla_id">  
  8.             <generator class="native" />  
  9.         </id>  
  10.         <property name="name" />  
  11.     </class>  
  12. </hibernate-mapping>  

hibernate中有一条best practice就是尽量少的使用双向关联,所以这里我就通过最常用的单向多对一来举例说明了。这个多对一的例子相信我们在初学hibernate的时候就已经很了解了,在为数据库插入了少量测试数据之后,现在我写了这样的一个测试方法:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session.load(Student.class1);  
  6.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  7. }  

很简单,测试了一下load,打印了学生自身的属性和其所关联的班级的属性,这里Hibernate就已经使用了抓取测试,至于是什么抓取策略,我们看一下console中打印的sql语句:


可以看到总共发了2条sql语句,一条查询学生,一条查询班级。很明显这是类似于延迟加载的一种策略,因为他并没有一次性的为我们把学生和班级一起查询出来,而是用到哪个才查询哪个。下面我们在Hibernate提供的策略里面找着看,


经过这4种策略的对比,不难发现,第二种查询抓取(Select fetching)正是我们上面的例子中用到的抓取策略,可是我们并没有进行配置,所以说:基于O/R映射文件配置关联关系的情况下,导航关联对象默认使用的是查询抓取(select fetching),所以说我们在<many-to-one>中指定fetch=“select”会发现运行结果不会有任何变化,这就是默认的行为。再看一下蓝色线条标出的话,如果显示指定lazy=“false”的话会禁止延迟抓取,什么情况下需要这样做呢?下面就再测试一下,在Student.hbm.xml的配置文件中的<many-to-one>中加上lazy=“false”,略微修改上面的测试方法,让其只打印stu.getName(),不打印stu.getCla().getName(),可以看到如下结果:


可以看到,我只查询了学生的Name,但是却又发了1条SQL语句去查询班级,这就是禁止了延迟抓取的结果,会让我们的延迟加载失效,很显然这种情况无论何时都不应当出现,所以一般我们不会去禁止select fetching中的延迟抓取。


看完了第一种抓取策略,再看一下连接抓取(Join fetching),官方文档中的定义是:Hibernate通过在SELECT语句使用OUTER JOIN(外连接)来获得对象的关联实例或者关联集合。最开头也说了,抓取策略可以在O/R映射文件中配置,也可以在HQL中设置,那么现在就以这两种方式分别说一下Join fetching的用法,依旧是第一个例子,load学生对象后打印学生Name和班级Name。

首先是基于配置文件的设置,我们可以在Student的配置文件中的<many-to-one>中指定fetch=“join”,其他的都不用改,运行一下可以看到如下结果:


果然就如Join fetching的介绍一样,这里使用了一个left outer join自动关联查询了学生和班级,一条SQL就得到了结果,所以这种情况下使用和Select fetching相比,一个发1条SQL语句,一个发2条SQL语句,很明显我们应该使用Join fetching来处理。


接下来介绍一下HQL中使用Join fetching,在介绍这个之前,我们先看另一种情况,就是:在配置文件中设置fetch=“join”,然后在测试方法中不用load,用HQL替换load,测试方法的代码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session.createQuery("from Student stu where stu.id = 1")  
  6.             .uniqueResult();  
  7.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  8. }  
用这种方式代替了load,而且在配置文件中也配置了fetch=“join”,那么是否会自动通过一条outer join的SQL帮我们一次性查出结果呢?运行一下看看:

显示没有达到我们预想中的效果,也就是说如果使用HQL查询对象,那么在配置文件中声明fetch=“join”是无法实现连接抓取的,所以接下来就介绍一下如何在HQL中进行连接抓取。其实很简单,在HQL中这样写:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session  
  6.             .createQuery(  
  7.                     "from Student stu left outer join fetch stu.cla where stu.id=1")  
  8.             .uniqueResult();  
  9.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  10. }  

我们可以通过join fetch关键字来抓取指定的关联对象,这样就实现了在HQL中进行连接抓取,运行一下可以看到:


还是通过一条left outer join自动关联查询了学生和班级,这就是抓取策略中的Join fetching。但是Join fetching本身也存在一定的问题,就是如果我们不需要查询关联对象,那么他也会发一个outer join的SQL语句,尽管HQL我们可以控制,但是如果通过O/M映射文件配置的fetch=“join”的话这样是无法控制的。所以说一般情况下能用HQL进行连接抓取的话尽量用HQL去做,基于配置文件毕竟不如HQL灵活。


由于子查询抓取(Subselect fetching)是用来抓取集合的,我们这里没有用到双向关联,所以暂且不做相关介绍,最后看一下关于批量抓取(Batch fetching)的应用。

批量抓取顾名思义就是一次性抓取一批数据,在上面的例子中我们都是通过加载一个对象来举例说明的,接下来为数据库继续添加少量数据:


现在再通过最简单的HQL来查询一下学生表的所有数据并输出:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2.     public void testFetch02() {  
  3.         Session session = HibernateUtil.oepnSession();  
  4.         session.beginTransaction();  
  5.         List<Student> list = session.createQuery("from Student stu").list();  
  6.         for (Student stu : list) {  
  7.             System.out.println(stu.getName() + "," + stu.getCla().getName());  
  8.         }  
  9.     }  

运行之后可以看到:


一共发了4条SQL语句,第一条查询学生,后三条查询班级,我们一共有3条班级数据,也就是说Hibernate会为每一个班级发1条SQL去查询班级信息,当班级表的数据量很大时,这样就会发大量的SQL显示会降低性能,在这里我们就可以用Batch fetching去优化。很简单,我们在Classroom.hbm.xml的配置文件中的<class>根标签中指定batch-size即可,形如:

<class name="Classroom" table="t_classroom" batch-size="3">...</classroom>

这样就指定了我们一次查询会批量抓取3条classroom的数据,也就不用为每个班级都发一条SQL语句了,而是会通过一条SQL一次性的查出3个班级。

其他地方的代码都不用改,再次运行上面的Test方法可以看到:


可以看到当我们指定了batch-size=3,那么就通过1条SQL完成了3个班级的查询,这就是批量抓取。当然批量抓取也存在弊端,就是当数据量较大时,如果批量抓取过多的数据,也会影响性能的,因为抓取的数据会存在内存中,所以大多数情况下还是会使用基于HQL的Join fetching来替代它。



三、基于Annotation的抓取配置



基于Annotation的抓取配置和基于O/R映射文件的抓取配置略有不同,但基于HQL方面的配置都是一样的,这里就不详细举例说明了,仅仅简单的提一下需要注意的一些关键点:

1.xml中的fetch=“select/join”相当于annotation中的fetch = FetchType.LAZY/FetchType.EAGER,xml默认的是select抓取,而基于Annotation默认的是EAGER,也就是join抓取了。

2.批量抓取的设置中,xml是在需要批量抓取的对象的hbm文件中的class根标签中去指定batch-size=?,而基于annotation是在需要批量抓取的对象的类中指定@BatchSize(size=?),这个注解一般写在@Table或@Entity下面即可。

3.能用HQL的方式进行抓取尽量用HQL,因为这样可以无视xml或annotation各自的个性化配置。


2.Hibernate调用视图和存储过程


一、前言



转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/42705585

使用hibernate一年多了,一直觉得他是一个很好用的持久层框架,在处理含有多个复杂的关联关系的数据表时,hibernate提供的各种关联映射可以让我们用少量的代码快速、便捷的去维护各种外键关系,当然他的核心还是允许我们以“面向对象”的方式去操作数据表,因为我们的Java语言就是面向对象的,所以我们使用ORM的持久层框架应该更容易理解和上手,他还有许许多多的优点,比如我们使用HQL可以无视数据库的移植等等,同样的,这样一系列的封装也就导致了性能的下降,所以我们在实际项目中使用hibernate的话,优化是必不可少的工作,所以从本篇blog开始我就总结一下我所用到的和了解到的hibernate优化技术,首先是抓取策略。



二、抓取策略详解与应用



在hibernate官方文档的第21章中,我们可以看到“性能提升”首先介绍的就是抓取策略,


下面看一下文档中对抓取策略的概述:“当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate可以使用抓取策略获取关联对象。抓取策略可以在O/R映射的配置文件中声明,也可以在特定的HQL或条件查询(Criteria Query)中重载声明”

简单分析一下,实体间的关联关系导航,举个例子,学生和班级是多对一的关系,我要通过学生对象去查找该学生所对应的班级对象,这就是一个最简单的导航。下面就通过“学生”和“班级”这两个实体通过代码来演示一下如何使用抓取策略。

Student:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. import java.util.Date;  
  4.   
  5. public class Student {  
  6.   
  7.     private int id;  
  8.     private String name;  
  9.     private String sex;  
  10.     private Date birthday;  
  11.     private Classroom cla;  
  12.   
  13.     public int getId() {  
  14.         return id;  
  15.     }  
  16.   
  17.     public void setId(int id) {  
  18.         this.id = id;  
  19.     }  
  20.   
  21.     public String getName() {  
  22.         return name;  
  23.     }  
  24.   
  25.     public void setName(String name) {  
  26.         this.name = name;  
  27.     }  
  28.   
  29.     public String getSex() {  
  30.         return sex;  
  31.     }  
  32.   
  33.     public void setSex(String sex) {  
  34.         this.sex = sex;  
  35.     }  
  36.   
  37.     public Date getBirthday() {  
  38.         return birthday;  
  39.     }  
  40.   
  41.     public void setBirthday(Date birthday) {  
  42.         this.birthday = birthday;  
  43.     }  
  44.   
  45.     public Classroom getCla() {  
  46.         return cla;  
  47.     }  
  48.   
  49.     public void setCla(Classroom cla) {  
  50.         this.cla = cla;  
  51.     }  
  52.   
  53. }  
Student.hbm.xml:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping package="com.wl.entity">  
  6.     <class name="Student" table="t_student">  
  7.         <id name="id" column="stu_id">  
  8.             <generator class="native" />  
  9.         </id>  
  10.         <property name="name" />  
  11.         <property name="sex" />  
  12.         <property name="birthday" type="date" />  
  13.         <many-to-one name="cla" column="cid"/>  
  14.     </class>  
  15. </hibernate-mapping>  

Classroom:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. public class Classroom {  
  4.   
  5.     private int id;  
  6.     private String name;  
  7.   
  8.     public int getId() {  
  9.         return id;  
  10.     }  
  11.   
  12.     public void setId(int id) {  
  13.         this.id = id;  
  14.     }  
  15.   
  16.     public String getName() {  
  17.         return name;  
  18.     }  
  19.   
  20.     public void setName(String name) {  
  21.         this.name = name;  
  22.     }  
  23.   
  24. }  

Classroom.hbm.xml:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping package="com.wl.entity">  
  6.     <class name="Classroom" table="t_classroom">  
  7.         <id name="id" column="cla_id">  
  8.             <generator class="native" />  
  9.         </id>  
  10.         <property name="name" />  
  11.     </class>  
  12. </hibernate-mapping>  

hibernate中有一条best practice就是尽量少的使用双向关联,所以这里我就通过最常用的单向多对一来举例说明了。这个多对一的例子相信我们在初学hibernate的时候就已经很了解了,在为数据库插入了少量测试数据之后,现在我写了这样的一个测试方法:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session.load(Student.class1);  
  6.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  7. }  

很简单,测试了一下load,打印了学生自身的属性和其所关联的班级的属性,这里Hibernate就已经使用了抓取测试,至于是什么抓取策略,我们看一下console中打印的sql语句:


可以看到总共发了2条sql语句,一条查询学生,一条查询班级。很明显这是类似于延迟加载的一种策略,因为他并没有一次性的为我们把学生和班级一起查询出来,而是用到哪个才查询哪个。下面我们在Hibernate提供的策略里面找着看,


经过这4种策略的对比,不难发现,第二种查询抓取(Select fetching)正是我们上面的例子中用到的抓取策略,可是我们并没有进行配置,所以说:基于O/R映射文件配置关联关系的情况下,导航关联对象默认使用的是查询抓取(select fetching),所以说我们在<many-to-one>中指定fetch=“select”会发现运行结果不会有任何变化,这就是默认的行为。再看一下蓝色线条标出的话,如果显示指定lazy=“false”的话会禁止延迟抓取,什么情况下需要这样做呢?下面就再测试一下,在Student.hbm.xml的配置文件中的<many-to-one>中加上lazy=“false”,略微修改上面的测试方法,让其只打印stu.getName(),不打印stu.getCla().getName(),可以看到如下结果:


可以看到,我只查询了学生的Name,但是却又发了1条SQL语句去查询班级,这就是禁止了延迟抓取的结果,会让我们的延迟加载失效,很显然这种情况无论何时都不应当出现,所以一般我们不会去禁止select fetching中的延迟抓取。


看完了第一种抓取策略,再看一下连接抓取(Join fetching),官方文档中的定义是:Hibernate通过在SELECT语句使用OUTER JOIN(外连接)来获得对象的关联实例或者关联集合。最开头也说了,抓取策略可以在O/R映射文件中配置,也可以在HQL中设置,那么现在就以这两种方式分别说一下Join fetching的用法,依旧是第一个例子,load学生对象后打印学生Name和班级Name。

首先是基于配置文件的设置,我们可以在Student的配置文件中的<many-to-one>中指定fetch=“join”,其他的都不用改,运行一下可以看到如下结果:


果然就如Join fetching的介绍一样,这里使用了一个left outer join自动关联查询了学生和班级,一条SQL就得到了结果,所以这种情况下使用和Select fetching相比,一个发1条SQL语句,一个发2条SQL语句,很明显我们应该使用Join fetching来处理。


接下来介绍一下HQL中使用Join fetching,在介绍这个之前,我们先看另一种情况,就是:在配置文件中设置fetch=“join”,然后在测试方法中不用load,用HQL替换load,测试方法的代码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session.createQuery("from Student stu where stu.id = 1")  
  6.             .uniqueResult();  
  7.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  8. }  
用这种方式代替了load,而且在配置文件中也配置了fetch=“join”,那么是否会自动通过一条outer join的SQL帮我们一次性查出结果呢?运行一下看看:

显示没有达到我们预想中的效果,也就是说如果使用HQL查询对象,那么在配置文件中声明fetch=“join”是无法实现连接抓取的,所以接下来就介绍一下如何在HQL中进行连接抓取。其实很简单,在HQL中这样写:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session  
  6.             .createQuery(  
  7.                     "from Student stu left outer join fetch stu.cla where stu.id=1")  
  8.             .uniqueResult();  
  9.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  10. }  

我们可以通过join fetch关键字来抓取指定的关联对象,这样就实现了在HQL中进行连接抓取,运行一下可以看到:


还是通过一条left outer join自动关联查询了学生和班级,这就是抓取策略中的Join fetching。但是Join fetching本身也存在一定的问题,就是如果我们不需要查询关联对象,那么他也会发一个outer join的SQL语句,尽管HQL我们可以控制,但是如果通过O/M映射文件配置的fetch=“join”的话这样是无法控制的。所以说一般情况下能用HQL进行连接抓取的话尽量用HQL去做,基于配置文件毕竟不如HQL灵活。


由于子查询抓取(Subselect fetching)是用来抓取集合的,我们这里没有用到双向关联,所以暂且不做相关介绍,最后看一下关于批量抓取(Batch fetching)的应用。

批量抓取顾名思义就是一次性抓取一批数据,在上面的例子中我们都是通过加载一个对象来举例说明的,接下来为数据库继续添加少量数据:


现在再通过最简单的HQL来查询一下学生表的所有数据并输出:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2.     public void testFetch02() {  
  3.         Session session = HibernateUtil.oepnSession();  
  4.         session.beginTransaction();  
  5.         List<Student> list = session.createQuery("from Student stu").list();  
  6.         for (Student stu : list) {  
  7.             System.out.println(stu.getName() + "," + stu.getCla().getName());  
  8.         }  
  9.     }  

运行之后可以看到:


一共发了4条SQL语句,第一条查询学生,后三条查询班级,我们一共有3条班级数据,也就是说Hibernate会为每一个班级发1条SQL去查询班级信息,当班级表的数据量很大时,这样就会发大量的SQL显示会降低性能,在这里我们就可以用Batch fetching去优化。很简单,我们在Classroom.hbm.xml的配置文件中的<class>根标签中指定batch-size即可,形如:

<class name="Classroom" table="t_classroom" batch-size="3">...</classroom>

这样就指定了我们一次查询会批量抓取3条classroom的数据,也就不用为每个班级都发一条SQL语句了,而是会通过一条SQL一次性的查出3个班级。

其他地方的代码都不用改,再次运行上面的Test方法可以看到:


可以看到当我们指定了batch-size=3,那么就通过1条SQL完成了3个班级的查询,这就是批量抓取。当然批量抓取也存在弊端,就是当数据量较大时,如果批量抓取过多的数据,也会影响性能的,因为抓取的数据会存在内存中,所以大多数情况下还是会使用基于HQL的Join fetching来替代它。



三、基于Annotation的抓取配置



基于Annotation的抓取配置和基于O/R映射文件的抓取配置略有不同,但基于HQL方面的配置都是一样的,这里就不详细举例说明了,仅仅简单的提一下需要注意的一些关键点:

1.xml中的fetch=“select/join”相当于annotation中的fetch = FetchType.LAZY/FetchType.EAGER,xml默认的是select抓取,而基于Annotation默认的是EAGER,也就是join抓取了。

2.批量抓取的设置中,xml是在需要批量抓取的对象的hbm文件中的class根标签中去指定batch-size=?,而基于annotation是在需要批量抓取的对象的类中指定@BatchSize(size=?),这个注解一般写在@Table或@Entity下面即可。

3.能用HQL的方式进行抓取尽量用HQL,因为这样可以无视xml或annotation各自的个性化配置。




前言



转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/43022193上一篇blog记录了hibernate抓取策略的相关用法(http://blog.csdn.net/wlwlwlwl015/article/details/42705585),它主要是在对象导航时为我们进行HQL方面的优化。本篇blog将介绍一些通用性的优化方式,即在hibernate中使用视图和存储过程。在数据量比较大时(百万级),使用hibernate时不再推荐使用HQL,而是使用原生的SQL语句,而视图、索引、存储过程等数据库对象也都是基于底层数据库和原生的SQL派生出的优化方案,废话不多说,下面就开始通过代码介绍一下如何在hibernate中调用view、proc以及需要注意的一些关键点。



通过hibernate查询视图



数据库视图(View)的概念和优点等等就不说了,这个书上和网上都讲了很多,下面直接通过例子来看一下如何在hibernate中查询视图,依旧是上一篇中的例子,一对多的典型示例:班级→学生,先看一下数据表:



下面写一个简单的视图,例如需要查询以下几个字段:stu_id、sname、sex、birthday、cname,首先根据需求创建视图,

[sql]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. DELIMITER $$  
  2.   
  3. USE `wltestdb`$$  
  4.   
  5. DROP VIEW IF EXISTS `v_stuinfo`$$  
  6.   
  7. CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v_stuinfo` AS (  
  8. SELECT  
  9.   `t1`.`stu_id`   AS `stu_id`,  
  10.   `t1`.`name`     AS `sname`,  
  11.   `t1`.`sex`      AS `sex`,  
  12.   `t1`.`birthday` AS `birthday`,  
  13.   `t2`.`name`     AS `cname`  
  14. FROM (`t_student` `t1`  
  15.    JOIN `t_classroom` `t2`  
  16.      ON ((`t1`.`cid` = `t2`.`cla_id`))))$$  
  17.   
  18. DELIMITER ;  

通过查询语句select * from v_stuinfo即可查询视图,


OK,视图没问题,接下来就是如何映射和查询了,首先是通过hibernate建立view的mapping。


1.映射视图

如果不知道如何去手写view的映射,我们可以通过MyEclipse的hibernate的反向工程来生成View的映射实体及映射配置,打开DB Browser视图,找到我们的view然后右键Hibernate Reverse Engineering,视情况选择生成annotation或者xml文件,一路next之后我们可以发现多了三个文件,2个PO类一个hbm.xml文件,

我们可以看到生成了大写V打头+视图名的一个类,还有一个是在上一个类多加了Id结尾的类, 一共视图生成2个PO类和一个hbm.xml文件,这就是hibernate为我们映射的视图,由于视图是没有主键的,所以hibernate在无法确定<id>的情况下自然也就无法通过1个实体类完成映射,hibernate的解决办法是:用两个PO来映射视图,一个PO封装查询视图返回的所有列,另一个PO将封装好的对象作为联合主键,这样就能完成映射了。下面看一下这两个类的代码和配置文件的代码,

VStuinfo:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. /** 
  4.  * VStuinfo entity. @author MyEclipse Persistence Tools 
  5.  */  
  6.   
  7. public class VStuinfo implements java.io.Serializable {  
  8.   
  9.     // Fields  
  10.   
  11.     private VStuinfoId id;  
  12.   
  13.     // Constructors  
  14.   
  15.     /** default constructor */  
  16.     public VStuinfo() {  
  17.     }  
  18.   
  19.     /** full constructor */  
  20.     public VStuinfo(VStuinfoId id) {  
  21.         this.id = id;  
  22.     }  
  23.   
  24.     // Property accessors  
  25.   
  26.     public VStuinfoId getId() {  
  27.         return this.id;  
  28.     }  
  29.   
  30.     public void setId(VStuinfoId id) {  
  31.         this.id = id;  
  32.     }  
  33.   
  34. }  

VStinfoId:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. import java.util.Date;  
  4.   
  5. /** 
  6.  * VStuinfoId entity. @author MyEclipse Persistence Tools 
  7.  */  
  8.   
  9. public class VStuinfoId implements java.io.Serializable {  
  10.   
  11.     // Fields  
  12.   
  13.     private Integer stuId;  
  14.     private String sname;  
  15.     private String sex;  
  16.     private Date birthday;  
  17.     private String cname;  
  18.   
  19.     // Constructors  
  20.   
  21.     /** default constructor */  
  22.     public VStuinfoId() {  
  23.     }  
  24.   
  25.     /** minimal constructor */  
  26.     public VStuinfoId(Integer stuId) {  
  27.         this.stuId = stuId;  
  28.     }  
  29.   
  30.     /** full constructor */  
  31.     public VStuinfoId(Integer stuId, String sname, String sex, Date birthday,  
  32.             String cname) {  
  33.         this.stuId = stuId;  
  34.         this.sname = sname;  
  35.         this.sex = sex;  
  36.         this.birthday = birthday;  
  37.         this.cname = cname;  
  38.     }  
  39.   
  40.     // Property accessors  
  41.   
  42.     public Integer getStuId() {  
  43.         return this.stuId;  
  44.     }  
  45.   
  46.     public void setStuId(Integer stuId) {  
  47.         this.stuId = stuId;  
  48.     }  
  49.   
  50.     public String getSname() {  
  51.         return this.sname;  
  52.     }  
  53.   
  54.     public void setSname(String sname) {  
  55.         this.sname = sname;  
  56.     }  
  57.   
  58.     public String getSex() {  
  59.         return this.sex;  
  60.     }  
  61.   
  62.     public void setSex(String sex) {  
  63.         this.sex = sex;  
  64.     }  
  65.   
  66.     public Date getBirthday() {  
  67.         return this.birthday;  
  68.     }  
  69.   
  70.     public void setBirthday(Date birthday) {  
  71.         this.birthday = birthday;  
  72.     }  
  73.   
  74.     public String getCname() {  
  75.         return this.cname;  
  76.     }  
  77.   
  78.     public void setCname(String cname) {  
  79.         this.cname = cname;  
  80.     }  
  81.   
  82.     public boolean equals(Object other) {  
  83.         if ((this == other))  
  84.             return true;  
  85.         if ((other == null))  
  86.             return false;  
  87.         if (!(other instanceof VStuinfoId))  
  88.             return false;  
  89.         VStuinfoId castOther = (VStuinfoId) other;  
  90.   
  91.         return ((this.getStuId() == castOther.getStuId()) || (this.getStuId() != null  
  92.                 && castOther.getStuId() != null && this.getStuId().equals(  
  93.                 castOther.getStuId())))  
  94.                 && ((this.getSname() == castOther.getSname()) || (this  
  95.                         .getSname() != null && castOther.getSname() != null && this  
  96.                         .getSname().equals(castOther.getSname())))  
  97.                 && ((this.getSex() == castOther.getSex()) || (this.getSex() != null  
  98.                         && castOther.getSex() != null && this.getSex().equals(  
  99.                         castOther.getSex())))  
  100.                 && ((this.getBirthday() == castOther.getBirthday()) || (this  
  101.                         .getBirthday() != null  
  102.                         && castOther.getBirthday() != null && this  
  103.                         .getBirthday().equals(castOther.getBirthday())))  
  104.                 && ((this.getCname() == castOther.getCname()) || (this  
  105.                         .getCname() != null && castOther.getCname() != null && this  
  106.                         .getCname().equals(castOther.getCname())));  
  107.     }  
  108.   
  109.     public int hashCode() {  
  110.         int result = 17;  
  111.   
  112.         result = 37 * result  
  113.                 + (getStuId() == null ? 0 : this.getStuId().hashCode());  
  114.         result = 37 * result  
  115.                 + (getSname() == null ? 0 : this.getSname().hashCode());  
  116.         result = 37 * result  
  117.                 + (getSex() == null ? 0 : this.getSex().hashCode());  
  118.         result = 37 * result  
  119.                 + (getBirthday() == null ? 0 : this.getBirthday().hashCode());  
  120.         result = 37 * result  
  121.                 + (getCname() == null ? 0 : this.getCname().hashCode());  
  122.         return result;  
  123.     }  
  124.   
  125. }  

VStuinfo.hbm.xml:
[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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.     Mapping file autogenerated by MyEclipse Persistence Tools 
  6. -->  
  7. <hibernate-mapping>  
  8.     <class name="com.wl.entity.VStuinfo" table="v_stuinfo" catalog="wltestdb">  
  9.         <composite-id name="id" class="com.wl.entity.VStuinfoId">  
  10.             <key-property name="stuId" type="java.lang.Integer">  
  11.                 <column name="stu_id" />  
  12.             </key-property>  
  13.             <key-property name="sname" type="java.lang.String">  
  14.                 <column name="sname" />  
  15.             </key-property>  
  16.             <key-property name="sex" type="java.lang.String">  
  17.                 <column name="sex" />  
  18.             </key-property>  
  19.             <key-property name="birthday" type="java.util.Date">  
  20.                 <column name="birthday" length="10" />  
  21.             </key-property>  
  22.             <key-property name="cname" type="java.lang.String">  
  23.                 <column name="cname" />  
  24.             </key-property>  
  25.         </composite-id>  
  26.     </class>  
  27. </hibernate-mapping>  

2.查询视图

查询视图很简单,既然已经映射好了,那么就当成普通对象用HQL查询就可以了,下面看一下测试代码和运行结果,


没有问题,成功打印出了视图返回的两项数据。但这样映射视图需要注意一点,就是视图返回的所有列不能有NULL值,一旦有NULL值的话,那么上图中第18行返回的List必然为NULL(之前做项目就遇到这个问题,控制台发了正确的SQL语句,可返回的List对象总是NULL)。解决办法也很简单,可以给可能为空的列添加默认值,或者是给视图指定一个主键等等,当然如果视图一定不会返回NULL值的话就可以忽略这个问题了。


通过hibernate调用存储过程



同样的存储过程的概念和优点在这里就不做介绍了,依旧是通过一个完整的例子来演示hibernate中如何调用存储过程。

1.创建存储过程

依然使用班级和学生举例说明,并且还是查询上面的那5个列,只不过是用存储过程来实现,首先是创建存储过程,
[sql]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. DELIMITER $$  
  2.   
  3. USE `wltestdb`$$  
  4.   
  5. DROP PROCEDURE IF EXISTS `proc_stuinfo`$$  
  6.   
  7. CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_stuinfo`(  
  8.     IN pstuid INT  
  9.     )  
  10. BEGIN  
  11.     SELECT   
  12.         t1.stu_id AS stuid,t1.name AS sname,t1.sex,  
  13.         t1.birthday,t2.name AS cname   
  14.         FROM t_student t1 INNER JOIN t_classroom t2   
  15.         ON t1.cid=t2.cla_id   
  16.         WHERE t1.stu_id=pstuid;  
  17.     END$$  
  18.   
  19. DELIMITER ;  

可以看到提供了一个输入参数用于传stu_id,调用一下看看数据,

可以看到调用存储过程之后成功返回了数据,接下面就是如何通过hibernate去调用存储过程了。

2.在hibernate中调用存储过程

如何在hibernate中调用存储过程,这个问题我经常在面试中问别人,不理解的是这么常用的东西好像知道的人很少,不知道是存储过程用的少还是在jdbc的api不熟悉。其实通过hibernate调用比JDBC调用存储过程更简单,我们甚至不需要使用CallableStatement对象,直接通过creatSQLQuery("{call proc_name(param)}")就可以返回数据了。下面看一下测试代码和运行结果,

这里我封装了一个DTO对象用来保存proc返回的数据,可以看到console成功打印出了调用语句和数据结果。

我个人推荐用上面这种方式调用存储过程最简单,当然还有一些其它的方式,例如通过session得到Connection对象,再通过CallableStatement去调用存储过程,代码这样写结果也是一样的,

这里可以发现session.connection()方法已经过时,从hibernate3.2.2版本开始这个方法就不推荐使用了,而是通过spring提供的spring-orm包下的SessionFactoryUtils去获取Connection对象,形如:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. try {  
  2.             Connection connection = SessionFactoryUtils.getDataSource(  
  3.                     getSessionFactory()).getConnection();  
  4.         } catch (SQLException e) {  
  5.             // TODO Auto-generated catch block  
  6.             e.printStackTrace();  
  7.         }  
关于hibernate操作存储过程的内容暂时介绍到这里,以后如果有更深入的学习研究还会陆续更新本篇blog。

一、前言



转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/42705585

使用hibernate一年多了,一直觉得他是一个很好用的持久层框架,在处理含有多个复杂的关联关系的数据表时,hibernate提供的各种关联映射可以让我们用少量的代码快速、便捷的去维护各种外键关系,当然他的核心还是允许我们以“面向对象”的方式去操作数据表,因为我们的Java语言就是面向对象的,所以我们使用ORM的持久层框架应该更容易理解和上手,他还有许许多多的优点,比如我们使用HQL可以无视数据库的移植等等,同样的,这样一系列的封装也就导致了性能的下降,所以我们在实际项目中使用hibernate的话,优化是必不可少的工作,所以从本篇blog开始我就总结一下我所用到的和了解到的hibernate优化技术,首先是抓取策略。



二、抓取策略详解与应用



在hibernate官方文档的第21章中,我们可以看到“性能提升”首先介绍的就是抓取策略,


下面看一下文档中对抓取策略的概述:“当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate可以使用抓取策略获取关联对象。抓取策略可以在O/R映射的配置文件中声明,也可以在特定的HQL或条件查询(Criteria Query)中重载声明”

简单分析一下,实体间的关联关系导航,举个例子,学生和班级是多对一的关系,我要通过学生对象去查找该学生所对应的班级对象,这就是一个最简单的导航。下面就通过“学生”和“班级”这两个实体通过代码来演示一下如何使用抓取策略。

Student:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. import java.util.Date;  
  4.   
  5. public class Student {  
  6.   
  7.     private int id;  
  8.     private String name;  
  9.     private String sex;  
  10.     private Date birthday;  
  11.     private Classroom cla;  
  12.   
  13.     public int getId() {  
  14.         return id;  
  15.     }  
  16.   
  17.     public void setId(int id) {  
  18.         this.id = id;  
  19.     }  
  20.   
  21.     public String getName() {  
  22.         return name;  
  23.     }  
  24.   
  25.     public void setName(String name) {  
  26.         this.name = name;  
  27.     }  
  28.   
  29.     public String getSex() {  
  30.         return sex;  
  31.     }  
  32.   
  33.     public void setSex(String sex) {  
  34.         this.sex = sex;  
  35.     }  
  36.   
  37.     public Date getBirthday() {  
  38.         return birthday;  
  39.     }  
  40.   
  41.     public void setBirthday(Date birthday) {  
  42.         this.birthday = birthday;  
  43.     }  
  44.   
  45.     public Classroom getCla() {  
  46.         return cla;  
  47.     }  
  48.   
  49.     public void setCla(Classroom cla) {  
  50.         this.cla = cla;  
  51.     }  
  52.   
  53. }  
Student.hbm.xml:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping package="com.wl.entity">  
  6.     <class name="Student" table="t_student">  
  7.         <id name="id" column="stu_id">  
  8.             <generator class="native" />  
  9.         </id>  
  10.         <property name="name" />  
  11.         <property name="sex" />  
  12.         <property name="birthday" type="date" />  
  13.         <many-to-one name="cla" column="cid"/>  
  14.     </class>  
  15. </hibernate-mapping>  

Classroom:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. public class Classroom {  
  4.   
  5.     private int id;  
  6.     private String name;  
  7.   
  8.     public int getId() {  
  9.         return id;  
  10.     }  
  11.   
  12.     public void setId(int id) {  
  13.         this.id = id;  
  14.     }  
  15.   
  16.     public String getName() {  
  17.         return name;  
  18.     }  
  19.   
  20.     public void setName(String name) {  
  21.         this.name = name;  
  22.     }  
  23.   
  24. }  

Classroom.hbm.xml:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC  
  3. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  4. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">  
  5. <hibernate-mapping package="com.wl.entity">  
  6.     <class name="Classroom" table="t_classroom">  
  7.         <id name="id" column="cla_id">  
  8.             <generator class="native" />  
  9.         </id>  
  10.         <property name="name" />  
  11.     </class>  
  12. </hibernate-mapping>  

hibernate中有一条best practice就是尽量少的使用双向关联,所以这里我就通过最常用的单向多对一来举例说明了。这个多对一的例子相信我们在初学hibernate的时候就已经很了解了,在为数据库插入了少量测试数据之后,现在我写了这样的一个测试方法:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session.load(Student.class1);  
  6.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  7. }  

很简单,测试了一下load,打印了学生自身的属性和其所关联的班级的属性,这里Hibernate就已经使用了抓取测试,至于是什么抓取策略,我们看一下console中打印的sql语句:


可以看到总共发了2条sql语句,一条查询学生,一条查询班级。很明显这是类似于延迟加载的一种策略,因为他并没有一次性的为我们把学生和班级一起查询出来,而是用到哪个才查询哪个。下面我们在Hibernate提供的策略里面找着看,


经过这4种策略的对比,不难发现,第二种查询抓取(Select fetching)正是我们上面的例子中用到的抓取策略,可是我们并没有进行配置,所以说:基于O/R映射文件配置关联关系的情况下,导航关联对象默认使用的是查询抓取(select fetching),所以说我们在<many-to-one>中指定fetch=“select”会发现运行结果不会有任何变化,这就是默认的行为。再看一下蓝色线条标出的话,如果显示指定lazy=“false”的话会禁止延迟抓取,什么情况下需要这样做呢?下面就再测试一下,在Student.hbm.xml的配置文件中的<many-to-one>中加上lazy=“false”,略微修改上面的测试方法,让其只打印stu.getName(),不打印stu.getCla().getName(),可以看到如下结果:


可以看到,我只查询了学生的Name,但是却又发了1条SQL语句去查询班级,这就是禁止了延迟抓取的结果,会让我们的延迟加载失效,很显然这种情况无论何时都不应当出现,所以一般我们不会去禁止select fetching中的延迟抓取。


看完了第一种抓取策略,再看一下连接抓取(Join fetching),官方文档中的定义是:Hibernate通过在SELECT语句使用OUTER JOIN(外连接)来获得对象的关联实例或者关联集合。最开头也说了,抓取策略可以在O/R映射文件中配置,也可以在HQL中设置,那么现在就以这两种方式分别说一下Join fetching的用法,依旧是第一个例子,load学生对象后打印学生Name和班级Name。

首先是基于配置文件的设置,我们可以在Student的配置文件中的<many-to-one>中指定fetch=“join”,其他的都不用改,运行一下可以看到如下结果:


果然就如Join fetching的介绍一样,这里使用了一个left outer join自动关联查询了学生和班级,一条SQL就得到了结果,所以这种情况下使用和Select fetching相比,一个发1条SQL语句,一个发2条SQL语句,很明显我们应该使用Join fetching来处理。


接下来介绍一下HQL中使用Join fetching,在介绍这个之前,我们先看另一种情况,就是:在配置文件中设置fetch=“join”,然后在测试方法中不用load,用HQL替换load,测试方法的代码如下:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session.createQuery("from Student stu where stu.id = 1")  
  6.             .uniqueResult();  
  7.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  8. }  
用这种方式代替了load,而且在配置文件中也配置了fetch=“join”,那么是否会自动通过一条outer join的SQL帮我们一次性查出结果呢?运行一下看看:

显示没有达到我们预想中的效果,也就是说如果使用HQL查询对象,那么在配置文件中声明fetch=“join”是无法实现连接抓取的,所以接下来就介绍一下如何在HQL中进行连接抓取。其实很简单,在HQL中这样写:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2. public void testFetchLoad() {  
  3.     Session session = HibernateUtil.oepnSession();  
  4.     session.beginTransaction();  
  5.     Student stu = (Student) session  
  6.             .createQuery(  
  7.                     "from Student stu left outer join fetch stu.cla where stu.id=1")  
  8.             .uniqueResult();  
  9.     System.out.println(stu.getName() + "," + stu.getCla().getName());  
  10. }  

我们可以通过join fetch关键字来抓取指定的关联对象,这样就实现了在HQL中进行连接抓取,运行一下可以看到:


还是通过一条left outer join自动关联查询了学生和班级,这就是抓取策略中的Join fetching。但是Join fetching本身也存在一定的问题,就是如果我们不需要查询关联对象,那么他也会发一个outer join的SQL语句,尽管HQL我们可以控制,但是如果通过O/M映射文件配置的fetch=“join”的话这样是无法控制的。所以说一般情况下能用HQL进行连接抓取的话尽量用HQL去做,基于配置文件毕竟不如HQL灵活。


由于子查询抓取(Subselect fetching)是用来抓取集合的,我们这里没有用到双向关联,所以暂且不做相关介绍,最后看一下关于批量抓取(Batch fetching)的应用。

批量抓取顾名思义就是一次性抓取一批数据,在上面的例子中我们都是通过加载一个对象来举例说明的,接下来为数据库继续添加少量数据:


现在再通过最简单的HQL来查询一下学生表的所有数据并输出:

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. @Test  
  2.     public void testFetch02() {  
  3.         Session session = HibernateUtil.oepnSession();  
  4.         session.beginTransaction();  
  5.         List<Student> list = session.createQuery("from Student stu").list();  
  6.         for (Student stu : list) {  
  7.             System.out.println(stu.getName() + "," + stu.getCla().getName());  
  8.         }  
  9.     }  

运行之后可以看到:


一共发了4条SQL语句,第一条查询学生,后三条查询班级,我们一共有3条班级数据,也就是说Hibernate会为每一个班级发1条SQL去查询班级信息,当班级表的数据量很大时,这样就会发大量的SQL显示会降低性能,在这里我们就可以用Batch fetching去优化。很简单,我们在Classroom.hbm.xml的配置文件中的<class>根标签中指定batch-size即可,形如:

<class name="Classroom" table="t_classroom" batch-size="3">...</classroom>

这样就指定了我们一次查询会批量抓取3条classroom的数据,也就不用为每个班级都发一条SQL语句了,而是会通过一条SQL一次性的查出3个班级。

其他地方的代码都不用改,再次运行上面的Test方法可以看到:


可以看到当我们指定了batch-size=3,那么就通过1条SQL完成了3个班级的查询,这就是批量抓取。当然批量抓取也存在弊端,就是当数据量较大时,如果批量抓取过多的数据,也会影响性能的,因为抓取的数据会存在内存中,所以大多数情况下还是会使用基于HQL的Join fetching来替代它。



三、基于Annotation的抓取配置



基于Annotation的抓取配置和基于O/R映射文件的抓取配置略有不同,但基于HQL方面的配置都是一样的,这里就不详细举例说明了,仅仅简单的提一下需要注意的一些关键点:

1.xml中的fetch=“select/join”相当于annotation中的fetch = FetchType.LAZY/FetchType.EAGER,xml默认的是select抓取,而基于Annotation默认的是EAGER,也就是join抓取了。

2.批量抓取的设置中,xml是在需要批量抓取的对象的hbm文件中的class根标签中去指定batch-size=?,而基于annotation是在需要批量抓取的对象的类中指定@BatchSize(size=?),这个注解一般写在@Table或@Entity下面即可。

3.能用HQL的方式进行抓取尽量用HQL,因为这样可以无视xml或annotation各自的个性化配置。

前言



转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/43022193上一篇blog记录了hibernate抓取策略的相关用法(http://blog.csdn.net/wlwlwlwl015/article/details/42705585),它主要是在对象导航时为我们进行HQL方面的优化。本篇blog将介绍一些通用性的优化方式,即在hibernate中使用视图和存储过程。在数据量比较大时(百万级),使用hibernate时不再推荐使用HQL,而是使用原生的SQL语句,而视图、索引、存储过程等数据库对象也都是基于底层数据库和原生的SQL派生出的优化方案,废话不多说,下面就开始通过代码介绍一下如何在hibernate中调用view、proc以及需要注意的一些关键点。



通过hibernate查询视图



数据库视图(View)的概念和优点等等就不说了,这个书上和网上都讲了很多,下面直接通过例子来看一下如何在hibernate中查询视图,依旧是上一篇中的例子,一对多的典型示例:班级→学生,先看一下数据表:



下面写一个简单的视图,例如需要查询以下几个字段:stu_id、sname、sex、birthday、cname,首先根据需求创建视图,

[sql]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. DELIMITER $$  
  2.   
  3. USE `wltestdb`$$  
  4.   
  5. DROP VIEW IF EXISTS `v_stuinfo`$$  
  6.   
  7. CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v_stuinfo` AS (  
  8. SELECT  
  9.   `t1`.`stu_id`   AS `stu_id`,  
  10.   `t1`.`name`     AS `sname`,  
  11.   `t1`.`sex`      AS `sex`,  
  12.   `t1`.`birthday` AS `birthday`,  
  13.   `t2`.`name`     AS `cname`  
  14. FROM (`t_student` `t1`  
  15.    JOIN `t_classroom` `t2`  
  16.      ON ((`t1`.`cid` = `t2`.`cla_id`))))$$  
  17.   
  18. DELIMITER ;  

通过查询语句select * from v_stuinfo即可查询视图,


OK,视图没问题,接下来就是如何映射和查询了,首先是通过hibernate建立view的mapping。


1.映射视图

如果不知道如何去手写view的映射,我们可以通过MyEclipse的hibernate的反向工程来生成View的映射实体及映射配置,打开DB Browser视图,找到我们的view然后右键Hibernate Reverse Engineering,视情况选择生成annotation或者xml文件,一路next之后我们可以发现多了三个文件,2个PO类一个hbm.xml文件,

我们可以看到生成了大写V打头+视图名的一个类,还有一个是在上一个类多加了Id结尾的类, 一共视图生成2个PO类和一个hbm.xml文件,这就是hibernate为我们映射的视图,由于视图是没有主键的,所以hibernate在无法确定<id>的情况下自然也就无法通过1个实体类完成映射,hibernate的解决办法是:用两个PO来映射视图,一个PO封装查询视图返回的所有列,另一个PO将封装好的对象作为联合主键,这样就能完成映射了。下面看一下这两个类的代码和配置文件的代码,

VStuinfo:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. /** 
  4.  * VStuinfo entity. @author MyEclipse Persistence Tools 
  5.  */  
  6.   
  7. public class VStuinfo implements java.io.Serializable {  
  8.   
  9.     // Fields  
  10.   
  11.     private VStuinfoId id;  
  12.   
  13.     // Constructors  
  14.   
  15.     /** default constructor */  
  16.     public VStuinfo() {  
  17.     }  
  18.   
  19.     /** full constructor */  
  20.     public VStuinfo(VStuinfoId id) {  
  21.         this.id = id;  
  22.     }  
  23.   
  24.     // Property accessors  
  25.   
  26.     public VStuinfoId getId() {  
  27.         return this.id;  
  28.     }  
  29.   
  30.     public void setId(VStuinfoId id) {  
  31.         this.id = id;  
  32.     }  
  33.   
  34. }  

VStinfoId:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. package com.wl.entity;  
  2.   
  3. import java.util.Date;  
  4.   
  5. /** 
  6.  * VStuinfoId entity. @author MyEclipse Persistence Tools 
  7.  */  
  8.   
  9. public class VStuinfoId implements java.io.Serializable {  
  10.   
  11.     // Fields  
  12.   
  13.     private Integer stuId;  
  14.     private String sname;  
  15.     private String sex;  
  16.     private Date birthday;  
  17.     private String cname;  
  18.   
  19.     // Constructors  
  20.   
  21.     /** default constructor */  
  22.     public VStuinfoId() {  
  23.     }  
  24.   
  25.     /** minimal constructor */  
  26.     public VStuinfoId(Integer stuId) {  
  27.         this.stuId = stuId;  
  28.     }  
  29.   
  30.     /** full constructor */  
  31.     public VStuinfoId(Integer stuId, String sname, String sex, Date birthday,  
  32.             String cname) {  
  33.         this.stuId = stuId;  
  34.         this.sname = sname;  
  35.         this.sex = sex;  
  36.         this.birthday = birthday;  
  37.         this.cname = cname;  
  38.     }  
  39.   
  40.     // Property accessors  
  41.   
  42.     public Integer getStuId() {  
  43.         return this.stuId;  
  44.     }  
  45.   
  46.     public void setStuId(Integer stuId) {  
  47.         this.stuId = stuId;  
  48.     }  
  49.   
  50.     public String getSname() {  
  51.         return this.sname;  
  52.     }  
  53.   
  54.     public void setSname(String sname) {  
  55.         this.sname = sname;  
  56.     }  
  57.   
  58.     public String getSex() {  
  59.         return this.sex;  
  60.     }  
  61.   
  62.     public void setSex(String sex) {  
  63.         this.sex = sex;  
  64.     }  
  65.   
  66.     public Date getBirthday() {  
  67.         return this.birthday;  
  68.     }  
  69.   
  70.     public void setBirthday(Date birthday) {  
  71.         this.birthday = birthday;  
  72.     }  
  73.   
  74.     public String getCname() {  
  75.         return this.cname;  
  76.     }  
  77.   
  78.     public void setCname(String cname) {  
  79.         this.cname = cname;  
  80.     }  
  81.   
  82.     public boolean equals(Object other) {  
  83.         if ((this == other))  
  84.             return true;  
  85.         if ((other == null))  
  86.             return false;  
  87.         if (!(other instanceof VStuinfoId))  
  88.             return false;  
  89.         VStuinfoId castOther = (VStuinfoId) other;  
  90.   
  91.         return ((this.getStuId() == castOther.getStuId()) || (this.getStuId() != null  
  92.                 && castOther.getStuId() != null && this.getStuId().equals(  
  93.                 castOther.getStuId())))  
  94.                 && ((this.getSname() == castOther.getSname()) || (this  
  95.                         .getSname() != null && castOther.getSname() != null && this  
  96.                         .getSname().equals(castOther.getSname())))  
  97.                 && ((this.getSex() == castOther.getSex()) || (this.getSex() != null  
  98.                         && castOther.getSex() != null && this.getSex().equals(  
  99.                         castOther.getSex())))  
  100.                 && ((this.getBirthday() == castOther.getBirthday()) || (this  
  101.                         .getBirthday() != null  
  102.                         && castOther.getBirthday() != null && this  
  103.                         .getBirthday().equals(castOther.getBirthday())))  
  104.                 && ((this.getCname() == castOther.getCname()) || (this  
  105.                         .getCname() != null && castOther.getCname() != null && this  
  106.                         .getCname().equals(castOther.getCname())));  
  107.     }  
  108.   
  109.     public int hashCode() {  
  110.         int result = 17;  
  111.   
  112.         result = 37 * result  
  113.                 + (getStuId() == null ? 0 : this.getStuId().hashCode());  
  114.         result = 37 * result  
  115.                 + (getSname() == null ? 0 : this.getSname().hashCode());  
  116.         result = 37 * result  
  117.                 + (getSex() == null ? 0 : this.getSex().hashCode());  
  118.         result = 37 * result  
  119.                 + (getBirthday() == null ? 0 : this.getBirthday().hashCode());  
  120.         result = 37 * result  
  121.                 + (getCname() == null ? 0 : this.getCname().hashCode());  
  122.         return result;  
  123.     }  
  124.   
  125. }  

VStuinfo.hbm.xml:
[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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.     Mapping file autogenerated by MyEclipse Persistence Tools 
  6. -->  
  7. <hibernate-mapping>  
  8.     <class name="com.wl.entity.VStuinfo" table="v_stuinfo" catalog="wltestdb">  
  9.         <composite-id name="id" class="com.wl.entity.VStuinfoId">  
  10.             <key-property name="stuId" type="java.lang.Integer">  
  11.                 <column name="stu_id" />  
  12.             </key-property>  
  13.             <key-property name="sname" type="java.lang.String">  
  14.                 <column name="sname" />  
  15.             </key-property>  
  16.             <key-property name="sex" type="java.lang.String">  
  17.                 <column name="sex" />  
  18.             </key-property>  
  19.             <key-property name="birthday" type="java.util.Date">  
  20.                 <column name="birthday" length="10" />  
  21.             </key-property>  
  22.             <key-property name="cname" type="java.lang.String">  
  23.                 <column name="cname" />  
  24.             </key-property>  
  25.         </composite-id>  
  26.     </class>  
  27. </hibernate-mapping>  

2.查询视图

查询视图很简单,既然已经映射好了,那么就当成普通对象用HQL查询就可以了,下面看一下测试代码和运行结果,


没有问题,成功打印出了视图返回的两项数据。但这样映射视图需要注意一点,就是视图返回的所有列不能有NULL值,一旦有NULL值的话,那么上图中第18行返回的List必然为NULL(之前做项目就遇到这个问题,控制台发了正确的SQL语句,可返回的List对象总是NULL)。解决办法也很简单,可以给可能为空的列添加默认值,或者是给视图指定一个主键等等,当然如果视图一定不会返回NULL值的话就可以忽略这个问题了。


通过hibernate调用存储过程



同样的存储过程的概念和优点在这里就不做介绍了,依旧是通过一个完整的例子来演示hibernate中如何调用存储过程。

1.创建存储过程

依然使用班级和学生举例说明,并且还是查询上面的那5个列,只不过是用存储过程来实现,首先是创建存储过程,
[sql]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. DELIMITER $$  
  2.   
  3. USE `wltestdb`$$  
  4.   
  5. DROP PROCEDURE IF EXISTS `proc_stuinfo`$$  
  6.   
  7. CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_stuinfo`(  
  8.     IN pstuid INT  
  9.     )  
  10. BEGIN  
  11.     SELECT   
  12.         t1.stu_id AS stuid,t1.name AS sname,t1.sex,  
  13.         t1.birthday,t2.name AS cname   
  14.         FROM t_student t1 INNER JOIN t_classroom t2   
  15.         ON t1.cid=t2.cla_id   
  16.         WHERE t1.stu_id=pstuid;  
  17.     END$$  
  18.   
  19. DELIMITER ;  

可以看到提供了一个输入参数用于传stu_id,调用一下看看数据,

可以看到调用存储过程之后成功返回了数据,接下面就是如何通过hibernate去调用存储过程了。

2.在hibernate中调用存储过程

如何在hibernate中调用存储过程,这个问题我经常在面试中问别人,不理解的是这么常用的东西好像知道的人很少,不知道是存储过程用的少还是在jdbc的api不熟悉。其实通过hibernate调用比JDBC调用存储过程更简单,我们甚至不需要使用CallableStatement对象,直接通过creatSQLQuery("{call proc_name(param)}")就可以返回数据了。下面看一下测试代码和运行结果,

这里我封装了一个DTO对象用来保存proc返回的数据,可以看到console成功打印出了调用语句和数据结果。

我个人推荐用上面这种方式调用存储过程最简单,当然还有一些其它的方式,例如通过session得到Connection对象,再通过CallableStatement去调用存储过程,代码这样写结果也是一样的,

这里可以发现session.connection()方法已经过时,从hibernate3.2.2版本开始这个方法就不推荐使用了,而是通过spring提供的spring-orm包下的SessionFactoryUtils去获取Connection对象,形如:
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. try {  
  2.             Connection connection = SessionFactoryUtils.getDataSource(  
  3.                     getSessionFactory()).getConnection();  
  4.         } catch (SQLException e) {  
  5.             // TODO Auto-generated catch block  
  6.             e.printStackTrace();  
  7.         }  
关于hibernate操作存储过程的内容暂时介绍到这里,以后如果有更深入的学习研究还会陆续更新本篇blog。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值