浅谈Hibernate缓存机制:一级缓存、二级缓存

来源:互联网 发布:南山空同 知乎 编辑:程序博客网 时间:2024/05/01 18:44

一:什么是缓存机制

   当我们频繁访问数据库时,尤其像Hibernate持久层框架,会导致数据库访问性能降低,因此我们期望有一种机制能提供一个"缓存空间",我们将需要的数据复制到这个"缓存空间",当数据查询时,我们先在这个"缓存空间"里找,如果没有,我们再去数据库查找,这样就减少了与数据库的访问,从而提高了数据库访问性能,这就是缓存机制。

二:Hibernate缓存机制

1:一级缓存:Hibernate默认的缓存机制,它属于Session级别的缓存机制,也就是说Session关闭,缓存数据消失。

2:二级缓存:属于SessionFactory级别的缓存,二级缓存是全局性的,应用中的所有Session都共享这个二级缓存。

  二级缓存默认是关闭的,一旦开启,当我们需要查询数据时,会先在一级缓存查询,没有,去二级缓存,还没有,好,咱们再去数据库,因此缓存机制大大提高了数据库的访问性能。

三:一级缓存用法

  当程序调用Session的save()方法持久化对象时,程序并不会立刻将这个数据搞到数据库,而是将它放在了Session的一级缓存中,Session的get()、load()方法也是,当我们调用Session的flush()时,数据才会一并存到数据库。

下面例子演示一级缓存用法:

1:持久化类 News.java

