zoukankan      html  css  js  c++  java
  • Hibernate缓存原理与策略

    Hibernate缓存原理:

      对于Hibernate这类ORM而言,缓存显的尤为重要,它是持久层性能提升的关键.简单来讲Hibernate就是对JDBC进行封装,以实现内部状态的管理,OR关系的映射等,但随之带来的就是数据访问效率的降低,和性能的下降,而缓存就是弥补这一缺点的重要方法.

         缓存就是数据库数据在内存中的临时容器,包括数据库数据在内存中的临时拷贝,它位于数据库与数据库访问层中间.ORM在查询数据时首先会根据自身的缓存管理策略,在缓存中查找相关数据,如发现所需的数据,则直接将此数据作为结果加以利用,从而避免了数据库调用性能的开销.而相对内存操作而言,数据库调用是一个代价高昂的过程.
         一般来讲ORM中的缓存分为以下几类:
             1:事务级缓存:即在当前事务范围内的数据缓存.就Hibernate来讲,事务级缓存是基于Session的生命周期实现的,每个Session内部会存在一个数据缓存,它随着 Session的创建而存在,随着Session的销毁而灭亡,因此也称为Session Level Cache.
             2:应用级缓存:即在某个应用中或应用中某个独立数据库访问子集中的共享缓存,此缓存可由多个事务共享(数据库事务或应用事务),事务之间的缓存共享策略与应用的事务隔离机制密切相关.在Hibernate中,应用级缓存由SessionFactory实现,所有由一个SessionFactory创建的 Session实例共享此缓存,因此也称为SessionFactory Level Cache.
             3:分布式缓存:即在多个应用实例,多个JVM间共享的缓存策略.分布式缓存由多个应用级缓存实例组成,通过某种远程机制(RMI,JMS)实现各个缓存实例间的数据同步,任何一个实例的数据修改,将导致整个集群间的数据状态同步.

    Hibernate的一,二级缓存策略:

      Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓存可以进行配置和更改,并且可以动态加载和卸载,属于多事务级别,要防止事务并发性。

    缓存是以map的形式进行存储的(key-id,value-object)

    一级缓存(Session):

       事务范围,每个事务(Session)都有单独的第一级缓存.

       一级缓存的管理:当应用程序调用Session的save()、update()、saveOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate()--(用的是n+1次查询,先查id)或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj):从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象,flush():使缓存与数据库同步。

    当查询相应的字段如(name),而不是对象时,不支持缓存。

    二级缓存(SessionFactory):

      Hibernate的二级缓存策略的一般过程如下:

       1:条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL句查询数据库,一次获得所有的数据对象(这个问题要考虑,如果你查询十万条数据时,内存不是被占用)。

     2:把获得的所有数据对象根据ID放入到第二级缓存中。

     3: 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。

       4:删除、更新、增加数据的时候,同时更新缓存。

      Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache

    概念讲解

    在hibernate中提供了二种缓存机制:一级缓存、二级缓存,因为二级缓存策略是针对于ID查询的缓存策略,对于条件查询则毫无作用,为此,Hibernate提供了针对条件查询的Query Cache(查询缓存)。

    一、一级缓存:

    一级缓存是hibernate自带的,不受用户干预,其生命周期和session的生命周期一致,当前session一旦关闭,一级缓存就会消失,因此,一级缓存也叫session缓存或者事务级缓存,一级缓存只存储实体对象,不会缓存一般的对象属性,即:当获得对象后,就将该对象缓存起来,如果在同一个session中再去获取这个对象时,它会先判断缓存中有没有这个对象的ID,如果有,就直接从缓存中取出,否则,则去访问数据库,取了以后同时会将这个对象缓存起来。

    二、二级缓存:

    二级缓存也称为进程缓存或者sessionFactory级的缓存,它可以被所有的session共享,二级缓存的生命周期和sessionFactory的生命周期一致,二级缓存也是只存储实体对象

    二级缓存的一般过程如下:

    ①:条件查询的时候,获取查询到的实体对象

    ②:把获得到的所有数据对象根据ID放到二级缓存中

    ③:当Hibernate根据ID访问数据对象时,首先从sesison的一级缓存中查,查不到的时候如果配置了二级缓存,会从二级缓存中查找,如果还查不到,再查询数据库,把结果按照ID放入到缓存中

    ④:进行delete、update、add操作时会同时更新缓存

    三、查询缓存:

    查询缓存是对普通属性结果集的缓存,对实体对象的结果集只缓存id,对于经常使用的查询语句,如果启用了查询缓存,当第一次执行查询语句时,Hibernate会把查询结果存放在二级缓存中,以后再次执行该查询语句时,只需从缓存中获得查询结果,从而提高查询性能,查询缓存中以键值对的方式存储的,key键为查询的条件语句(具体的key规则应该是:类名+方法名+参数列表),value为查询之后等到的结果集的ID列表

    查询缓存的一般过程如下:

    ①:Query Cache保存了之前查询执行过的SelectSQL,以及结果集等信息组成一个Query Key

    ②:当再次遇到查询请求的时候,就会根据QueryKey从QueryCache中找,找到就返回,但当数据表发生数据变动的话,hbiernate就会自动清除QueryCache中对应的Query Key

    我们从查询缓存的策略中可以看出,Query Cache只有在特定的条件下才会发挥作用,而且要求相当严格:

    ①:完全相同的SelectSQL重复执行

    ②:重复执行期间,QueryKey对应的数据表不能有数据变动

    启用缓存的配置

    EJB中配置查询缓存和二级缓存

    1、在persistence.xml中启用缓存

    <persistence-unitname="gxpt-qx-entity" transaction-type="JTA" >
    <!--对jpa进行性能测试 -->
    <provider>net.bull.javamelody.JpaPersistence</provider>
     
                     <jta-data-source>java:/MySqlDS</jta-data-source>
                     <!--<jta-data-source>java:/MyOracleDS</jta-data-source>   -->
                     
                     <properties> 
                              <!-- <propertyname="hibernate.dialect"value="org.hibernate.dialect.Oracle10gDialect"/> -->
                              <propertyname="hibernate.dialect"value="org.hibernate.dialect.MySQLDialect"/>
    <propertyname="hibernate.hbm2ddl.auto" value="update" />
    <propertyname="hibernate.show_sql" value="true" />
     
    <!--指定二级缓存产品的提供商 -->
    <propertyname="hibernate.cache.provider_class"
    value="net.sf.ehcache.hibernate.SingletonEhCacheProvider"/>
    <!--                        <propertyname="hibernate.cache.region.factory_class"
    value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>-->
    <!--开启二级缓存 -->
    <propertyname="hibernate.cache.use_second_level_cache"value="true"/>
    <!--开启查询缓存 -->
    <propertyname="hibernate.cache.use_query_cache"value="true"/> 
    <!--指定缓存配置文件位置   -->
    <propertyname="hibernate.cache.provider_configuration_file_resource_path"
    value="/ehcache.xml"/>
     
                     </properties>
    </persistence-unit>

    2、通过注解指定User类使用二级缓存

    @Entity
    @Table(name="tq_user")
    @Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    publicclass User

    3、开启查询缓存,除了在persistence.xml中有以上配置外,还需要在底层代码手动开启查询缓存

    query.setHint("org.hibernate.cacheable", true);

    或者query.setCacheable(true);

    性能测试

    通过sql语句 

    1、二级缓存关闭时,查询缓存开启和关闭情况对比*****普通属性查询

    public String myCache() {
            List<String> strings = this.userServiceImpl
                    .search("selectu.username from User u where id<4 order by id asc");
            for (String str : strings){
               System.out.println("username:" + str);
            }
     
            System.out.println("===================================");
            List<String> strings2 =this.userServiceImpl
                    .search("select u.usernamefrom User u where id<4 order by id asc");
            for (String str : strings2){
               System.out.println("username:" + str);
            }
            return "/mycache";
        } 

    当前二级缓存为关闭状态,看看查询缓存关闭时的查询结果:

      public List<String> search(Stringhql) {
            List<String> rtnStrs = newArrayList<String>();
            try {
               Session session = this.sessionFactory.openSession();
                session.beginTransaction();
     
               Query query = session.createQuery(hql);
                //query.setCacheable(true);//手动开启查询缓存
                rtnStrs =(List<String>) query.list();
     
               session.getTransaction().commit();
            } catch (Exception e){
                System.out.println("DAO层根据HQL语句查询失败");
            }
            return rtnStrs;
        }

    上面代码中屏蔽了query.setCacheable(true)。

    关闭二级缓存、关闭查询缓存 运行如下:

    开启查询缓存、关闭二级缓存运行如下

    结论:对于查询普通属性,无论二级缓存是否开启,只要开启了查询缓存,当两次执行的sql语句相同时,第二次不会发出sql语句,直接从内存中获取。

    2、查询缓存开启时,二级缓存打开和关闭情况对比*******查询实体对象

    /**
         * 查询缓存开启,二级缓存关闭*******查询实体对象
         *
         *运行结果:如果关闭查询缓存和二级缓存,在两次查询时都发出sql语句,此时为两条查询语句
         *
         *运行结果:如果开启查询缓存,关闭二级缓存,第二次会发出根据ID查询实体的n条查询语句
         *
         *运行结论:第一次执行list时,会把查询对象的ID缓存到查询缓存中,第二次执行list时(两次的查询SQL语句相同),会遍历查询缓存中的ID到
         * (一级、二级)缓存里找实体对象, 此时没有,则发出查询语句到数据库中查询
         *
        */        
    publicString mycache3() {
    List<User>users1 = this.userServiceImpl.search();
               for(User u : users1) {
                  System.out.println("users1:username:"+ u.getUsername());
              }
       System.out.println("===============");
     
            List<User> users2 =this.userServiceImpl.search();
            for (User u : users2) {
               System.out.println("users2:usersname:" + u.getUsername());
            }
    return"/mycache";
        }

    开启查询缓存、关闭二级缓存 运行如下:(两次都发出sql,而且第二次发出n条语句)

    开启查询缓存、关闭二级缓存 运行如下(只发出一条语句)

    总结:

    (1)、当只是用hibernate查询缓存,而关闭二级缓存的时候:

    ①如果查询的是部分属性结果集,那么当第二次查询的时候就不会发出SQL语句,直接从Hibernate查询缓存中取数据

    ②如果查询的是实体结果集(eg.from User)这个HQL,那么查询出来的实体,首先hibernate查询缓存存放实体的ID,第二次查询的时候,就到hibernate查询缓存中取出ID一条一条的到数据库查询,这样将发出N条SQL语句,造成SQL泛滥。所以,在使用查询缓存的时候,最好配合开启二级缓存。

    (2)、当开启Hibernate查询缓存和二级缓存的时候:

    ①如果查询的是部分属性结果集,这个和上面只用hbiernate查询缓存而关闭二级缓存的时候一致,因为不涉及实体,不会用到二级缓存。

    ②如果查询的是实体结果集,那么查询出来的实体首先在查询缓存中存放实体的ID,并将实体对象保存到二级缓存中,第二次查询的时候,就到hibernate查询缓存中取ID,根据ID去二级缓存中匹配数据,如果有数据就不会发出sql语句,如果都有,第二次查询一条SQL语句都不会发出,直接从二级缓存中取数据。

    通过Javamelody

    JavaMelody能够在运行环境中监测Java或Java EE应用程序服务器。并以图表的形式显示:Java内存和Java CPU使用情况,用户Session数量,JDBC连接数,和http请求、sql请求、jsp页面与业务接口方法(EJB3、Spring、Guice)的执行数量,平均执行时间,错误百分比等。

    坤哥博客有介绍Java项目性能监控和调优工具-Javamelody

    监控项目缓存个数

    这是本次开发的权限项目中的缓存,共有12个,其中红色部分分别为二级缓存和查询缓存

    对spring的监控

    加缓存情况

    不加缓存情况

    根据统计结果,发现缓存的确可以提高性能。

    但是有时候使用了缓存反而性能会降低,比如update方法,因为数据发生变更后,hibernate需要保持缓存和数据库两份的数据同步,所以加上缓存后,update性能降低,add、delete操作也是相同的道理。

    所以缓存适用于在项目中存在大量查询的情况,否则是没必要适用的。

    Q:什么样的数据适合存放到第二级缓存中?

        1.很少被修改的数据

        2.不是很重要的数据,允许出现偶尔并发的数据

        3.不会被并发访问的数据

        4.参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。

     不适合存放到第二级缓存的数据?

        1 经常被修改的数据

        2 财务数据,绝对不允许出现并发

        3 与其他应用共享的数据。

     常用的缓存插件 Hibernater 的二级缓存是一个插件,下面是几种常用的缓存插件:

      EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。

      OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持。

      SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。

      JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。

    配置二级缓存的主要步骤:

      1 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。

      2 选择合适的缓存插件,然后编辑该插件的配置文件。

  • 相关阅读:
    ztree实现树的异步加载
    form submit提交遇到的问题
    js页面刷新常用的几种方式
    IE6下的按钮效果
    实现两个div并排的三种方式
    继续推荐Android12个自测源码
    优秀程序员实现向卓越程序员跨越
    面试那点事【面试题+面试技巧+职位推荐】
    Android 开发源码分享
    Android 亲测源码分享
  • 原文地址:https://www.cnblogs.com/dingjiaoyang/p/5362997.html
Copyright © 2011-2022 走看看