hibernate的二级缓存

发表时间:2017-05-17 17:30:23 浏览量( 46 ) 留言数( 0 )

学习目标:

1、搭建Hibernate环境

2、理解Hibernate的运行流程

3、能正确运行Hibernate的第一个例子


学习过程:

一、hibernate的二级缓存

缓存可以分为多个级别,如我们前面学习的一级缓存即Session缓存,二级缓存需要显式配置才可启用。使用二级缓存时,与一级缓存不同的Hibernate并不提供二级缓存,而是由第三方提供,二级缓存一般将对象存放在内存中,也可以存放在硬盘中,Hibernate3.2前以EHCache为默认的二级缓存,3.2之后就不再提供默认的二级缓存提供者。

  • EhCache: ,支持缓存类型:内存、硬盘,支持查询缓存,最快的缓存之一,适用于大型、高并发的系统,支持LRU、LFU、FIFO算法。配置文件EhCache.xml

  • Hashtable:,支持缓存类型:内存,支持查询缓存,使用hash表来实现的一简单的内部缓存,没有性能算法优化。不建议使用。

  • OSCache:,支持缓存类型:内存、硬盘,支持查询缓存,广泛使用的高性能的J2EE缓存技术。配置文件oscache.properties

  • SwamCache:,支持缓存类型:集群,不支持查询缓存,简单有效的分布式缓存

  • JBossCache:,支持缓存类型:集群、事务,支持查询缓存,可复制、持续性、事务性的缓存。配置文件treecache.xml

二、一级缓存

hibernate的session,默认已经开启了一级缓存所以我们不需要任何的配置就可以使用一级缓存,一级缓存可以在session没有关闭的时候使用,比如下面的例子,连续执行了两次的get方法,但是查询都是相同id的员工,我们可以看看控制台的输出,只执行了一次查询。

public class Run1 {
	public static void main(String[] args) {
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();

		// 开启一个事务
		session.beginTransaction();

		//一级缓存,在同一个session中多次查询也不会执行多次数据库查询操作的。
		Post post = session.get(Post.class, 3);
		System.out.println(post.getPostName());
		
		//第二次get也不会查询数据库的,直接可以从缓存中获得。除非你clear了
		//session.clear();
		Post post2 = session.get(Post.class, 3);
		System.out.println(post2.getPostName());

		// 提交事物
		session.getTransaction().commit();
		session.close();

		HibernateUtil.getSessionFactory().close();
	}
}

控制台执行如下,可见只是执行了一次的数据库查询。

attcontent/553799e9-2e63-48a3-845a-8b68f542af38.png

三、二级缓存

先看一下下面的执行,开启两个session,如果现在执行,查看控制台肯定会输出两个select,等会我们配置一下二级缓存,你再执行下面的操作,就会发现只执行了一句查询,因为已经缓存再内存中了。代码如下:

public class Run2 {
	public static void main(String[] args) {
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();

		// 开启一个事务
		session.beginTransaction();

		Post post = session.get(Post.class, 3);
		System.out.println(post.getPostName());
		
		// 提交事物
		session.getTransaction().commit();
		session.close();
		
		
		//开启一个新的会话
		Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();

		// 开启一个事务
		session2.beginTransaction();

	
		Post post2 = session2.get(Post.class, 3);
		System.out.println(post2.getPostName());
		
		// 提交事物
		session2.getTransaction().commit();
		session2.close();
		

		HibernateUtil.getSessionFactory().close();
	}
}

下面我们配置二级缓存。

(1)修改hibernate配置hibernate.cfg.xml,启动二级缓存,并指定使用那个二级缓存实现,代码如下:

        <!-- 是否使用缓存 -->
       <!-- 启用二级缓存 -->  
        <property name="cache.use_second_level_cache">true</property> 
        <property name="cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

(2)因为这里我们使用EhCache缓存,所以需要配置EhCache的配置文件,,并输入一下内容,先导入相关的包,pom.xml文件如下:

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-ehcache</artifactId>
			<version>5.2.12.Final</version>
		</dependency>


		<dependency>
			<groupId>org.ehcache</groupId>
			<artifactId>ehcache</artifactId>
			<version>3.1.3</version>
		</dependency>

新建文件EhCache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
	<diskStore path="java.io.tmpdir" />
	<!--
