Hibernate:二级缓存

缓存(Cache):计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存。

Hibernate中提供了两个级别的缓存:

  • 第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的。
  • 第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存。

1,一级缓存和二级缓存

Session级别的是一级缓存不需要开发者关心,默认总是有效的,当应用保存持久化实体、修改持久化实体时,Sessioin并不会立即把这种改变flush到数据库,而是缓存在当前Session的一级缓存中,除非程序显式调用Session的flush()方法,或程序关闭Session时才会把这些改变一次性的flush到底层数据库——通过这种缓存,可以减少与数据库交互,从而提高数据库访问性能。

在 Session 接口的实现中包含一系列的 Java 集合,这些 Java 集合构成了 Session 缓存。只要 Session 实例没有结束生命周期,且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期。

Session缓存的操作

  • flush:Session 按照缓存中对象的属性变化来同步更新数据库。

默认情况下 Session 在以下时间点刷新缓存:

  • 显式调用 Session 的 flush() 方法。
  • 当应用程序调用 Transaction 的 commit()方法的时, 该方法先 flush ,然后在向数据库提交事务。
  • 当应用程序执行一些查询(HQL, Criteria)操作时,如果缓存中持久化对象的属性已经发生了变化,会先 flush 缓存,以保证查询结果能够反映持久化对象的最新状态。

flush 缓存的例外情况:如果对象使用 native 生成器生成 OID,那么当调用 Session 的 save() 方法保存对象时, 会立即执行向数据库插入该实体的 insert 语句。

commit () 和 flush() 方法的区别:flush 执行一系列 sql 语句,但不提交事务;commit 方法先调用flush() 方法,然后提交事务,意味着提交事务意味着对数据库操作永久保存下来。

SessionFactory级别的二级缓存时全局性的,应用的所有Session都共享这个二级缓存。不过,SessionFactory级别的二级缓存默认是关闭的,必须由程序显式开启。一旦在应用中开启了二级缓存,当Session需要抓取数据时,Session将会先查找一级缓存,再查找二级缓存,只有当一级缓存和二级缓存中都没有需要抓取的数据时,才会去查找底层数据库。在适当情况下,合理地设置Hibernate的二级缓存也可以很好地提高应用的数据库访问性能。

SessionFactory 的缓存可以分为两类:

  • 内置缓存: Hibernate 自带的, 不可卸载。通常在 Hibernate 的初始化阶段,Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中,映射元数据是映射文件中数据(.hbm.xml 文件中的数据)的复制。该内置缓存是只读的。
  • 外置缓存(二级缓存):一个可配置的缓存插件。在默认情况下, SessionFactory 不会启用这个缓存插件。外置缓存中的数据是数据库数据的复制,外置缓存的物理介质可以是内存或硬盘。

适合放入二级缓存中的数据:

  • 很少被修改。
  • 不是很重要的数据, 允许出现偶尔的并发问题。

不适合放入二级缓存中的数据:

  • 经常被修改。
  • 财务数据, 绝对不允许出现并发问题。
  • 与其他应用程序共享的数据。

2,开启二级缓存

为了开启Hibernate二级缓存,需要在hibernate.cfg.xml文件中设置如下属性:

<property name="hibernate.cache.use_second_level_cache">true</property>

一旦开启了二级缓存,并且设置了对某个持久化实体类启用缓存,SessionFactory就会缓存应用访问过的该实体类的每个对象,除非缓存的数据超出缓存空间。

实际应用一般不需要开发者自己实现缓存,直接使用第三方提供的开源缓存实现即可。因此,在hibernate.cfg.xml文件中设置开启缓存后,还需要设置使用哪种二级缓存实现类。

<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.EhcacheRegionFactory</property>

二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存:

缓存缓存实现类类型集群安全查询缓存支持
ConcurrentHashMaporg.hibernate.testing.cache.CachingRegionFactory内存  
EhCacheorg.hibernate.ache.echache.EhCacheRegionFactory内存、缓存、事务性、支持集群
Infinispanorg.hibernate.cache.infinispan.InfinispanRegionFactory事务性,支持集群

开启二级缓存

