Hibernate入门第十四讲——Hibernate的优化方案与抓取策略

Hibernate的优化方案

一般而言,我们可以从以下两方面来优化Hibernate。

  • HQL语句的优化;
  • 一级缓存的优化。

下面我就稍微从这两方面说道说道,如有不足之处,还请指出。

HQL语句的优化

HQL语句可以从以下几个方面进行优化:

  • 使用参数绑定
    • 使用参数绑定的原因是让数据库一次解析SQL,对后续的重复请求可以使用生成好的执行计划,这样做节省CPU时间和内存;
    • 避免SQL注入。
  • 尽量少使用NOT:如果where子句中包含not关键字,那么执行时该字段的索引失效;
  • 尽量使用where来替换having:having在检索出所有记录后才对结果集进行过滤,这个处理需要一定的开销,而where子句限制记录的数目,能减少这方面的开销;
  • 减少对表的查询:在含有子查询的HQL中,尽量减少对表的查询,降低开销;
  • 使用表的别名:当在HQL语句中连接多个表时,使用别名,提高程序阅读性,并把别名前缀与每个列连接上,这样一来,可以减少解析时间并减少列歧义引起的语法错误;
  • 实体的更新与删除:在Hibernate3以后支持HQL的update与delete操作。

一级缓存的优化

一级缓存也叫做Session缓存,在一个Hibernate Session中有效,这级缓存的可干预性不强,大多于Hibernate自动管理,但它提供清除缓存的方法,这对大批量增加(更新)操作而言是有效果的,例如,同时增加十万条记录,按常规进行,很可能会出现异常,这时可能需要手动清除一级缓存,session.evict以及session.clear。

Hibernate的抓取策略(对于查询关联对象的一种优化)

延迟加载

延迟加载是Hibernate为提高程序执行的效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。说人话就是:延迟加载也叫懒加载(lazy),当执行到该行代码的时候,不会发送语句去进行查询,只有在真正使用这个对象的其他属性的时候才会真正发送SQL语句进行查询。还记得load方法采用的策略是延迟加载,get方法采用的策略是立即加载吗?延迟加载可分为两类,它们分别是:

  • 类级别的延迟加载;
  • 关联级别的延迟加载。

下面我就简单说说它俩。

类级别的延迟加载

类级别的延迟加载指的是通过load方法查询某个对象的时候,是否采用延迟(默认采用的就是延迟加载)。例如,

Customer c = session.load(Customer.class, 1l);

类级别的延迟加载可以通过<class>元素的lazy属性来设置,默认值是true。即默认是延迟加载。如果为了显示的声明出来,那么我们可以在映射配置文件中设置如下,注意:<class>元素上的lazy属性只对普通属性有效,对关联对象无效。
在这里插入图片描述
如果想让类级别的延迟加载失效,那么该咋办呢?总共有三种方式可让类级别的延迟加载失效,下面我会分别介绍它们。

  • 第一种方式:将<class>元素上的lazy属性设置为false。
    为了便于演示这种方式,我们在com.meimeixia.hibernate.demo02包下编写一个LoadTest单元测试类,并在该类中编写如下demo01()测试方法。
    在这里插入图片描述
    以上程序证明了如果将<class>元素上的lazy属性设置为false,代表类级别的延迟加载失效,这时load与get方法就完全一样了,都是立即加载。
    虽然我们是知道了load方法采用的策略是延迟加载,get方法采用的策略是立即加载,但是什么时候用get方法,什么时候用load方法呢?如果你查询的数据非常大,例如说它里面有一些大的字段,这个时候建议你采用load方法,不要一上来就立即加载,把我们的内存占满,这样可以让我们的性能得到一部分的提升;如果你查询的数据非常少,直接get就无所谓了,因为它不会占用我们很多的内存
  • 第二种方式:将持久化类使用final修饰,例如:
    在这里插入图片描述
  • 第三种方式:调用Hibernate.initialize()方法,例如:
    在这里插入图片描述
    Hibernate这个框架是在dao层进行操作的,如果说我现在采用了一个load的方案去获取了一个对象,我们最终会把Session关闭再返回,那么我们就要把这个对象返回到service层,最后再返回到web层,这个时候load出来的代理对象其实还没有对数据进行初始化,也即它里面还没有真正有数据,返回的时候就出问题了,那如何对一个延迟的代理对象进行初始化呢?这个时候,你就要调用Hibernate.initialize()方法对load出来的代理对象初始化一把了。