maxElementsInMemory为缓存对象的最大数目,
eternal设置是否永远不过期,timeToIdleSeconds对象处于空闲状态的最多秒数,timeToLiveSeconds对象处于缓存状态的最多秒数
	-->
	<defaultCache maxElementsInMemory="999999999" eternal="true"
		timeToIdleSeconds="10000" timeToLiveSeconds="10000" overflowToDisk="true" />
</ehcache>

(3)在需要启动缓存的映射文件中启动缓存。这里我们以员工employee的映射文件为例子,打开Employee.hbm.xml文件,添加内容如下:

    <class name="com.javadayup.stuormrelate.Post" table="post" catalog="hbm_db">
    
        <!-- 支持二级缓存 -->
        <cache usage="read-write" region="com.javadayup.stuormrelate.Post" />

三、测试

(1)根据id查询

重新之心上面的测试代码,第一次查询的时候会执行对数据库的查询,但是在第二次查询的时候就不会在执行数据库的查询。

(2)执行更新

我们知道二级缓存的信息是暂时保存在web服务器的内存中,所以每一次用户访问的时候都会先查询web服务器的缓存是否有这条信息,如果有就不会查询数据库,现在的问题是如果我们执行了更新,那么web服务器是否会同时更新缓存了,写一个antion方法测试一下,代码如下:

public class Run3 {
	public static void main(String[] args) {
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();

		// 开启一个事务
		session.beginTransaction();

		//一级缓存,在同一个session中多次查询也不会执行多次数据库查询操作的。
		Post post = session.get(Post.class, 3);
		System.out.println(post.getPostName());
		
		// 提交事物
		session.getTransaction().commit();
		session.close();
		
		
		Session session3 = HibernateUtil.getSessionFactory().getCurrentSession();

		// 开启一个事务
		session3.beginTransaction();


		//执行更新操作
		Post post3 = session3.get(Post.class, 3);
		post3.setPostName("我新改版的。");
		session3.update(post3);
		
		// 提交事物
		session3.getTransaction().commit();
		session3.close();
		
		
		
		Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();

		// 开启一个事务
		session2.beginTransaction();


		Post post2 = session2.get(Post.class, 3);
		System.out.println(post2.getPostName());
		
		// 提交事物
		session2.getTransaction().commit();
		session2.close();
		

		HibernateUtil.getSessionFactory().close();
	}
}

你会发现缓存更新了,数据库也更新了,用户查询的时候还是不需要查询数据库的,也就是说hibernate非常智能,在你执行查询的同时他会同时更新缓存的信息。

(3)执行list查询

我们可以试一下查询全部的员工信息,看看有没有把全部员工缓存,action代码如下:

public class Run4 {
	public static void main(String[] args) {
		Session session = HibernateUtil.getSessionFactory().getCurrentSession();

		// 开启一个事务
		session.beginTransaction();

		//不会缓存的
		List<Post> post = session.createQuery("from Post").list();
		
		// 提交事物
		session.getTransaction().commit();
		session.close();
		
		
		
		Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();

		// 开启一个事务
		session2.beginTransaction();
		//不会缓存的
		List<Post> post2 = session2.createQuery("from Post").list();
		
		// 提交事物
		session2.getTransaction().commit();
		session2.close();
		

		HibernateUtil.getSessionFactory().close();
	}
}

你会发现每一次都会搜索数据库,也就是说hibernate对list的查询是不会缓存任何数据的。

(4)清空缓存,如果直接修改数据库的内容,那么web服务器的缓存是不会更新的,也就是说现在用户得到的数据已经是不正确的了,那么如果我们手动更新的数据库的操作,那么你最好提供了一个方法可以清空所有的缓存,以保证用户得到的信息都是正确的。清空缓存的实现代码如下:

/**
 * 清空所有的缓存
 * @author Administrator
 *
 */
public class CleatHibernateUtil  {

	private SessionFactory sessionFactory;

	public void clearSession() {
		Map<String, CollectionMetadata> roleMap = sessionFactory
				.getAllCollectionMetadata();
		for (String roleName : roleMap.keySet()) {
			sessionFactory.evictCollection(roleName);
		}
		Map<String, ClassMetadata> entityMap = sessionFactory
				.getAllClassMetadata();
		for (String entityName : entityMap.keySet()) {
			sessionFactory.evictEntity(entityName);
		}
		sessionFactory.evictQueries();

	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

}

在spring容器配置这个组件,然后在action中调用就可以了。

四、总结

不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧。