博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Hibernate的缓存
阅读量:4103 次
发布时间:2019-05-25

本文共 6127 字,大约阅读时间需要 20 分钟。

思路:

1.首先说清楚什么是缓存,2.再说Hibernate 的Session就是一级缓存,即有了一级缓存,为什么还要二级缓存,3.最后再说如何配置Hibernate的二级缓存。

1.什么是缓存?

缓存就是把以前从数据库中查询出来和使用过的对象保存在内存中(一个数据结构中),这个数据结构通常是或者类似HashMap,以后需要使用这个对象时,先查询缓存中是否有这个对象,如果有,则使用缓存中的对象,如果没有,则需要去查询数据库,并将查询出来的结果保存在缓存中,以便下次使用。

2.Hibernate的Session就是一级缓存,我们通常将之称为Hibernate的一级缓存,当想使用Session从数据库中查询出一个对象时,Session会先从自己内部查看是否存在这个对象,存在则直接返回,不存在才会去访问数据库,并将查询的结构保存在自己内部。

由于Session代表的是一次会话,一个Session与一个数据库连接相关联,所以Session最好不要长时间打开,通常仅用于一个事务当中,在事务结束后就会关闭。

并且Session是线程不安全的,被多个线程共享时容易出现问题。通常只有那种全局意义上的缓存才是真正的缓存应用,才有较大的缓存价值。

因此Hibernate的一级缓存(Session)的作用并不明显,应用价值不大。

Hibernate的二级缓存就是要为Hibernate配置一种全局缓存,让多个线程和多个事务都可以共享这个缓存,我们希望的是,一个人使用过,其他人也可以使用,Session没有这个效果。

3.二级缓存是独立于Hibernate的软件部件,属于第三方产品,多个厂商和组织都提供有缓存产品。

例如:EHCache 和 OSCache等待。

使用Hibernate二级缓存步骤:

1.首先要在Hibernate.cfg.xml配置文件中配置使用哪个厂家的缓存产品。

2.接着需要配置该缓存产品自己的配置文件,

3.最后要配置Hibernate中的哪些实体对象要纳入到二级缓存的管理中。

扩展:

一个SessionFactory可以关联一个二级缓存,即一个二级缓存只能负责缓存一个数据库中的数据,当使用Hibernate二级缓存后,注意不要有其他应用或SessionFactory来更改当前数据库中的数据,不然缓存的数据就会与数据库中的实际数据不一致。

下面是一个前辈的总结:(转)

在hibernate中最常用的有三类缓存,分别为一级缓存、二级缓存和查询缓存,

下面我们对这三个缓存在项目中的使用以及优缺点分析一下。

      缓存它的作用在于提高性能系统性能,介于应用系统与数据库之间而存在于内存或磁盘上的数据。

      我们编程的模式一般是这样的page-->filter-->action-->server-->dao-->db,可以在这一个请求过程中的任何一点加入缓存,上一篇介绍的是在server层加缓存:。

       为页面增加缓存:

      首先,来看一下一级缓存它默认开启且很常用。

一级缓存

    同是一种缓存常常可以有好几个名字,这是从不同的角度考虑的结果,从缓存的生命周期角度来看一级缓存又可以叫做:

sessin缓存、线程级缓存、事务级缓存。

我们编程中线程、事务、session这三个概念是绑定到一起的放到了threadlocal中,同时开启同时关闭即同生共死也有人叫做request-per-session-transaction编程;

       当session关闭后缓存中的对象会丢失,也就是说两个不同的session中的缓存数据都是不一样的,缓存数据不能够跨session访问。

       

     缓存数据的数据类型

    在一级缓存中缓存的是实体对象,在使用查询方法get() 、load() 、iterate()三个方法查询时都会先查询session缓存,如果有对象则从缓存里面取出来,如果缓存中没有再去数据库里面查询。
    load()测试:测试注意一定要在同一个事务里面,当我在Spring管理的session测试时调用两次load()总是查询两次发出两条SQL语句,还以为session级缓存没有起作用,原来是因为hibernate集成spring之后事务、session都由spring管理,每次调用前后事务一级session都自动打开和关闭,自己控制不了中间过程,于是将spring去掉拿到hibernate原session,再手动开发关闭事务这样做可以保证在同一个session、同一个事务里面操作方法,确实是发了一条SQL语句,看下面代码:
    load()、get()方法:
    
[java] 
  1. @Test  
  2. public void testLoad()  
  3. {  
  4.     Session session=sf.openSession();  
  5.     session.beginTransaction();  
  6.       
  7.     Category category1=(Category)session.load(Category.class,1);  
  8.     Category category2=(Category)session.load(Category.class,1);  
  9.     System.out.println(category1);  
  10.     System.out.println(category2);  
  11.       
  12.     session.getTransaction().commit();  
  13.     session.close();  
  14. }  


结果

[java] 
  1. Hibernate: select category0_.id as id0_0_, category0_.name as name0_0_ from Category category0_ where category0_.id=?  
  2. hibernate.Category@10efd7c  
  3. hibernate.Category@10efd7c  


      结果不仅发送了一条语句而且两个对象打印出来也是一样的。

load()、get()第一次查询时会发出sql语句,从数据库表里面查询;第二次查询时会先去缓存里面查找,如果没有发生更新修改操作,那么将从缓存中读取数据,否则查询数据库。

save方法

[java] 
  1. @Test  
  2. public void testGet()  
  3. {  
  4.     Session session=sf.openSession();  
  5.     session.beginTransaction();  
  6.       
  7.     Category category1=new Category();  
  8.     category1.setName("新闻");  
  9.       
  10.     session.save(category1);  
  11.     Category category2=(Category)session.load(Category.class,category1.getId());  
  12.       
  13.     System.out.println(category2.getName());  
  14.     session.getTransaction().commit();  
  15.     session.close();  
  16. }  

      save也支持缓存,当执行save方法时首先往session缓存里面添加一条数据,等事务提交或者缓存刷新时才往数据库里面更新,从上面执行过程可以看出只发出了一条插入语句没有发查询语句,因为第二次是从缓存中查询出来的。

      PS:save之后执行get或者load需要知道对象的ID,此时save方法执行后虽然数据库里没有数据,但是对象的ID已经生成可以通过这个ID查询对象。

批量插入数据

       在批量插入数据的时候采取每次插入一部分数据,如下,每次插入20条数据不需要一条一条插入。

[java] 
  1. public void testInserBatch() {  
  2.     Session session = sf.openSession();  
  3.     session.beginTransaction();  
  4.       
  5.     for(int i=0; i<1000; i++) {  
  6.         Category c = new Category();  
  7.         c.setName("test" + i);  
  8.         session.save(c);  
  9.         if (i%20==0) {  
  10.             session.flush();  
  11.         }  
  12.     }  
  13.           
  14.     session.getTransaction().commit();  
  15.     session.close();  
  16. }  

每次20条数据清理一下缓存,每次清理缓存调用session.flush()方法会发出20条insert语句,但是数据库里面还没有数据等所有数据都发出insert语句统一提交事务,事务同session是一个等级的因此需统一控制事务。

hibernate N+1问题

       Hibernate 中常会用到 set 等集合表示 1 对多的关系,在我们做的这个铁科院项目中,在获取实体的时候就能根据关系将关联的对象或者对象集合取出,还可以设定 cacade 进行关联更新和删除。这不得不说 hibernate 的 orm 做得很好,很贴近 oo 的使用习惯了。

      但是对数据库访问还是必须考虑性能问题的,在设定了 1 对多这种关系之后, 查询就会出现传说中的 n+1 问题。
一对多:在一方,查找得到了 n 个对象,那么又需要将 n 个对象关联的集合取出,于是本来的一条 sql 查询变成了 n+1 条;
多对一:在多方,查询得到了 m 个对象,那么也会将 m 个对象对应的 1 方的对象取出, 也变成了 m+1 ;
      解决问题的方法:
1、 使用 fetch 抓取, Hibernate 抓取策略分为单端代理和集合代理的抓取策略。
Hibernate 抓取策略 ( 单端代理的抓取策略) :
保持默认也就是如下 :
<many-to-one name="clazz"cascade="save-update" fetch="select" />
fetch="select" 就是另外发送一条 select 语句抓取当前对象关联实体或者集合设置 fetch="join"
<many-to-one name="clazz"cascade="save-update" fetch="join"/>
Hibernate 会通过 select 语句使用外连接来加载器关联实体活集合此时 lazy 会失效
Hibernate 抓取策略 ( 集合代理的抓取策略 ) :
保持默认( fetch="select" )也就是如下 :
<set name="students"inverse="true">
<key column="clazz"/>
<one-to-many class="com.june.hibernate.Student"/>
</set>
1)fetch="select" 会另外发出一条语句查询集合
2) 设置fetch="join" 采用外连接集合的 lazy 失效
3) 这只fetch="subselect" 另外发出一条 select 语句抓取前面查询到的所有的实体对象的关联集合 fetch 只对 HQL 查询产生影响其他的则不会

