Hibernate缓存及查询策略研究

本文探讨了Hibernate的缓存策略,包括一级缓存、二级缓存和查询缓存,以及它们如何提高多表查询性能。一级缓存默认开启,二级缓存需手动配置。查询缓存以SQL或HQL为键,缓存查询结果的ID集合。在实际应用中,正确配置延迟加载、抓取策略与缓存,能有效优化查询效率。但要注意,查询缓存可能降低性能,且需谨慎处理数据一致性问题。
摘要由CSDN通过智能技术生成

公司现在有一个比较旧的SSH项目,使用hibernate作为数据访问层框架。因为表之间的关系比较复杂,所以在类与类之间配置了很多的一对多,多对多关联关系,在查数据的时候会查出很多不需要的数据,导致了查询性能的低下,一次简单的分页查询却要耗时一秒以上。
不过,Hibernate也提供了一些方式来提高多表查询性能:可以通过查询策略来优化生成的sql语句,也可以通过缓存来减少sql的生成。下面就介绍下我对缓存及查询策略的一些测试与研究。

缓存策略

Hibernate在查询数据时,会根据自身的缓存管理策略,在缓存中查找相关数据。如果发现所需的数据,则直接将此数据作为结果加以使用,从而避免数据库调用性能的开销。
Hibernate缓存包括两大类:一级缓存和二级缓存
Hibernate一级缓存又被称为“Session的缓存”。Session缓存是内置的,不能被卸载,是事务范围的缓存。
Hibernate二级缓存又称为“SessionFactory的缓存”,由于SessionFactory对象的生命周期和应用程序的整个过程对应。第二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件。
在这里,我只介绍缓存是如何工作的,因为理解了缓存的运作原理才能最大化地利用缓存。具体使用方法请自行百度。

一级缓存

一级缓存默认开启,只要session没有关闭,它就会一直存在。

  • 当我们通过hibernate中的session提供的一些API例如 save get update等进行操作时,就会以ID为key将对象保存到session中。当下一次去查询相同ID对象时,就不会去从数据库查询,而是直接从缓存中获取。

二级缓存

Hibernate的二级缓存同一级缓存一样,也是针对对象ID来进行缓存,但需要手动配置。

  • 在执行各种条件查询时,如果所获得的结果集为实体对象的集合,那么就会把所有的数据对象分别根据ID放入到二级缓存中。
  • 当Hibernate根据ID访问数据对象的时候,首先会从Session一级缓存中查找,如果查不到并且配置了二级缓存,那么会从二级缓存中查找,如果还查不到,就会查询数据库,把结果按照ID放入到缓存中。
  • 删除、更新、增加数据的时候,同时更新二级缓存。

查询缓存

在前文中也提到了,Hibernate的一级二级缓存都是通过id对实体进行缓存,但它不会缓存查询结果,如果想对查询结果进行缓存,则可以考虑使用查询缓存。

  • 查询缓存是以sql或hql为key,保存查询到的id集合。
  • 查询缓存不仅要求所使用的HQL、SQL语句相同,甚至要求所传入的参数也相同,Hibernate才能直接从缓存中取得数据。因此,只有经常使用相同的查询语句、并且使用相同查询参数才能通过查询缓存获得好处,
  • 在开启查询缓存的时候,也应该开启二级缓存。如果不使用二级缓存,可能出现N的问题。因为查询缓存只缓存对象的ID或ID集合,二级缓存才是根据ID缓存对象。如果不开启二级缓存,通过查询缓存拿到ID后,还要去发送SQL语句获得对象的信息。
    总而言之,查询缓存并不一定能提高应用程序的性能,甚至反而会降低应用性能,因此实际项目中要谨慎的使用查询缓存(后面也会演示这一点)。

还要特别注意一点,对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在自己程序中使用其他方式进行数据的修改,例如,JDBC)。因为那样做Hibernate将不会知道数据已经被修改,也就无法保证缓存中的数据与数据库中数据的一致性。

查询策略

查询策略,指的是Hibernate在查询关联对象时抓取的方式,通过@FetchMode注解来配置,可以设置select,join和subselect。

  1. select方式时先查询返回要查询的主体对象(列表),再根据关联外键id,每一个对象发一个select查询,获取关联的对象,形成n+1次查询;
  2. join方式,主体对象和关联对象用一句外键关联的sql同时查询出来,不会形成多次查询。
  3. subselect是通过两句sql分别查询主体和关联对象,即直接查询主体和通过子查询得到的主体id来查询关联对象
    注意,subselect只能设置于多个关联对象。

用一对多来举例:

  1. fetchMode = "join"是在查询的时候使用外连接进行查询,不会产生1+n的现象
  2. fetchMode = "select"是在查询的时候先查询出一端的实体,然后在根据一端的查询出多端的实体,会产生1+n条sql语句
  3. fetchMode = "subselect"是在查询的时候通过两条sql语句查询两端的实体,产生2条sql语句。注意:抓取单个对象不可设置subselect

使用方法(需与@OneToOne/@OneToMany/@ManyToMany一起使用):

@OneToMany
@JoinColumn(name = "entrust_order_id")
@Fetch(FetchMode = "SELECT/JOIN/SUBSELECT"private List<EntrOrderDetail> entrOrderDetail;;

延时加载

另外,Hibernate提供了延迟加载,也叫懒加载, 即只有真正使用到关联对象的数据时才会获取。
使用方法:

@OneToMany(fetch = FetchType.LAZY/EAGER)
@JoinColumn(name = "entrust_order_id")
private List<EntrOrderDetail> entrOrderDetail;

两个配置项:
lazy 延迟加载
eager 立即加载

特别注意:hibernate的延迟加载在JSP页面中无效,因为延时加载只在hibernate的session中生效,而session在service层提交事务后就关闭了。可以开启openSessioninview延长session的生命周期,达到延时加载的功能。

实际应用

理解了hibernate的查询策略和缓存策略,接下来测试不同配置中,二级缓存/抓取方式/延时加载的效果。

测试思路:控制变量法,通过观察hibernate sql的生成来判断效果。
测试版本:Hibernate 4.3.11
注意,测试篇幅较长,时间宝贵的可以直接看结论

实际使用分为两种情况:

ManyToOne,OneToOne,即获取单个对象

测试实体类:

//关联关系:多个学生对一个老师
//学生
public class Student {
   
    @Id
    @GeneratedValue
    private Long id;
    @Column
    private String name;
    @ManyToOne
    @JoinColumn(name = "teacher_id")
    private Teacher teacher;     //关联一个老师
}

//老师
//若开启二级缓存:@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Teacher {
   
    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "teacher_name")
    private String teacherName;
}

测试代码:

Student student = studentDao.get(1L);
System.out.println("-------测试延时加载------");
System.out.println(student.getTeacher());

测试结果:

  1. (Hibernate默认设置) fetchMode = “join”,fetchType= eag
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值