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

一:什么是缓存机制

   当我们频繁访问数据库时,尤其像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

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

3:主程序 NewManager.java

public class NewManager {
	public static void main(String[]args){
		NewManager newManager=new NewManager();
		newManager.secondCache();
	}
	public void secondCache(){
		//获取Session
		Session 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_
PHP	PHP是世界上最好的语言
-------------------
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(){
		//获取Session
		Session 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("-----------------------------");
		//获取Session
		session=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_
PHP	PHP是世界上最好的语言
-----------------------------
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"/>
	<defaultCache
		maxElementsInMemory="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(){
		//获取Session
		Session 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();
		//关闭Session
		HibernateUtil.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_
PHP	PHP是世界上最好的语言
-----------关闭Session,启用二级缓存查询------------
C++	C++表示不服

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

五:总结

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

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





    原文作者:VipMao
    原文地址: https://blog.csdn.net/VipMao/article/details/51517658
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