Hibernate 一级缓存和二级缓存


转载自:http://www.cnblogs.com/200911/archive/2012/10/09/2716873.html


1、 什么是缓存

 

缓存是介于物理数据源与应用程序之间,是对数据库中的数据复制一份临时放在内存中的容器,其作用是为了减少应用程序对物理数据源访问的次数,从而提高了应用程序的运行性能。Hibernate在进行读取数据的时候,根据缓存机制在相应的缓存中查询,如果在缓存中找到了需要的数据(我们把这称做“缓存命中"),则就直接把命中的数据作为结果加以利用,避免了大量发送SQL语句到数据库查询的性能损耗。

 

Hibernate缓存分类:

 

一、Session缓存(又称作事务缓存):Hibernate内置的,不能卸除。

 

缓存范围:缓存只能被当前Session对象访问。缓存的生命周期依赖于Session的生命周期,当Session被关闭后,缓存也就结束生命周期。

 

二、SessionFactory缓存(又称作应用缓存):使用第三方插件,可插拔。

 

缓存范围:缓存被应用范围内的所有session共享,不同的Session可以共享。这些session有可能是并发访问缓存,因此必须对缓存进行更新。缓存的生命周期依赖于应用的生命周期,应用结束时,缓存也就结束了生命周期,二级缓存存在于应用程序范围。

 

1.1、       一级缓存

 

Hibernate一些与一级缓存相关的操作(时间点):

 

数据放入缓存:

 

1. save()。当session对象调用save()方法保存一个对象后,该对象会被放入到session的缓存中。

 

2. get()load()。当session对象调用get()或load()方法从数据库取出一个对象后,该对象也会被放入到session的缓存中。

 

3. 使用HQL和QBC等从数据库中查询数据。

 

例如:数据库有一张表叫Customer,有字段id,username等。

 

 

public class Client
{
   public static void main(String[] args)
    {
       Session session = HibernateUtil.getSessionFactory().openSession();
       Transaction tx = null;
       try
       {
           /*开启一个事务*/
           tx = session.beginTransaction();
           /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/
           Customer customer1 = (Customer)session.get(Customer.class,"402881e534fa5a440134fa5a45340002");
           System.out.println("customer.getUsernameis"+customer1.getUsername());
            /*事务提交*/
           tx.commit();
          
           System.out.println("-------------------------------------");
          
           /*开启一个新事务*/
           tx = session.beginTransaction();
           /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/
           Customer customer2 = (Customer)session.get(Customer.class,"402881e534fa5a440134fa5a45340002");
           System.out.println("customer2.getUsernameis"+customer2.getUsername());
           /*事务提交*/
           tx.commit();
          
           System.out.println("-------------------------------------");
          
           /*比较两个get()方法获取的对象是否是同一个对象*/
           System.out.println("customer1 == customer2 result is"+(customer1==customer2));
       }
       catch (Exception e)
       {
           if(tx!=null)
           {
                tx.rollback();
           }
       }
       finally
       {
           session.close();
       }
    }
}

 

程序控制台输出结果:

 

 

Hibernate:
   select
       customer0_.id as id0_0_,
       customer0_.username as username0_0_,
       customer0_.balance as balance0_0_
   from
       customer customer0_
   where
       customer0_.id=?
customer.getUsername is lisi
-------------------------------------
customer2.getUsername is lisi
-------------------------------------
customer1 == customer2 result is true


其原理是:在同一个Session里面,第一次调用get()方法, Hibernate先检索缓存中是否有该查找对象,发现没有,Hibernate发送SELECT语句到数据库中取出相应的对象,然后将该对象放入缓存中,以便下次使用,第二次调用get()方法,Hibernate先检索缓存中是否有该查找对象,发现正好有该查找对象,就从缓存中取出来,不再去数据库中检索,没有再次发送select语句。

 

数据从缓存中清除:

 

1. evict()将指定的持久化对象从缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。

2. clear()将缓存中的所有持久化对象清除,释放其占用的内存资源。

evict()方法是针对指定的持久化对象,clear()是全部

 

其他缓存操作:

 

1. contains()判断指定的对象是否存在于缓存中。

2. flush()刷新缓存区的内容,使之与数据库数据保持同步。

 

 

1.2、       二级缓存

 

@Test
 
public void testCache2() {
 
Session session1 = sf.openSession();//获得Session1
 
session1.beginTransaction();
 
Category c = (Category)session1.load(Category.class, 1);
 
System.out.println(c.getName());
 
 
 
 
 
session1.getTransaction().commit();
 
session1.close();
 
 
 
Session session2 = sf.openSession();//获得Session2
 
session2.beginTransaction();
 
Category c2 = (Category)session2.load(Category.class, 1);
 
System.out.println(c2.getName());
 
session2.getTransaction().commit();
 
session2.close();
 
}
 


当我们重启一个Session,第二次调用load或者get方法检索同一个对象的时候会重新查找数据库,会发select语句信息。

 

原因:一个session不能取另一个session中的缓存。

 

性能上的问题:假如是多线程同时去取Category这个对象,load一个对象,这个对像本来可以放到内存中的,可是由于是多线程,是分布在不同的session当中的,所以每次都要从数据库中取,这样会带来查询性能较低的问题。

 

解决方案:使用二级缓存。

 

1.什么是二级缓存?

 

SessionFactory级别的缓存,可以跨越Session存在,可以被多个Session所共享。

 

2.适合放到二级缓存中:

 

(1)经常被访问

 

(2)改动不大

 

(3)数量有限

 

(4)不是很重要的数据,允许出现偶尔并发的数据。

 

这样的数据非常适合放到二级缓存中的。

 

用户的权限:用户的数量不大,权限不多,不会经常被改动,经常被访问。

 

例如组织机构。

 

 

 

思考:什么样的类,里面的对象才适合放到二级缓存中?

 

改动频繁,类里面对象特别多,BBS好多帖子,这些帖子20000多条,哪些放到缓存中,不能确定。除非你确定有一些经常被访问的,数据量并不大,改动非常少,这样的数据非常适合放到二级缓存中的。

 

 

3.二级缓存实现原理:

 

  Hibernate如何将数据库中的数据放入到二级缓存中?注意,你可以把缓存看做是一个Map对象,它的Key用于存储对象OID,Value用于存储POJO。首先,当我们使用Hibernate从数据库中查询出数据,获取检索的数据后,Hibernate将检索出来的对象的OID放入缓存中key 中,然后将具体的POJO放入value中,等待下一次再次向数据查询数据时,Hibernate根据你提供的OID先检索一级缓存,若有且配置了二级缓存,则检索二级缓存,如果还没有则才向数据库发送SQL语句,然后将查询出来的对象放入缓存中。

 

4使用二级缓存

 

(1)打开二级缓存:

 

为Hibernate配置二级缓存:

 

在主配置文件中hibernate.cfg.xml:

 

 

<!-- 使用二级缓存 -->
 
<propertyname="cache.use_second_level_cache">true</property>
 
 <!--设置缓存的类型,设置缓存的提供商-->
 
<property   
 
 name="cache.provider_class">org.hibernate.cache.EhCacheProvider
 
</property>

 

(2)配置ehcache.xml

 

<ehcache>
 
   <!--
       缓存到硬盘的路径
   -->
 
   <diskStore path="d:/ehcache"/>
 
 
 
   <defaultCache
 
       maxElementsInMemory="200"<!-- 最多缓存多少个对象 -->
 
       eternal="false"<!-- 内存中的对象是否永远不变 -->
 
       timeToIdleSeconds="50"<!--发呆了多长时间,没有人访问它,这么长时间清除 -->
 
       timeToLiveSeconds="60"<!--活了多长时间,活了1200秒后就可以拿走,一般Live要比Idle设置的时间长 -->
 
       overflowToDisk="true"<!--内存中溢出就放到硬盘上 -->
 
       />
 
   <!--
       指定缓存的对象,缓存哪一个实体类
       下面出现的的属性覆盖上面出现的,没出现的继承上面的。
   -->
 
   <cache name="com.suxiaolei.hibernate.pojos.Order"
 
       maxElementsInMemory="200"
 
       eternal="true"
 
       timeToIdleSeconds="0"
 
       timeToLiveSeconds="0"
 
        overflowToDisk="false"
 
       />
 
</ehcache>


 

(3)使用二级缓存需要在实体类中加入注解:

 

需要ehcache-1.2.jar包:

 

还需要commons_loging1.1.1.jar包

 

@Cache(usage =CacheConcurrencyStrategy.READ_WRITE)

 

Load默认使用二级缓存,就是当查一个对象的时候,它先会去二级缓存里面去找,如果找到了就不去数据库中查了。

 

Iterator默认的也会使用二级缓存,有的话就不去数据库里面查了,不发送select语句了。

 

List默认的往二级缓存中加数据,假如有一个query,把数据拿出来之后会放到二级缓存,但是执行查询的时候不会到二级缓存中查,会在数据库中查。原因每个query中查询条件不一样。

 

(4)也可以在需要被缓存的对象中hbm文件中的<class>标签下添加一个<cache>子标签:

 

 
<hibernate-mapping>
       <class name="com.suxiaolei.hibernate.pojos.Order"table="orders">
           <cache usage="read-only"/>
           <id name="id" type="string">
                <columnname="id"></column>
                <generatorclass="uuid"></generator>
           </id>
          
           <property name="orderNumber" column="orderNumber"type="string"></property>
           <property name="cost" column="cost"type="integer"></property>
          
           <many-to-one name="customer"class="com.suxiaolei.hibernate.pojos.Customer"
                        column="customer_id"cascade="save-update">
           </many-to-one>      
       </class>
   </hibernate-mapping>


 

存在一对多的关系,想要在在获取一方的时候将关联的多方缓存起来,需要再集合属性下添加<cache>子标签,这里需要将关联的对象的hbm文件中必须在存在<class>标签下也添加<cache>标签,不然Hibernate只会缓存OID。

 

 <hibernate-mapping>
       <class name="com.suxiaolei.hibernate.pojos.Customer"table="customer">
           <!-- 主键设置 -->
           <id name="id" type="string">
                <columnname="id"></column>
                <generatorclass="uuid"></generator>
           </id>
          
           <!-- 属性设置 -->
           <property name="username" column="username"type="string"></property>
           <property name="balance" column="balance"type="integer"></property>
          
           <set name="orders" inverse="true"cascade="all" lazy="false" fetch="join">
                <cacheusage="read-only"/>
                <keycolumn="customer_id" ></key>
                <one-to-manyclass="com.suxiaolei.hibernate.pojos.Order"/>
           </set>
       </class>
   </hibernate-mapping>



一级缓存是用于同一个Session中共享数据的,减少往数据库查询次数,当不同Session频繁访问数据库的同一张表时,一级缓存就不明显了,需要使用二级缓存,二级缓存是用于不同Session中共享数据的,在多线程频繁查询数据库,更新数据库方面有较好的效果

 

 

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值