@Entity@Table(name="new_inf")public class News {@Id@Column(name="new_id")@GeneratedValue(strategy=GenerationType.IDENTITY)private Integer id;private String title;private String content;//省略set、get方法

2:数据库表 new_inf


3:主程序 NewManager.java

public class NewManager {public static void main(String[]args){NewManager newManager=new NewManager();newManager.secondCache();}public void secondCache(){//获取SessionSession session=HibernateUtil.currentSession();//开启事务Transaction tx=session.beginTransaction();//获取数据List list=session.createQuery("from News news").list();//将数据放入session缓存即一级缓存News news =(News) session.load(News.class, 2);System.out.println(news.getTitle()+"\t"+news.getContent());tx.commit();System.out.println("-------------------");//开启一个新的事务tx=session.beginTransaction();//从一级缓存中获取数据News newse=(News) session.load(News.class, 3);System.out.println(newse.getTitle()+"\t"+newse.getContent());tx.commit();}}

4:控制台输出结果

Hibernate:     select        news0_.new_id as new_id1_0_,        news0_.content as content2_0_,        news0_.title as title3_0_     from        new_inf news0_PHPPHP是世界上最好的语言-------------------C++C++表示不服

  可以看到控制台仅仅输出了一条sql语句,也就是仅与数据库进行了一次交互,这是为什么?原因就是我们前面获取数据,然后将数据放入了一级缓存中,然后我们在后面查询id=3的数据时直接在一级缓存中查询就可以了。

前面说到,一级缓存是Session缓存,session一关闭数据就没了,真的吗?咱们试试

咱们将主程序NewManager.java改为:

public class NewManager {public static void main(String[]args){NewManager newManager=new NewManager();newManager.secondCache();}public void secondCache(){//获取SessionSession session=HibernateUtil.currentSession();//开启事务Transaction tx=session.beginTransaction();//获取数据List list=session.createQuery("from News news").list();//将数据放入session缓存即一级缓存News news =(News) session.load(News.class, 2);System.out.println(news.getTitle()+"\t"+news.getContent());tx.commit();//关闭Session 意味着一级缓存内的数据消失HibernateUtil.closeSession();System.out.println("-----------------------------");//获取Sessionsession=HibernateUtil.currentSession();//开启一个新的事务     tx=session.beginTransaction();//这里是在数据库查询数据而不是一级缓存中News newse=(News) session.load(News.class, 3);System.out.println(newse.getTitle()+"\t"+newse.getContent());tx.commit();}}

控制台结果是这样的:

Hibernate:     select        news0_.new_id as new_id1_0_,        news0_.content as content2_0_,        news0_.title as title3_0_     from        new_inf news0_PHPPHP是世界上最好的语言-----------------------------Hibernate:     select        news0_.new_id as new_id1_0_0_,        news0_.content as content2_0_0_,        news0_.title as title3_0_0_     from        new_inf news0_     where        news0_.new_id=?C++C++表示不服

上面这个主程序我们关闭了Session,一级缓存内的数据将会消失,所以查询时将会去数据库查询,也就输出了两条SQL语句,这就是为什么叫一级缓存为Session查询。

一级缓存中常用其他方法:

session.evit(Object obj) 将指定的持久化对象从一级缓存中清除,释放所占用的内存资源,该对象从持久化状态变为脱管状态,从而成为游离对象

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

session.contains(Object obj)  判断指定的对象是否存在于一级缓存中。

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


那我们可不可以关闭Session的同时也从缓存内查询数据呢?答案是可以的,通过下面的二级缓存,也就是SessionFactory级别的缓存。

四:二级缓存 

使用二级缓存步骤:

1:在配置文件hibernate.cfg.xml中开启二级缓存并设置缓存实现类如下:

 <!-- 开启二级缓存 -->        <property name="hibernate.cache.use_second_level_cache">true</property>        <!-- 设置缓存区的实现类,类型为内存、磁盘、事务性、支持集群 -->        <property name="hibernate.cache.region.factory_class">        org.hibernate.cache.ehcache.EhCacheRegionFactory        </property>         <!-- 二级缓存配置文件的位置 -->        <property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>        <!-- 指定根据当前线程来界定上下文相关Session --><property name="hibernate.current_session_context_class">thread</property>

上面的EhCacheRegionFactory就是Hibernate常用的缓存实现类

2:将相应的缓存Jar文件添加到类加载路径

  这个我们在下载好的hibernate的lib->optional->ehcache下可以找到,然后导入Jar文件。

3:添加ehcache.xml配置文件到类加载路径下 如下:

<!-- ehcache.xml --><?xml version="1.0" encoding="UTF-8"?><ehcache>    <!--        缓存到硬盘的路径    -->    <diskStore path="d:/ehcache"></diskStore>    <!--        默认设置        maxElementsInMemory : 在內存中最大緩存的对象数量。        eternal : 缓存的对象是否永远不变。        timeToIdleSeconds :可以操作对象的时间。        timeToLiveSeconds :缓存中对象的生命周期,时间到后查询数据会从数据库中读取。        overflowToDisk :内存满了,是否要缓存到硬盘。    -->    <defaultCache maxElementsInMemory="200" eternal="false"         timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></defaultCache>    <!--        指定缓存的对象。        下面出现的的属性覆盖上面出现的,没出现的继承上面的。    -->    <cache name="com.suxiaolei.hibernate.pojos.Order" maxElementsInMemory="200" eternal="false"         timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></cache></ehcache>

4:通过使用@Cache注解修饰需要启用二级缓存的实体类、实体的那些集合属性。如下

@Entity@Table(name="new_inf")@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)public class News {@Id@Column(name="new_id")@GeneratedValue(strategy=GenerationType.IDENTITY)private Integer id;private String title;private String content;

二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。

全部完成后,咱们测试一下

持久化类:News.java

@Entity@Table(name="new_inf")@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)public class News {@Id@Column(name="new_id")@GeneratedValue(strategy=GenerationType.IDENTITY)private Integer id;private String title;private String content;//省略所有set、get方法

配置文件 ehcache.xml

<?xml version="1.0" encoding="UTF-8"?><ehcache><diskStore path="java.io.tmpdir"/><defaultCachemaxElementsInMemory="10000"eternal="false"overflowToDisk="true"timeToIdleSeconds="120"timeToLiveSeconds="120"diskPersistent="false"/></ehcache>

主程序 NewManager.java

public class NewManager {public static void main(String[]args){NewManager newManager=new NewManager();newManager.secondCache();}public void secondCache(){//获取SessionSession session=HibernateUtil.currentSession();//开启事务Transaction tx=session.beginTransaction();//获取数据List list=session.createQuery("from News news").list();//通过id查找数据News news =(News) session.load(News.class, 2);System.out.println(news.getTitle()+"\t"+news.getContent());tx.commit();//关闭SessionHibernateUtil.closeSession();System.out.println("-----------关闭Session,启用二级缓存查询------------");session=HibernateUtil.currentSession();session.beginTransaction();//通过二级缓存查询数据News news2 =(News) session.load(News.class, 3);System.out.println(news2.getTitle()+"\t"+news2.getContent());}}

这里值得说的是,咱们在主程序中启用二级缓存时第二次Session对象和第一次Session对象都是同一对象,但是我们得明白,一级缓存是局部缓存仅对当前Session有效,但是二级缓存是全局缓存对所有的Session有效,因此在启用二级缓存时,即使我们用的是新建Session session2,程序依然可以在二级缓存中查询数据。

控制台输出结果:

Hibernate:     select        news0_.new_id as new_id1_0_,        news0_.content as content2_0_,        news0_.title as title3_0_     from        new_inf news0_PHPPHP是世界上最好的语言-----------关闭Session,启用二级缓存查询------------C++C++表示不服

可以看到虽然我们关闭了Session,但是我们启用了二级缓存,二级缓存属于SessionFactory缓存,它不会因为Session的关闭而丢失数据,因此我们依然可以通过二级缓存查询数据,如数据结果所示,第二次查询并没有同数据库交互,而是直接从二级缓存中获取数据。

五:总结

什么样的数据适合存放到第二级缓存中?   
1) 很少被修改的数据   
2) 不是很重要的数据,允许出现偶尔并发的数据   
3) 不会被并发访问的数据   
4) 常量数据   
不适合存放到第二级缓存的数据?   
1) 经常被修改的数据   
2) 绝对不允许出现并发访问的数据,如财务数据,绝对不允许出现并发   
3) 与其他应用共享的数据。
  无论是一级缓存还是二级缓存,都是对整个实体进行缓存,而不是缓存的实体属性,像上面的例子,我们是缓存的News这个实体,最后我们可以从缓存中取得实体的各个属性,如果想对普通属性进行缓存,比如缓存Nwes实体的属性new.title则可以使用查询缓存。

自己学习过程中的心得整理一下同大家分享,共同进步。







1 0