1.Hibernate抓取策略
一、前言
转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/42705585
使用hibernate一年多了,一直觉得他是一个很好用的持久层框架,在处理含有多个复杂的关联关系的数据表时,hibernate提供的各种关联映射可以让我们用少量的代码快速、便捷的去维护各种外键关系,当然他的核心还是允许我们以“面向对象”的方式去操作数据表,因为我们的Java语言就是面向对象的,所以我们使用ORM的持久层框架应该更容易理解和上手,他还有许许多多的优点,比如我们使用HQL可以无视数据库的移植等等,同样的,这样一系列的封装也就导致了性能的下降,所以我们在实际项目中使用hibernate的话,优化是必不可少的工作,所以从本篇blog开始我就总结一下我所用到的和了解到的hibernate优化技术,首先是抓取策略。
二、抓取策略详解与应用
在hibernate官方文档的第21章中,我们可以看到“性能提升”首先介绍的就是抓取策略,
![](https://img-blog.csdn.net/20150114101558363?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
下面看一下文档中对抓取策略的概述:“当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate可以使用抓取策略获取关联对象。抓取策略可以在O/R映射的配置文件中声明,也可以在特定的HQL或条件查询(Criteria Query)中重载声明”。
简单分析一下,实体间的关联关系导航,举个例子,学生和班级是多对一的关系,我要通过学生对象去查找该学生所对应的班级对象,这就是一个最简单的导航。下面就通过“学生”和“班级”这两个实体通过代码来演示一下如何使用抓取策略。
Student:
- package com.wl.entity;
-
- import java.util.Date;
-
- public class Student {
-
- private int id;
- private String name;
- private String sex;
- private Date birthday;
- private Classroom cla;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getSex() {
- return sex;
- }
-
- public void setSex(String sex) {
- this.sex = sex;
- }
-
- public Date getBirthday() {
- return birthday;
- }
-
- public void setBirthday(Date birthday) {
- this.birthday = birthday;
- }
-
- public Classroom getCla() {
- return cla;
- }
-
- public void setCla(Classroom cla) {
- this.cla = cla;
- }
-
- }
Student.hbm.xml:
- <?xml version="1.0"?>
- <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
- <hibernate-mapping package="com.wl.entity">
- <class name="Student" table="t_student">
- <id name="id" column="stu_id">
- <generator class="native" />
- </id>
- <property name="name" />
- <property name="sex" />
- <property name="birthday" type="date" />
- <many-to-one name="cla" column="cid"/>
- </class>
- </hibernate-mapping>
Classroom:
- package com.wl.entity;
-
- public class Classroom {
-
- private int id;
- private String name;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- }
Classroom.hbm.xml:
- <?xml version="1.0"?>
- <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
- <hibernate-mapping package="com.wl.entity">
- <class name="Classroom" table="t_classroom">
- <id name="id" column="cla_id">
- <generator class="native" />
- </id>
- <property name="name" />
- </class>
- </hibernate-mapping>
hibernate中有一条best practice就是尽量少的使用双向关联,所以这里我就通过最常用的单向多对一来举例说明了。这个多对一的例子相信我们在初学hibernate的时候就已经很了解了,在为数据库插入了少量测试数据之后,现在我写了这样的一个测试方法:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session.load(Student.class, 1);
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
很简单,测试了一下load,打印了学生自身的属性和其所关联的班级的属性,这里Hibernate就已经使用了抓取测试,至于是什么抓取策略,我们看一下console中打印的sql语句:
![](https://img-blog.csdn.net/20150114104144887?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到总共发了2条sql语句,一条查询学生,一条查询班级。很明显这是类似于延迟加载的一种策略,因为他并没有一次性的为我们把学生和班级一起查询出来,而是用到哪个才查询哪个。下面我们在Hibernate提供的策略里面找着看,
![](https://img-blog.csdn.net/20150114120348896?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
经过这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(),可以看到如下结果:
![](https://img-blog.csdn.net/20150114132649265?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到,我只查询了学生的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”,其他的都不用改,运行一下可以看到如下结果:
![](https://img-blog.csdn.net/20150114141731475?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
果然就如Join fetching的介绍一样,这里使用了一个left outer join自动关联查询了学生和班级,一条SQL就得到了结果,所以这种情况下使用和Select fetching相比,一个发1条SQL语句,一个发2条SQL语句,很明显我们应该使用Join fetching来处理。
接下来介绍一下HQL中使用Join fetching,在介绍这个之前,我们先看另一种情况,就是:在配置文件中设置fetch=“join”,然后在测试方法中不用load,用HQL替换load,测试方法的代码如下:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session.createQuery("from Student stu where stu.id = 1")
- .uniqueResult();
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
用这种方式代替了load,而且在配置文件中也配置了fetch=“join”,那么是否会自动通过一条outer join的SQL帮我们一次性查出结果呢?运行一下看看:
显示没有达到我们预想中的效果,也就是说如果使用HQL查询对象,那么在配置文件中声明fetch=“join”是无法实现连接抓取的,所以接下来就介绍一下如何在HQL中进行连接抓取。其实很简单,在HQL中这样写:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session
- .createQuery(
- "from Student stu left outer join fetch stu.cla where stu.id=1")
- .uniqueResult();
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
我们可以通过join fetch关键字来抓取指定的关联对象,这样就实现了在HQL中进行连接抓取,运行一下可以看到:
![](https://img-blog.csdn.net/20150114145039890?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
还是通过一条left outer join自动关联查询了学生和班级,这就是抓取策略中的Join fetching。但是Join fetching本身也存在一定的问题,就是如果我们不需要查询关联对象,那么他也会发一个outer join的SQL语句,尽管HQL我们可以控制,但是如果通过O/M映射文件配置的fetch=“join”的话这样是无法控制的。所以说一般情况下能用HQL进行连接抓取的话尽量用HQL去做,基于配置文件毕竟不如HQL灵活。
由于子查询抓取(Subselect fetching)是用来抓取集合的,我们这里没有用到双向关联,所以暂且不做相关介绍,最后看一下关于批量抓取(Batch fetching)的应用。
批量抓取顾名思义就是一次性抓取一批数据,在上面的例子中我们都是通过加载一个对象来举例说明的,接下来为数据库继续添加少量数据:
![](https://img-blog.csdn.net/20150115104337866?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20150115104914187?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
现在再通过最简单的HQL来查询一下学生表的所有数据并输出:
- @Test
- public void testFetch02() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- List<Student> list = session.createQuery("from Student stu").list();
- for (Student stu : list) {
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
- }
运行之后可以看到:
![](https://img-blog.csdn.net/20150115105206671?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
一共发了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方法可以看到:
![](https://img-blog.csdn.net/20150115110025593?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到当我们指定了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章中,我们可以看到“性能提升”首先介绍的就是抓取策略,
![](https://img-blog.csdn.net/20150114101558363?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
下面看一下文档中对抓取策略的概述:“当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate可以使用抓取策略获取关联对象。抓取策略可以在O/R映射的配置文件中声明,也可以在特定的HQL或条件查询(Criteria Query)中重载声明”。
简单分析一下,实体间的关联关系导航,举个例子,学生和班级是多对一的关系,我要通过学生对象去查找该学生所对应的班级对象,这就是一个最简单的导航。下面就通过“学生”和“班级”这两个实体通过代码来演示一下如何使用抓取策略。
Student:
- package com.wl.entity;
-
- import java.util.Date;
-
- public class Student {
-
- private int id;
- private String name;
- private String sex;
- private Date birthday;
- private Classroom cla;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getSex() {
- return sex;
- }
-
- public void setSex(String sex) {
- this.sex = sex;
- }
-
- public Date getBirthday() {
- return birthday;
- }
-
- public void setBirthday(Date birthday) {
- this.birthday = birthday;
- }
-
- public Classroom getCla() {
- return cla;
- }
-
- public void setCla(Classroom cla) {
- this.cla = cla;
- }
-
- }
Student.hbm.xml:
- <?xml version="1.0"?>
- <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
- <hibernate-mapping package="com.wl.entity">
- <class name="Student" table="t_student">
- <id name="id" column="stu_id">
- <generator class="native" />
- </id>
- <property name="name" />
- <property name="sex" />
- <property name="birthday" type="date" />
- <many-to-one name="cla" column="cid"/>
- </class>
- </hibernate-mapping>
Classroom:
- package com.wl.entity;
-
- public class Classroom {
-
- private int id;
- private String name;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- }
Classroom.hbm.xml:
- <?xml version="1.0"?>
- <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
- <hibernate-mapping package="com.wl.entity">
- <class name="Classroom" table="t_classroom">
- <id name="id" column="cla_id">
- <generator class="native" />
- </id>
- <property name="name" />
- </class>
- </hibernate-mapping>
hibernate中有一条best practice就是尽量少的使用双向关联,所以这里我就通过最常用的单向多对一来举例说明了。这个多对一的例子相信我们在初学hibernate的时候就已经很了解了,在为数据库插入了少量测试数据之后,现在我写了这样的一个测试方法:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session.load(Student.class, 1);
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
很简单,测试了一下load,打印了学生自身的属性和其所关联的班级的属性,这里Hibernate就已经使用了抓取测试,至于是什么抓取策略,我们看一下console中打印的sql语句:
![](https://img-blog.csdn.net/20150114104144887?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到总共发了2条sql语句,一条查询学生,一条查询班级。很明显这是类似于延迟加载的一种策略,因为他并没有一次性的为我们把学生和班级一起查询出来,而是用到哪个才查询哪个。下面我们在Hibernate提供的策略里面找着看,
![](https://img-blog.csdn.net/20150114120348896?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
经过这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(),可以看到如下结果:
![](https://img-blog.csdn.net/20150114132649265?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到,我只查询了学生的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”,其他的都不用改,运行一下可以看到如下结果:
![](https://img-blog.csdn.net/20150114141731475?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
果然就如Join fetching的介绍一样,这里使用了一个left outer join自动关联查询了学生和班级,一条SQL就得到了结果,所以这种情况下使用和Select fetching相比,一个发1条SQL语句,一个发2条SQL语句,很明显我们应该使用Join fetching来处理。
接下来介绍一下HQL中使用Join fetching,在介绍这个之前,我们先看另一种情况,就是:在配置文件中设置fetch=“join”,然后在测试方法中不用load,用HQL替换load,测试方法的代码如下:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session.createQuery("from Student stu where stu.id = 1")
- .uniqueResult();
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
用这种方式代替了load,而且在配置文件中也配置了fetch=“join”,那么是否会自动通过一条outer join的SQL帮我们一次性查出结果呢?运行一下看看:
显示没有达到我们预想中的效果,也就是说如果使用HQL查询对象,那么在配置文件中声明fetch=“join”是无法实现连接抓取的,所以接下来就介绍一下如何在HQL中进行连接抓取。其实很简单,在HQL中这样写:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session
- .createQuery(
- "from Student stu left outer join fetch stu.cla where stu.id=1")
- .uniqueResult();
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
我们可以通过join fetch关键字来抓取指定的关联对象,这样就实现了在HQL中进行连接抓取,运行一下可以看到:
![](https://img-blog.csdn.net/20150114145039890?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
还是通过一条left outer join自动关联查询了学生和班级,这就是抓取策略中的Join fetching。但是Join fetching本身也存在一定的问题,就是如果我们不需要查询关联对象,那么他也会发一个outer join的SQL语句,尽管HQL我们可以控制,但是如果通过O/M映射文件配置的fetch=“join”的话这样是无法控制的。所以说一般情况下能用HQL进行连接抓取的话尽量用HQL去做,基于配置文件毕竟不如HQL灵活。
由于子查询抓取(Subselect fetching)是用来抓取集合的,我们这里没有用到双向关联,所以暂且不做相关介绍,最后看一下关于批量抓取(Batch fetching)的应用。
批量抓取顾名思义就是一次性抓取一批数据,在上面的例子中我们都是通过加载一个对象来举例说明的,接下来为数据库继续添加少量数据:
![](https://img-blog.csdn.net/20150115104337866?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20150115104914187?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
现在再通过最简单的HQL来查询一下学生表的所有数据并输出:
- @Test
- public void testFetch02() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- List<Student> list = session.createQuery("from Student stu").list();
- for (Student stu : list) {
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
- }
运行之后可以看到:
![](https://img-blog.csdn.net/20150115105206671?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
一共发了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方法可以看到:
![](https://img-blog.csdn.net/20150115110025593?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到当我们指定了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中查询视图,依旧是上一篇中的例子,一对多的典型示例:班级→学生,先看一下数据表:
![](https://img-blog.csdn.net/20150122174040109?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20150122174129828?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
下面写一个简单的视图,例如需要查询以下几个字段:stu_id、sname、sex、birthday、cname,首先根据需求创建视图,
- DELIMITER $$
-
- USE `wltestdb`$$
-
- DROP VIEW IF EXISTS `v_stuinfo`$$
-
- CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v_stuinfo` AS (
- SELECT
- `t1`.`stu_id` AS `stu_id`,
- `t1`.`name` AS `sname`,
- `t1`.`sex` AS `sex`,
- `t1`.`birthday` AS `birthday`,
- `t2`.`name` AS `cname`
- FROM (`t_student` `t1`
- JOIN `t_classroom` `t2`
- ON ((`t1`.`cid` = `t2`.`cla_id`))))$$
-
- DELIMITER ;
通过查询语句select * from v_stuinfo即可查询视图,
![](https://img-blog.csdn.net/20150122174654781?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
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:
- package com.wl.entity;
-
-
-
-
-
- public class VStuinfo implements java.io.Serializable {
-
-
-
- private VStuinfoId id;
-
-
-
-
- public VStuinfo() {
- }
-
-
- public VStuinfo(VStuinfoId id) {
- this.id = id;
- }
-
-
-
- public VStuinfoId getId() {
- return this.id;
- }
-
- public void setId(VStuinfoId id) {
- this.id = id;
- }
-
- }
VStinfoId:
VStuinfo.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.wl.entity.VStuinfo" table="v_stuinfo" catalog="wltestdb">
- <composite-id name="id" class="com.wl.entity.VStuinfoId">
- <key-property name="stuId" type="java.lang.Integer">
- <column name="stu_id" />
- </key-property>
- <key-property name="sname" type="java.lang.String">
- <column name="sname" />
- </key-property>
- <key-property name="sex" type="java.lang.String">
- <column name="sex" />
- </key-property>
- <key-property name="birthday" type="java.util.Date">
- <column name="birthday" length="10" />
- </key-property>
- <key-property name="cname" type="java.lang.String">
- <column name="cname" />
- </key-property>
- </composite-id>
- </class>
- </hibernate-mapping>
2.查询视图
查询视图很简单,既然已经映射好了,那么就当成普通对象用HQL查询就可以了,下面看一下测试代码和运行结果,
没有问题,成功打印出了视图返回的两项数据。但这样映射视图需要注意一点,就是视图返回的所有列不能有NULL值,一旦有NULL值的话,那么上图中第18行返回的List必然为NULL(之前做项目就遇到这个问题,控制台发了正确的SQL语句,可返回的List对象总是NULL)。解决办法也很简单,可以给可能为空的列添加默认值,或者是给视图指定一个主键等等,当然如果视图一定不会返回NULL值的话就可以忽略这个问题了。
通过hibernate调用存储过程
同样的存储过程的概念和优点在这里就不做介绍了,依旧是通过一个完整的例子来演示hibernate中如何调用存储过程。
1.创建存储过程
依然使用班级和学生举例说明,并且还是查询上面的那5个列,只不过是用存储过程来实现,首先是创建存储过程,
- DELIMITER $$
-
- USE `wltestdb`$$
-
- DROP PROCEDURE IF EXISTS `proc_stuinfo`$$
-
- CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_stuinfo`(
- IN pstuid INT
- )
- BEGIN
- SELECT
- t1.stu_id AS stuid,t1.name AS sname,t1.sex,
- t1.birthday,t2.name AS cname
- FROM t_student t1 INNER JOIN t_classroom t2
- ON t1.cid=t2.cla_id
- WHERE t1.stu_id=pstuid;
- END$$
-
- DELIMITER ;
可以看到提供了一个输入参数用于传stu_id,调用一下看看数据,
![](https://img-blog.csdn.net/20150123102331421?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到调用存储过程之后成功返回了数据,接下面就是如何通过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对象,形如:
- try {
- Connection connection = SessionFactoryUtils.getDataSource(
- getSessionFactory()).getConnection();
- } catch (SQLException e) {
-
- e.printStackTrace();
- }
关于hibernate操作存储过程的内容暂时介绍到这里,以后如果有更深入的学习研究还会陆续更新本篇blog。
一、前言
转载请标明出处:http://blog.csdn.net/wlwlwlwl015/article/details/42705585
使用hibernate一年多了,一直觉得他是一个很好用的持久层框架,在处理含有多个复杂的关联关系的数据表时,hibernate提供的各种关联映射可以让我们用少量的代码快速、便捷的去维护各种外键关系,当然他的核心还是允许我们以“面向对象”的方式去操作数据表,因为我们的Java语言就是面向对象的,所以我们使用ORM的持久层框架应该更容易理解和上手,他还有许许多多的优点,比如我们使用HQL可以无视数据库的移植等等,同样的,这样一系列的封装也就导致了性能的下降,所以我们在实际项目中使用hibernate的话,优化是必不可少的工作,所以从本篇blog开始我就总结一下我所用到的和了解到的hibernate优化技术,首先是抓取策略。
二、抓取策略详解与应用
在hibernate官方文档的第21章中,我们可以看到“性能提升”首先介绍的就是抓取策略,
![](https://img-blog.csdn.net/20150114101558363?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
下面看一下文档中对抓取策略的概述:“当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate可以使用抓取策略获取关联对象。抓取策略可以在O/R映射的配置文件中声明,也可以在特定的HQL或条件查询(Criteria Query)中重载声明”。
简单分析一下,实体间的关联关系导航,举个例子,学生和班级是多对一的关系,我要通过学生对象去查找该学生所对应的班级对象,这就是一个最简单的导航。下面就通过“学生”和“班级”这两个实体通过代码来演示一下如何使用抓取策略。
Student:
- package com.wl.entity;
-
- import java.util.Date;
-
- public class Student {
-
- private int id;
- private String name;
- private String sex;
- private Date birthday;
- private Classroom cla;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public String getSex() {
- return sex;
- }
-
- public void setSex(String sex) {
- this.sex = sex;
- }
-
- public Date getBirthday() {
- return birthday;
- }
-
- public void setBirthday(Date birthday) {
- this.birthday = birthday;
- }
-
- public Classroom getCla() {
- return cla;
- }
-
- public void setCla(Classroom cla) {
- this.cla = cla;
- }
-
- }
Student.hbm.xml:
- <?xml version="1.0"?>
- <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
- <hibernate-mapping package="com.wl.entity">
- <class name="Student" table="t_student">
- <id name="id" column="stu_id">
- <generator class="native" />
- </id>
- <property name="name" />
- <property name="sex" />
- <property name="birthday" type="date" />
- <many-to-one name="cla" column="cid"/>
- </class>
- </hibernate-mapping>
Classroom:
- package com.wl.entity;
-
- public class Classroom {
-
- private int id;
- private String name;
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- }
Classroom.hbm.xml:
- <?xml version="1.0"?>
- <!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
- "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
- <hibernate-mapping package="com.wl.entity">
- <class name="Classroom" table="t_classroom">
- <id name="id" column="cla_id">
- <generator class="native" />
- </id>
- <property name="name" />
- </class>
- </hibernate-mapping>
hibernate中有一条best practice就是尽量少的使用双向关联,所以这里我就通过最常用的单向多对一来举例说明了。这个多对一的例子相信我们在初学hibernate的时候就已经很了解了,在为数据库插入了少量测试数据之后,现在我写了这样的一个测试方法:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session.load(Student.class, 1);
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
很简单,测试了一下load,打印了学生自身的属性和其所关联的班级的属性,这里Hibernate就已经使用了抓取测试,至于是什么抓取策略,我们看一下console中打印的sql语句:
![](https://img-blog.csdn.net/20150114104144887?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到总共发了2条sql语句,一条查询学生,一条查询班级。很明显这是类似于延迟加载的一种策略,因为他并没有一次性的为我们把学生和班级一起查询出来,而是用到哪个才查询哪个。下面我们在Hibernate提供的策略里面找着看,
![](https://img-blog.csdn.net/20150114120348896?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
经过这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(),可以看到如下结果:
![](https://img-blog.csdn.net/20150114132649265?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到,我只查询了学生的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”,其他的都不用改,运行一下可以看到如下结果:
![](https://img-blog.csdn.net/20150114141731475?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
果然就如Join fetching的介绍一样,这里使用了一个left outer join自动关联查询了学生和班级,一条SQL就得到了结果,所以这种情况下使用和Select fetching相比,一个发1条SQL语句,一个发2条SQL语句,很明显我们应该使用Join fetching来处理。
接下来介绍一下HQL中使用Join fetching,在介绍这个之前,我们先看另一种情况,就是:在配置文件中设置fetch=“join”,然后在测试方法中不用load,用HQL替换load,测试方法的代码如下:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session.createQuery("from Student stu where stu.id = 1")
- .uniqueResult();
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
用这种方式代替了load,而且在配置文件中也配置了fetch=“join”,那么是否会自动通过一条outer join的SQL帮我们一次性查出结果呢?运行一下看看:
显示没有达到我们预想中的效果,也就是说如果使用HQL查询对象,那么在配置文件中声明fetch=“join”是无法实现连接抓取的,所以接下来就介绍一下如何在HQL中进行连接抓取。其实很简单,在HQL中这样写:
- @Test
- public void testFetchLoad() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- Student stu = (Student) session
- .createQuery(
- "from Student stu left outer join fetch stu.cla where stu.id=1")
- .uniqueResult();
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
我们可以通过join fetch关键字来抓取指定的关联对象,这样就实现了在HQL中进行连接抓取,运行一下可以看到:
![](https://img-blog.csdn.net/20150114145039890?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
还是通过一条left outer join自动关联查询了学生和班级,这就是抓取策略中的Join fetching。但是Join fetching本身也存在一定的问题,就是如果我们不需要查询关联对象,那么他也会发一个outer join的SQL语句,尽管HQL我们可以控制,但是如果通过O/M映射文件配置的fetch=“join”的话这样是无法控制的。所以说一般情况下能用HQL进行连接抓取的话尽量用HQL去做,基于配置文件毕竟不如HQL灵活。
由于子查询抓取(Subselect fetching)是用来抓取集合的,我们这里没有用到双向关联,所以暂且不做相关介绍,最后看一下关于批量抓取(Batch fetching)的应用。
批量抓取顾名思义就是一次性抓取一批数据,在上面的例子中我们都是通过加载一个对象来举例说明的,接下来为数据库继续添加少量数据:
![](https://img-blog.csdn.net/20150115104337866?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20150115104914187?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
现在再通过最简单的HQL来查询一下学生表的所有数据并输出:
- @Test
- public void testFetch02() {
- Session session = HibernateUtil.oepnSession();
- session.beginTransaction();
- List<Student> list = session.createQuery("from Student stu").list();
- for (Student stu : list) {
- System.out.println(stu.getName() + "," + stu.getCla().getName());
- }
- }
运行之后可以看到:
![](https://img-blog.csdn.net/20150115105206671?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
一共发了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方法可以看到:
![](https://img-blog.csdn.net/20150115110025593?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到当我们指定了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中查询视图,依旧是上一篇中的例子,一对多的典型示例:班级→学生,先看一下数据表:
![](https://img-blog.csdn.net/20150122174040109?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
![](https://img-blog.csdn.net/20150122174129828?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
下面写一个简单的视图,例如需要查询以下几个字段:stu_id、sname、sex、birthday、cname,首先根据需求创建视图,
- DELIMITER $$
-
- USE `wltestdb`$$
-
- DROP VIEW IF EXISTS `v_stuinfo`$$
-
- CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v_stuinfo` AS (
- SELECT
- `t1`.`stu_id` AS `stu_id`,
- `t1`.`name` AS `sname`,
- `t1`.`sex` AS `sex`,
- `t1`.`birthday` AS `birthday`,
- `t2`.`name` AS `cname`
- FROM (`t_student` `t1`
- JOIN `t_classroom` `t2`
- ON ((`t1`.`cid` = `t2`.`cla_id`))))$$
-
- DELIMITER ;
通过查询语句select * from v_stuinfo即可查询视图,
![](https://img-blog.csdn.net/20150122174654781?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
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:
- package com.wl.entity;
-
-
-
-
-
- public class VStuinfo implements java.io.Serializable {
-
-
-
- private VStuinfoId id;
-
-
-
-
- public VStuinfo() {
- }
-
-
- public VStuinfo(VStuinfoId id) {
- this.id = id;
- }
-
-
-
- public VStuinfoId getId() {
- return this.id;
- }
-
- public void setId(VStuinfoId id) {
- this.id = id;
- }
-
- }
VStinfoId:
VStuinfo.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.wl.entity.VStuinfo" table="v_stuinfo" catalog="wltestdb">
- <composite-id name="id" class="com.wl.entity.VStuinfoId">
- <key-property name="stuId" type="java.lang.Integer">
- <column name="stu_id" />
- </key-property>
- <key-property name="sname" type="java.lang.String">
- <column name="sname" />
- </key-property>
- <key-property name="sex" type="java.lang.String">
- <column name="sex" />
- </key-property>
- <key-property name="birthday" type="java.util.Date">
- <column name="birthday" length="10" />
- </key-property>
- <key-property name="cname" type="java.lang.String">
- <column name="cname" />
- </key-property>
- </composite-id>
- </class>
- </hibernate-mapping>
2.查询视图
查询视图很简单,既然已经映射好了,那么就当成普通对象用HQL查询就可以了,下面看一下测试代码和运行结果,
没有问题,成功打印出了视图返回的两项数据。但这样映射视图需要注意一点,就是视图返回的所有列不能有NULL值,一旦有NULL值的话,那么上图中第18行返回的List必然为NULL(之前做项目就遇到这个问题,控制台发了正确的SQL语句,可返回的List对象总是NULL)。解决办法也很简单,可以给可能为空的列添加默认值,或者是给视图指定一个主键等等,当然如果视图一定不会返回NULL值的话就可以忽略这个问题了。
通过hibernate调用存储过程
同样的存储过程的概念和优点在这里就不做介绍了,依旧是通过一个完整的例子来演示hibernate中如何调用存储过程。
1.创建存储过程
依然使用班级和学生举例说明,并且还是查询上面的那5个列,只不过是用存储过程来实现,首先是创建存储过程,
- DELIMITER $$
-
- USE `wltestdb`$$
-
- DROP PROCEDURE IF EXISTS `proc_stuinfo`$$
-
- CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_stuinfo`(
- IN pstuid INT
- )
- BEGIN
- SELECT
- t1.stu_id AS stuid,t1.name AS sname,t1.sex,
- t1.birthday,t2.name AS cname
- FROM t_student t1 INNER JOIN t_classroom t2
- ON t1.cid=t2.cla_id
- WHERE t1.stu_id=pstuid;
- END$$
-
- DELIMITER ;
可以看到提供了一个输入参数用于传stu_id,调用一下看看数据,
![](https://img-blog.csdn.net/20150123102331421?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2x3bHdsd2wwMTU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
可以看到调用存储过程之后成功返回了数据,接下面就是如何通过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对象,形如:
- try {
- Connection connection = SessionFactoryUtils.getDataSource(
- getSessionFactory()).getConnection();
- } catch (SQLException e) {
-
- e.printStackTrace();
- }
关于hibernate操作存储过程的内容暂时介绍到这里,以后如果有更深入的学习研究还会陆续更新本篇blog。