• Hibernate总结


    Hibernate缓存应用的积累与总结

     

      Hibernate缓存一直比较难掌握,下面就分析和总结原因,相信你就会慢慢清楚了原来Hibernate缓存也是可以轻松掌握的,但前提要求大家必须跟着动手去验证一下,再用心体会,光看是没有用的

    目录:

    一、hibernate一级缓存(Session 级别的缓存)

    二、一级缓存特征及其应用

    三、管理一级缓存

    四、Hibernate二级缓存(sessionFactory级别缓存)

    五、总结

    一、hibernate一级缓存(Session 级别的缓存) 

      hibernate是一个线程对应一个session,一个线程可以看成一个用户。也就是说session级缓存(一级缓存)只能给一个线程用,别的线程用不了,一级缓存就是和线程绑定了。hibernate一级缓存生命周期很短,和session生命周期一样,一级缓存也称session级的缓存或事务级缓存。如果tb事务提交或回滚了,我们称session就关闭了,生命周期结束了。

      实验1:体验一级缓存:(动手做做)

    复制代码
     //同一个session中,发出两次load方法查询
    
    Employee  emp= (Employee )session.load(Employee .class, 1);
    
    System.out.println( emp.getName());
    
    //不会发出查询语句,load使用缓存
    
    emp = (Employee )session.load(Employee .class, 1);
    
    System.out.println(emp.getName());
    复制代码

      第二次查询第一次相同的数据,第二次load方法就是从缓存里取数据,不会发出sql语句到数据库里查询。

      缓存主要是用于查询 ,hibernate的很多方法都是首先从缓存中取数据如果没有在从数据库中获取,以提升查询效率如:get()/load()、iterate(),而且持久态的对象是会存储在缓存中的。例如:先save保存实体对象,再用load方法查询刚刚save的实体对象,则load方法不会发出sql语句到数据库查询的,而是到缓存里取数据,因为save方法也支持缓存.

    二、一级缓存特征及其应用:

      1.Session 级别的缓存,它同session邦定。它的生命周期和session相同。Session消毁,它也同时消毁;

      2.两个session 不能共享一级缓存,因它会伴随session的生命周期的创建和消毁;

      3.Session缓存是实体级别的缓存,就是只有在查询对象级别的时候才使用,如果使用HQL和SQL是查询属性级别的,是不使用一级缓存的!切记!!!

      4.iterate 查询使用缓存,会发出查询Id的SQL和HQL语句,但不会发出查实体的,它查询完会把相应的实体放到缓存里边,一些实体查询如果缓存里边有,就从缓存中查询,但还是会发出查询id的SQL和HQL语句。如果缓存中没有它会数据库中查询,然后将查询到的实体一个一个放到缓存中去,所以会有N+1问题出现。

      5.List()和iterate 查询区别:(动手做做)

      使用iterate,list查询实体对象*N+1问题,在默认情况下,使用query.iterate查询,有可以能出现N+1问题

      所谓的N+1是在查询的时候发出了N+1条sql语句1:首先发出一条查询对象id列表的sqlN:

      根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的sql语句list和iterate的区别?

      list每次都会发出sql语句,list会向缓存中放入数据,而不利用缓存中的数据

      iterate:在默认情况下iterate利用缓存数据,但如果缓存中不存在数据有可以能出现N+1问题

      6.Get()和load(),iterate方法都会使用一级缓存,

      Get与load的区别? (动手做做)

      1. 对于get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据库中没有就返回null。

      2. load方法加载实体对象的时候,根据映射文件上类级别的lazy属性的配置(默认为true),分情况讨论:

        (1)若为true,则首先在Session缓存中查找,看看该id对应的对象是否存在,不存在则使用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由CGLIB动态生成)。等到具体使用该对象(除获取OID以外)的时候,再查询二级缓存和数据库,若仍没发现符合条件的记录,则会抛出一个ObjectNotFoundException。

        (2)若为false,就跟get方法查找顺序一样,只是最终若没发现符合条件的记录,则会抛出一个ObjectNotFoundException。

      小结:

            1、get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;而load方法首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库

            2、如果未能发现符合条件的记录,get方法返回null,而load方法会抛出一个ObjectNotFoundException。

            3、load使用代理延迟加载数据,而get方法往往返回有实体数据的对象

            使用:

            1、如果想对一个对象进行增删改查之类,该使用load方法,性能提高,可以使用代理对象,省去了一次和数据库交互的机会,当真正用到该对象的属性时,才跟数据库交互

            2、如果你想加载一个对象使用它的属性,该使用get方法

      7.hiberate3 session 存储过程如下:

      例如 object 对象Session.save(object);

      这时候不会把数据放到数据库,会先放到session缓存中去,数据库中没有相应记录,session.flush();才发SQL和HQL语句,数据库中有了相应记录,

      但是数据库用select查不到,这是跟数据库事物级别有关系。

      Session.beginTrransaction()。commit();

      事物提交后可以查询到了。

      Session.flush()语句但是为什么不写呢,因为commit()会默认调用flush();  

    三、管理一级缓存

      无论何时,当你给save()、update()或 saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。当随后flush()方法被调用时,对象的状态会和数据库取得同步。 如果你不希望此同步操作发生,或者你正处理大量对象、需要对有效管理内存时,你可以调用evict() 方法,从一级缓存中去掉这些对象及其集合。

    复制代码
    ScrollableResult cats = sess.createQuery("from Cat as cat").scroll();
    while ( cats.next() ) {
        Cat cat = (Cat) cats.get(0);
        doSomethingWithACat(cat);
        sess.evict(cat);
    }
    复制代码

    Session还提供了一个contains()方法,用来判断某个实例是否处于当前session的缓存中。如若要把所有的对象从session缓存中彻底清除,则需要调用Session.clear()。

    四、Hibernate二级缓存(sessionFactory级别缓存)

      二级缓存需要sessionFactory来管理,它是进初级的缓存,所有人都可以使用,它是共享的。Hibernate二级缓存支持对象缓存、集合缓存、查询结果集缓存,对于查询结果集缓存可选。

      二级缓存比较复杂,一般用第三方产品。hibernate提供了一个简单实现,用Hashtable做的,只能作为我们的测试使用,商用还是需要第三方产品。

      几种优秀缓存方案:

      1、Memcached 分布式缓存系统 2、JBOSS CACHE   3 、EhCache   Ehcache 2.1起提供了针对Hibernate的JTA支持。 4、Infinispan 开源数据网格平台

      使用缓存,肯定是长时间不改变的数据,如果经常变化的数据放到缓存里就没有太大意义了。因为经常变化,还是需要经常到数据库里查询,那就没有必要用缓存了。hibernate做了一些优化,和一些第三方的缓存产品做了集成。这里采用EHCache缓存产品。

      和EHCache二级缓存产品集成:EHCache的jar文件在hibernate的lib里,我们还需要设置一系列的缓存使用策略,需要一个配置文件ehcache.xml来配置。

      hibernate.cfg.xml 配置(动手做做) 

    复制代码
    <!-- 开启二级缓存 -->  
    <property name="hibernate.cache.use_second_level_cache">true</property>  
    <!-- 开启查询缓存 -->  
    <property name="hibernate.cache.use_query_cache">true</property>  
    <!-- 二级缓存区域名的前缀 -->  
    <!--<property name="hibernate.cache.region_prefix">h3test</property>-->  
    <!-- 高速缓存提供程序  第三方产品-->  
    <property name="hibernate.cache.region.factory_class">  
    net.sf.ehcache.hibernate.EhCacheRegionFactory  
    </property>  
    <!-- 指定缓存配置文件位置 -->  
    <property name="hibernate.cache.provider_configuration_file_resource_path">  
    ehcache.xml  
    </property>  
    <!-- 强制Hibernate以更人性化的格式将数据存入二级缓存 -->  
    <property name="hibernate.cache.use_structured_entries">true</property>  
    <!-- Hibernate将收集有助于性能调节的统计数据 -->  
    <property name="hibernate.generate_statistics">true</property>   
    复制代码

      ehcache配置(ehcache.xml)(动手做做)

    复制代码
    <?xml version="1.0" encoding="UTF-8"?>  
    <ehcache name="h3test">      <!--指定区域名-->
    <defaultCache  
    maxElementsInMemory="100"      <!--缓存在内存中的最大数目-->
    eternal="false"                          <!--缓存是否持久-->
    timeToIdleSeconds="1200"     <!--当缓存条目闲置n秒后销毁-->
    timeToLiveSeconds="1200"    <!--当缓存条目存活n秒后销毁-->
    overflowToDisk="false">    <!--硬盘溢出-->
    </defaultCache>  
    </ehcache>    
    复制代码

      实体只读缓存

          只读缓存 read only,不须要锁与事务,因为缓存自数据从数据库加载后就不会改变。如果数据是只读的,例如引用数据,那么总是使用“read-only”策略,因为它是最简单、最高效的策略,也是集群安全的策略。是性能第一的策略

    <hibernate-mapping>  
    <class name="com.ljb.entity.Voucher" table="Voucher">  
    <cache usage="read-only"/>  
         ……  
    </hibernate-mapping>

      二级缓存测试代码(动手做做)

    复制代码
    Session session1 = sf.openSession();  
    Transaction t1 = session1.beginTransaction();  
    //确保数据库中有标识符为1的Voucher 
    Voucher voucher = (Vocher) session1.get(Vocher.class, 1);  
    //如果修改将报错,只读缓存不允许修改  
     //voucher.setName("aaa");  
    t1.commit();  
    session1.close();
    Session session2 = sf.openSession();  
    Transaction t2 = session2.beginTransaction();  
              
    voucher = (Vocher) session2.get(Vocher.class, 1);   //在二级缓存中查找结果,不会产出sql语句,不操作数据库
    t2.commit();  
    session2.close();  
    sf.close();   
    复制代码

      只读缓存不允许更新,将报错Can't write to a readonly object。允许新增,( 新增直接添加到二级缓存)

      读写缓存 read write

      对缓存的更新发生在数据库事务完成后。缓存需要支持锁。在一个事务中更新数据库,在这个事务成功完成后更新缓存,并释放锁。锁只是一种特定的缓存值失效表述方式,在它获得新数据库值前阻止其他事务读写缓存。那些事务会转而直接读取数据库  

         实体读/写缓存

    <hibernate-mapping>  
    <class name="com.ljb.entity.Voucher" table="Voucher">  
    <cache usage="read-write"/>  
            ……  
    </hibernate-mapping> 

      二级缓存测试代码

    复制代码
    Session session1 = sf.openSession();  
    Transaction t1 = session1.beginTransaction();  
    //确保数据库中有标识符为1的Voucher 
    Voucher voucher = (Vocher) session1.get(Vocher.class, 1);  
    //如果修改将报错,只读缓存不允许修改  
    voucher.setName("aaa");  
    t1.commit();  
    session1.close();        
    Session session2 = sf.openSession();  
    Transaction t2 = session2.beginTransaction();  
    voucher = (Vocher) session2.get(Vocher.class, 1);   //该条目已经被别的事务修改了,此时重新查询一次数据库   
    t2.commit();  
    session2.close();  
    sf.close();   
    复制代码

      允许更新,更新后自动同步到缓存。允许新增,新增记录后自动同步到缓存。保证read committed隔离级别及可重复读隔离级别(通过时间戳实现)整个过程加锁,如果当前事务的时间戳早于二级缓存中的条目的时间戳,说明该条目已经被别的事务修改了,此时重新查询一次数据库,否则才使用缓存数据,因此保证可重复读隔离级别

      非严格读写缓存 nonstrict read write

      在一个事务中更新数据库,在这个事务完成前就清除缓存,为了安全起见,无论事务成功与否,在事务完成后再次清除缓存。既不需要支持缓存锁,也不需要支持事务。如果是缓存集群,“清除缓存”调用会让所有副本都失效,这通常被称为“拉(pull)”更新策略。如果你的数据读很多或者很少有并发缓存访问和更新,那么可以使用“nonstrict-read-write”策略。感谢它的轻量级“拉”更新策略,它通常是性能第二好的策略。

      实体非严格读/写缓存

    <hibernate-mapping> 
    <class name="com.ljb.entity.Voucher" table="Voucher"> 
    <cache usage="nonstrict-read-write"/> 
         …… 
    </hibernate-mapping>  

      测试代码 略(我想大家会验证了)

      验证结果

      允许更新,更新后缓存失效,需再查询一次。 允许新增,新增记录自动加到二级缓存中。整个过程不加锁,不保证。

      事务缓存 transactional (一定要在JTA环境中)

      对缓存和数据库的更新被包装在同一个JTA事务中,这样缓存与数据库总是保持同步的。数据库和缓存都必须支持JTA。除非你真的想将缓存更新和数据库更新放在一个JTA事务里,否则不要使用“transactional”策略,因为JTA需要漫长的两阶段提交处理,这导致它基本是性能最差的策略。

      需要特定缓存的支持和JTA事务支持,此处不演示。 

      集合缓存

      演示读/写缓存示例,和之前实体缓存测试差不多,其他自测

    复制代码
    <hibernate-mapping>  
      <class name="cn.javass.h3test.model.UserModel" table="TBL_USER">  
        <cache usage="read-write" />  
        <set name="vouchers" cascade="all" inverse="true" lazy="false">  
          <cache usage="read-write"/>  
          <key column="fk_user_id"/>  
          <one-to-many class="cn.ljb.entity.Voucher"/>  
        </set>  
      </class>  
    </hibernate-mapping>    
    复制代码
    复制代码
    SessionFactory sf =   
    new Configuration().configure().buildSessionFactory();  
    Session session1 = sf.openSession();  
    Transaction t1 = session1.beginTransaction();  
     //确保数据库中有标识符为1的UserModel  
    UserModel user = (UserModel) session1.get(UserModel.class, 1);  
    user.getVouchers();  
    t1.commit();  
    session1.close();  
          
    Session session2 = sf.openSession();  
    Transaction t2 = session2.beginTransaction();  
    user = (UserModel) session2.get(UserModel.class, 1);  
    user.getVouchers();  
    t2.commit();  
    session2.close();  
    sf.close();  
    复制代码

      测试结论:   

      和实体并发策略有相同含义; 但集合缓存只缓存集合元素的标识符,在二级缓存中只存放相应实体的标识符,然后再通过标识符去二级缓存查找相应的实体最后组合为集合返回。

      查询缓存 (动手做做)

      1、保证全局配置中有开启了查询缓存。

      2、修改FarmModel.hbm.xml,添加如下红色部分配置,表示实体缓存并读/写

    <hibernate-mapping>  
        <class name="com.lijb.entity.Voucher" table="voucher">  
            <cache usage="read-write"/>  
             ……  
    </hibernate-mapping>   

      3、测试代码

    复制代码
    SessionFactory sf =   .new Configuration().configure().buildSessionFactory();  
    Session session1 = sf.openSession();  
    Transaction t1 = session1.beginTransaction();  
    Query query = session1.createQuery("fromVoucher");  
    //即使全局打开了查询缓存,此处也是必须的  
    query.setCacheable(true);  
    List<Voucher> voucherList = query.list();  
    t1.commit();  
    session1.close();  
    Session session2 = sf.openSession();  
    Transaction t2 = session2.beginTransaction();  
    query = session2.createQuery("from Voucher");  
    //即使全局打开了查询缓存,此处也是必须的  
    query.setCacheable(true);  
    voucherList = query.list();  
    t2.commit();  
    session2.close();  
    sf.close();  
    复制代码

      结论:

      和实体并发策略有相同含义; 和集合缓存类似,只缓存集合元素的标识符,在二级缓存中只存放相应实体的标识符,然后再通过标识符 去二级缓存查找相应的实体最后组合为集合返回。

          什么时候需要查询缓存?

          大多数时候无法从结果集高速缓存获益。必须知道:每隔多久重复执行同一查询。对于那些查询非常多但插入、删除、更新非常少的应用程序来说,查询缓存可提升性能。但写入多查询少的没有用,总失效。

      管理二级缓存

      对于二级缓存来说,在SessionFactory中定义了许多方法, 清除缓存中实例、整个类、集合实例或者整个集合。

    复制代码
    sessionFactory.evict(Cat.class, catId); //evict a particular Cat
    sessionFactory.evict(Cat.class);  //evict all Cats
    sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
    sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections
    sessionFactory.evictQueries()//evict all queries
    
    //CacheMode参数用于控制具体的Session如何与二级缓存进行交互。
    //CacheMode.NORMAL - 从二级缓存中读、写数据。
    //CacheMode.GET - 从二级缓存中读取数据,仅在数据更新时对二级缓存写数据。
    //CacheMode.PUT - 仅向二级缓存写数据,但不从二级缓存中读数据。
    //CacheMode.REFRESH - 仅向二级缓存写数据,但不从二级缓存中读数据。通过 hibernate.cache.use_minimal_puts的设置,强制二级缓存从数据库中读取数据,刷新缓存内容
    复制代码

      监控二级缓存

      如若需要查看二级缓存或查询缓存区域的内容,你可以使用统计(Statistics) API。通过sessionFactory.getStatistics();获取Hibernate统计信息。此时,你必须手工打开统计选项。

     hibernate.generate_statistics true
     hibernate.cache.use_structured_entries true

     五、总结

      不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。

      如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。

      在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些。

    作者:KeerDi —— 北方的后生

    出处:http://www.cnblogs.com/keerdi/

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    与众不同 windows phone (50)
    与众不同 windows phone (49)
    重新想象 Windows 8.1 Store Apps (93)
    重新想象 Windows 8.1 Store Apps 系列文章索引
    重新想象 Windows 8.1 Store Apps (92)
    重新想象 Windows 8.1 Store Apps (91)
    重新想象 Windows 8.1 Store Apps (90)
    重新想象 Windows 8.1 Store Apps (89)
    重新想象 Windows 8.1 Store Apps (88)
    重新想象 Windows 8.1 Store Apps (87)
  • 原文地址:https://www.cnblogs.com/123hll/p/6956853.html
Copyright © 2020-2023  润新知