Hibernate 优化技术之调用视图(View)和存储过程(Procedure)



前言



上一篇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!

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。