OpenSessionInview问题

      这个问题出现是由于load()懒加载导致的,第一次查询数据时使用了懒加载至查询出来数据的ID,当使用数据的时候还需要去数据库里面查询但是此时数据库的session已经关闭,解决此问题两种思路一种是不使用懒加载;其二是在web层开发关闭session,延长session的生命周期。

二级缓存

    二级缓存也称为进程级缓存或sessionFactory缓存,也可以叫做集群范围内的缓存,需要第三方来实现,hibernate默认的二级缓存插件为ehcache这个缓存,由于二级缓存是进程级的可能出现多线程并发问题,需要设置缓存的并发策略。

      hibernate二级缓存需要第三方插件支持,hibernate默认支持为ehcache关于配置请参考:

      开启二级缓存后对方法的影响

      get()/load()

      对于这两个方法没啥影响,第一次从数据库里面查询,第二次先判断缓存里面有没有数据如果没有再去数据库里面查询。

查询缓存

      查询缓存是针对普通属性结果集的缓存,不缓存实体对象,当和查询缓存关联的表发生修改的时候,查询缓存生命周期结束,里面的数据也随即被清空了。

      查询缓存的配置和使用:
List方法读写查询缓存,Iterator不使用查询缓存(查询缓存只对query.list()有效)
      查询缓存的配置,默认不开启hibernate3配置:

[html] 
  1. <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>  
  2. <property name="hibernate.cache.use_second_level_cache">false</property>   
  3.      <property name="hibernate.cache.use_query_cache">true</property>    

代码中,加上一句话 

[java] 
  1. query.setCacheable(true)  

  

一级、二级、查询之间的关系

一级缓存总是开启状态,我们需要关注的是查询缓存和二级缓存,查询缓存可以只开启一个或者两个都打开。

开启二级缓存时,如果两个session先后执行load或者get方法,只执行一条语句第二次会从缓存中查找,先从一级缓存中查询,如果没有再去二级缓存中查找。


一级缓存同二级缓存交互

                禁止一级缓存与二级缓存交互,如下设置

session.setCacheMode(CacheMode.IGNORE);

打开一个session执行查询,它会先将查询结果保存到一级缓存,待session关闭后,一级缓存中数据清空,由于禁止了一级缓存同二级缓存数据交互,因此,一级缓存关闭后不会将结构保存到二级缓存,打开第二个session后,后再发送一条查询语句,因此二级缓存中没有数据。



查询缓存与二级缓存

开启查询,关闭二级

         
如果两次执行query.list(),第一次发送查询语句会将结果对象的id保存到查询缓存中,第二次会先从查询缓存中取出ID,根据id先去一级缓存查找,再二级缓存,如果没有找到会去数据库中查找,一级缓存同session没有关系,只和表有关系。

                开启查询,开启二级缓存

两次执行query.list(),第一次发送查询语句将结果

       总结:

        缓存在一个项目中对于提高系统性能很重要,除了ehcache之外还有memcache、redis等缓存产品目前都很常用,redis具有丰富的数据类型以及单线程高效能访问效率,memcache虽然是多线程但效率还是没有redis高。

        这些缓存产品都可以实现分布式缓存,ehcache+rmi可以分布式缓存同步;memcache+redis都支持分布式,redis还提供了高可用性的解决方案:主从复制几个服务器直接爱你可以切换。

转载地址:http://zmusi.baihongyu.com/

你可能感兴趣的文章
Android区分系统应用和安装应用
查看>>
高通安卓调试LCD几方面总结(一)
查看>>
高通安卓调试LCD几方面总结(二)
查看>>
修改 EditText 中的光标位置
查看>>
Android中shape的使用
查看>>
i2c驱动调试经验
查看>>
我对linux理解之i2c
查看>>
android 自定义ImageView实现图片手势滑动,多点触摸放大缩小效果
查看>>
Datashee常用名词
查看>>
手把手教你写Linux I2C设备驱动
查看>>
Linux i2c设备的驱动程序
查看>>
Linux 驱动i2c -- Gsenser(一)
查看>>
Linux 驱动i2c -- Gsenser(二)
查看>>
Linux 驱动i2c -- Gsenser(三)
查看>>
LCM调试总结
查看>>
触摸屏驱动分析
查看>>
触摸屏(TP)乱跳原因总结
查看>>
tp 驱动分析记
查看>>
linux驱动工程师成长之路(五)LCD背光控制RT9379B
查看>>
android 电容屏(二):驱动调试之基本概念篇
查看>>