前言
上一篇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,首先根据需求创建视图,
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即可查询视图,
OK,视图没问题,接下来就是如何映射和查询了,首先是通过hibernate建立view的mapping。
1.映射视图
如果不知道如何去手写view的映射,我们可以通过MyEclipse的hibernate的反向工程来生成hbm文件或者Annotation,打开DB Browser视图,找到我们的view然后右键Hibernate Reverse Engineering,视情况选择生成annotation或者xml文件,一路next之后我们可以发现多了三个文件,2个PO类一个hbm.xml文件,
我们可以看到生成了大写V打头+视图名的一个类,还有一个是在上一个类多加了Id结尾的类,一共视图生成2个PO类,这就是hibernate为我们映射的视图了,由于视图是没有主键的,所以hibernate在无法确定<id>的情况下自然也就无法映射成一个实体,hibernate的解决办法是:用两个PO来映射视图,一个PO封装查询视图返回的所有列,另一个PO将封装好的对象作为联合主键,这样就能完成映射了。下面看一下这两个类的代码和配置文件的代码,
VStuinfo:
package com.wl.entity; /** * VStuinfo entity. @author MyEclipse Persistence Tools */ public class VStuinfo implements java.io.Serializable { // Fields private VStuinfoId id; // Constructors /** default constructor */ public VStuinfo() { } /** full constructor */ public VStuinfo(VStuinfoId id) { this.id = id; } // Property accessors public VStuinfoId getId() { return this.id; } public void setId(VStuinfoId id) { this.id = id; } }
VStinfoId:
package com.wl.entity; import java.util.Date; /** * VStuinfoId entity. @author MyEclipse Persistence Tools */ public class VStuinfoId implements java.io.Serializable { // Fields private Integer stuId; private String sname; private String sex; private Date birthday; private String cname; // Constructors /** default constructor */ public VStuinfoId() { } /** minimal constructor */ public VStuinfoId(Integer stuId) { this.stuId = stuId; } /** full constructor */ public VStuinfoId(Integer stuId, String sname, String sex, Date birthday, String cname) { this.stuId = stuId; this.sname = sname; this.sex = sex; this.birthday = birthday; this.cname = cname; } // Property accessors public Integer getStuId() { return this.stuId; } public void setStuId(Integer stuId) { this.stuId = stuId; } public String getSname() { return this.sname; } public void setSname(String sname) { this.sname = sname; } public String getSex() { return this.sex; } public void setSex(String sex) { this.sex = sex; } public Date getBirthday() { return this.birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getCname() { return this.cname; } public void setCname(String cname) { this.cname = cname; } public boolean equals(Object other) { if ((this == other)) return true; if ((other == null)) return false; if (!(other instanceof VStuinfoId)) return false; VStuinfoId castOther = (VStuinfoId) other; return ((this.getStuId() == castOther.getStuId()) || (this.getStuId() != null && castOther.getStuId() != null && this.getStuId().equals( castOther.getStuId()))) && ((this.getSname() == castOther.getSname()) || (this .getSname() != null && castOther.getSname() != null && this .getSname().equals(castOther.getSname()))) && ((this.getSex() == castOther.getSex()) || (this.getSex() != null && castOther.getSex() != null && this.getSex().equals( castOther.getSex()))) && ((this.getBirthday() == castOther.getBirthday()) || (this .getBirthday() != null && castOther.getBirthday() != null && this .getBirthday().equals(castOther.getBirthday()))) && ((this.getCname() == castOther.getCname()) || (this .getCname() != null && castOther.getCname() != null && this .getCname().equals(castOther.getCname()))); } public int hashCode() { int result = 17; result = 37 * result + (getStuId() == null ? 0 : this.getStuId().hashCode()); result = 37 * result + (getSname() == null ? 0 : this.getSname().hashCode()); result = 37 * result + (getSex() == null ? 0 : this.getSex().hashCode()); result = 37 * result + (getBirthday() == null ? 0 : this.getBirthday().hashCode()); result = 37 * result + (getCname() == null ? 0 : this.getCname().hashCode()); return result; } }
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"> <!-- Mapping file autogenerated by MyEclipse Persistence Tools --> <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,调用一下看看数据,
可以看到调用存储过程之后成功返回了数据,接下面就是如何通过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) { // TODO Auto-generated catch block e.printStackTrace(); }
关于hibernate操作存储过程的内容暂时介绍到这里,以后如果有更深入的学习研究还会陆续更新本篇blog。
总结
本篇blog记录了持久层的另一种优化方式,就是引用视图和存储过程,当数据量很大时,比如达到百万级之后,我们用HQL语句可能就达不到我们对效率方面的要求了,所以这时应使用原生SQL,比如视图、存储过程等等,基本就可以满足我们性能方面的要求。好了,本篇blog到这里就告于段落,关于hibernate优化的总结可能会暂时放一放,准备继续学习Android和前端方面的东西,时间永远不够用,加油,Raito!