关联级别的延迟加载

查询到某个对象,获得其关联的对象或属性,这种就称为关联级别的延迟加载。说得更大白话一点:关联级别的延迟加载指的是在查询到某个对象的时候,查询其关联的对象,是否采用延迟加载。例如,

Customer customer = session.get(Customer.class, 1l);
customer.getLinkMans();//通过客户获得联系人的时候,联系人对象是否采用了延迟加载,这就称之为是关联级别的延迟加载

因为抓取策略往往会和关联级别的延迟加载一起使用,优化语句,所以下面就要研究抓取策略了。

抓取策略

抓取策略的概述

抓取策略指的是查找到某个对象后,通过这个对象去查询关联对象的信息时的一种策略。我们知道Hibernate中对象之间的关联关系有三种:
在这里插入图片描述
这里我们主要讲的是在<set><many-to-one>这两个标签上设置fetch、lazy属性,为何要设置这俩哥们呢?因为通过一个对象抓取到其关联对象时,需要发送SQL语句,SQL语句到底何时发送,发送成什么样的格式,这就需要通过抓取策略来进行配置了。

  • fetch属性主要描述的是SQL语句的格式,例如是多条,还是子查询,还是多表联查;
  • lazy属性用于控制SQL语句何时发送。

我在这里举个例子说明下,例如现在要查询一个客户,并且还要关联查询他名下的联系人。客户代表一的一方,在客户实体类中有Set集合来描述其名下的联系人,那么客户映射配置文件就应该是这样子的配置:
在这里插入图片描述
此时,我们就可以在set标签上设置fetch和lazy这两个属性了。
再比如,查询一个联系人时,要查询关联的客户信息。联系人代表多的一方,在联系人实体类中有Customer对象来描述其关联的客户,在联系人映射配置文件中我们使用的是<many-to-one>标签,如下图所示。
在这里插入图片描述
此时,我们亦可在该标签上设置fetch和lazy这两个属性。当然了,你也可在<one-to-one>标签上设置这两个属性。
知道抓取策略要干的事之后,接下来,我将会花大篇幅来讨论<set><many-to-one>这两个标签上的fetch属性和lazy属性到底该如何设置,以优化发送的SQL语句。

<set>标签上的fetch与lazy

<set>标签上的fetch和lazy这两属性主要是用于设置关联的集合信息的抓取策略。

  • fetch属性:抓取策略,用来控制在查询关联对象时的SQL语句的格式。其可取值有:
    • select:默认值,发送普通的select语句查询关联对象
    • join:发送一条迫切左外连接来查询关联对象;
    • subselect:发送一条子查询去查询其关联对象。
  • lazy:延迟加载,控制查询关联对象的时候是否延迟。其可取值有:
    • true:默认值,查询关联对象的时候,采用延迟加载
    • false:查询关联对象的时候,不采用延迟加载;
    • extra:极其懒惰,也就是说你用什么样的SQL语句,就发送什么样的SQL语句。

这样看来,fetch与lazy的组合就有九种了,其实不然,fetch与lazy的组合实际上只有七种,且听我娓娓道来。

第一种组合(默认情况)

在com.meimeixia.hibernate.demo02包下编写一个SetFetchTest单元测试类,并在该类中编写如下测试方法:

package com.meimeixia.hibernate.demo02;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import com.meimeixia.hibernate.domain.Customer;
import com.meimeixia.hibernate.domain.LinkMan;
import com.meimeixia.hibernate.utils.HibernateUtils;

/**
 * 在<set>上的fetch和lazy
 * @author liayun
 *
 */
public class SetFetchTest {
   
		
	/*
	 * 默认情况,即fetch="select"、lazy="true"
	 */
	@Test
	public void demo01() {
   
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		//查询1号客户
		Customer customer = session.get(Customer.class, 1l);//只会发送一条查询客户的sql语句(根据客户的ID),作断点调试
		System.out.println(customer.getCust_name());
		//查询1号客户的每个联系人的信息
		for (LinkMan linkMan : customer.getLinkMans()) {
   //又会根据客户的ID去查询联系人的sql语句
			System.out.println(linkMan.getLkm_name());
		}
		
		tx.commit();
	}
	
}

Customer customer = sessi

  • 13
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李阿昀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值