(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.internal.EhcacheRegionFactory</property>

(2)将二级缓存的JAR包添加到项目中:将Hibernate目录下的lib/optional\下的对应缓存的JAR包复制到应用的类加载路径中,另外还需要另外两个JAR包。

 (3)将缓存实现所需要的配置文件添加到系统的类加载路径中。对于EhCache缓存,它需要一个ehcache.xml配置文件

<?xml version="1.0" encoding="GBK" ?>
<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"/>
</ehcache>

 (4)设置对哪些实体类、实体的那些集合启用二级缓存。这一步有两种方式:

  • 修改要使用缓存的持久化类文件,使用Hibernate提供的@Cache注解修饰该持久化类,或使用该注解修饰集合属性。
  • 在hibernate.cfg.xml文件中使用<class-cache.../>或<collection-cache.../>元素对指定的持久化类、集合属性启用二级缓存。

上面两种设置方式只是存在形式不同,本质完全相同。通常来说,推荐采用第一种方式,在这种方式下,不同实体的缓存策略放在不同的持久化类中管理,更符合软件工程中分而治之的策略。

@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Animal {
    private int id;
    private String name;
    public Animal() {

    }
    //省略get()和set()方法
}

二级缓存的并发访问策略:两个并发的事务同时访问持久层的缓存的相同数据时,也有可能出现各类并发问题。二级缓存可以设定以下 4 种类型的并发访问策略,每一种访问策略对应一种事务隔离级别。

  • 非严格读写(Nonstrict-read-write):不保证缓存与数据库中数据的一致性。提供 Read Uncommited 事务隔离级别,对于极少被修改,而且允许脏读的数据,可以采用这种策略。
  • 读写型(Read-write):提供 Read Commited 数据隔离级别。对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读。
  • 事务型(Transactional):仅在受管理环境下适用。它提供了 Repeatable Read 事务隔离级别。对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读。
  • 只读型(Read-Only):提供 Serializable 数据隔离级别,对于从来不会被修改的数据,可以采用这种访问策略。

(5)测测试缓存效果

public class Main {
    public static void main(String[] args) {
        Configuration conf = new Configuration().configure();
        ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().configure().build();
        SessionFactory sf = conf.buildSessionFactory(serviceRegistry);
        Session session =   sf.openSession();
        session.beginTransaction();
        List<Animal> list  = (List<Animal>)session.createQuery("from Animal").list();
        session.getTransaction().commit();
        System.out.println("----------------------");
        // 打开第二个Session
        Session sess2 = sf.openSession();
        sess2.beginTransaction();
        // 根据主键加载实体,系统将直接从二级缓存读取
        // 因此不会发出查询的SQL语句
        Animal news = (Animal)sess2.load(Animal.class , 1);
        System.out.println(news.getName());
        sess2.getTransaction().commit();
    }
}

3,管理缓存和统计缓存

1、Session级别的一级缓存是局部缓存,它只对当前Session有效。

对于Session级别的一级缓存而言,所有经过它操作的实体,不管使用save()、update()或saveOrUpdate()方法保存一个对象,还是使用load()、get()、list()、iterate()或scroll()方法获得一个对象,该对象都将被放入Session级别的一级缓存中——在Session调用flush()方法或close()方法之前,这些对象将一直缓存在一级缓存中。

在某些特殊的情况下,列入正在处理一个大对象(开销非常大),可能需要从一级缓存中去掉这个大对象或集合属性,可以调用Session的evict(Object object)方法,将该对象或集合从一级缓存中剔除。如果想把所有的对象都从Session中彻底清除,则调用Session的clear()方法即可。

为了判断某个对象是否处于Session缓存中,可以借助于Session提供的contains(Object object)方法,该方法返回一个boolean值,用于标识某个实例是否处于当前Session的缓存中。

2、SessionFactory级别的二级缓存是全局缓存,它对所有的Session都有效的。

SessionFactory提供了一个getCache()方法,该方法的返回值是Cache对象,通过该对象即可操作二级缓存中的实体、集合等。

Cache cache = sf.getCache();
//清除指定的News对象
cache.evictEntity(New.class,id);
//清除所有的News对象
cache.evictEntiryRegion(News.class);
//清除指定id的News所关联的参与者集合属性
cache.evictCollection("News.actors,id);
//清除所有News所关联的参与者集合属性
cache.evictCollection("News.actors");

为了更好地统计二级缓存中的内容,可以借助于Hibernate的统计API。为了开启二级缓存的统计功能,也需要在hibernate.cfg.xml文件中进行配置。

<!--开启二级缓存的统计功能-->
<property name="hibernate.generate_statistics">true</property>
<!--设置使用结构化方式来维护缓存项-->
<property name="cache.use_structured_entries">true</property>

可以通过如下方式查看二级缓存的内容:

// ----------统计二级缓存----------
Map cacheEntries = sf.getStatistics()
	// 二级缓存的名字默认与持久化类的类名相同
    .getSecondLevelCacheStatistics("org.crazyit.app.domain.News")
	.getEntries();
System.out.println(cacheEntries);

4,查询缓存

对于经常使用的查询语句,如果启用了查询缓存,当第一次执行查询语句时,Hibernate 会把查询结果存放在查询缓存中。 以后再次执行该查询语句时,只需从缓存中获得查询结果, 从而提高查询性能,查询缓存依赖于二级缓存。

查询缓存使用于如下场合:

  • 应用程序运行时经常使用查询语句。
  • 很少对与查询语句检索到的数据进行插入, 删除和更新操作。

启用查询缓存的步骤:

  • 置二级缓存, 因为查询缓存依赖于二级缓存。
  • 在 hibernate 配置文件中启用查询缓存。
<property name="hibernate.cache.use_query_cache">true</property>
  • 对于希望启用查询缓存的查询语句, 调用 Query 的 setCacheable() 方法。
public class NewsManager{
	static Configuration conf = new Configuration().configure();
	// 以Configuration实例创建SessionFactory实例
	static ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(conf.getProperties()).build();
	static SessionFactory sf = conf.buildSessionFactory(serviceRegistry);
	public static void main(String[] args) throws Exception{
		NewsManager mgr = new NewsManager();
		mgr.cacheQuery();
		mgr.stat();
	}

	private void noCacheQuery(){
		Session session = sf.getCurrentSession();
		session.beginTransaction();
		List titles  = session.createQuery("select news.title from News news")
			// 其实无需设置,默认就是关闭缓存的。
			.setCacheable(false)
			.list();
		for(Object title : titles){
			System.out.println(title);
		}
		System.out.println("-------------------------");
		// 第二次查询,因为没有使用查询缓存,因此会重新发出SQL语句进行查询
		titles  = session.createQuery("select news.title from News news")
			// 其实无需设置,默认就是关闭缓存的。
			.setCacheable(false)
			.list();
		for(Object title : titles)
		{
			System.out.println(title);
		}
		session.getTransaction().commit();
	}

	private void cacheQuery(){
		Session session = sf.getCurrentSession();
		session.beginTransaction();
		List titles  = session.createQuery("select news.title from News news")
			// 开启查询缓存
			.setCacheable(true)
			.list();
		for(Object title : titles)
		{
			System.out.println(title);
		}
		session.getTransaction().commit();
		System.out.println("-------------------------");
		Session sess2 = sf.getCurrentSession();
		sess2.beginTransaction();
		// 第二次查询,使用查询缓存,因此不会重新发出SQL语句进行查询
		titles  = sess2.createQuery("select news.title from News news")
			// 开启查询缓存
			.setCacheable(true)
			.list();
		for(Object title : titles)
		{
			System.out.println(title);
		}
		sess2.getTransaction().commit();
	}

	// 开启查询缓存,但使用iterate()方法查询,因此也不能缓存
	public static void cacheQueryIterator(){
		Session session = sf.getCurrentSession();
		session.beginTransaction();
		Iterator it  = session.createQuery("select news.title from News news")
			// 开启查询缓存
			.setCacheable(true)
			.iterate();
		while(it.hasNext()){
			System.out.println(it.next());
		}
		session.getTransaction().commit();
		System.out.println("-------------------------");
		Session sess2 = sf.getCurrentSession();
		sess2.beginTransaction();
		// 第二次查询,虽然使用了查询缓存,但由于使用iterate()获取查询结果,
		// 因此无法利用查询缓存。
		it  = sess2.createQuery("select news.title from News news")
			// 开启查询缓存
			.setCacheable(true)
			.iterate();
		while(it.hasNext()){
			System.out.println(it.next());
		}
		sess2.getTransaction().commit();
	}
	private void stat(){
		//----------统计查询缓存----------
		long hitCount = sf.getStatistics()
			//查询缓存的名字与HQL语句或SQL语句相同
			.getQueryStatistics("select news.title from News news")
			.getCacheHitCount();
		System.out.println("查询缓存命中的次数:" + hitCount);
	}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值