Hibernate part 17:二级缓存

nameFay 2015-08-31

Session级别的缓存,事务范围的

SessionFactory级别的缓存,进程范围的

SessionFactory缓存:

内置:Hibernate自带的,只能缓存一些配置的SQL语句,如命名查询配置在*.hbm.xml中的SQL语句

外置:需要配置第三方插件使用,自己内有实现

二级缓存的结构


Hibernate part 17:二级缓存
 

二级缓存提供商:

EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持
OpenSymphony `:可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存

二级缓存并发访问策略:

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

二级缓存的配置:

1、ehcache依赖jar包:ehcache-1.5.0.jar、commons-logging.jar、backport-util-concurrent.jar

2、在hibernate.cfg.cml配置文件中配置使用二级缓存

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

3、 配置二级缓存提供商

<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

 4、配置二级缓存的并发策略

位置一:*.hbm.xml中

<hibernate-mapping>
	<class name="rock.lee.bean.Customer" table="customer" catalog="test" >
		<cache usage="read-write"/>

 位置二:hibernate.cfg.xml

<class-cache usage="read-write" class="rock.lee.bean.Customer"/>
		<class-cache usage="read-write" class="rock.lee.bean.Order"/>
		<collection-cache usage="read-write" collection="rock.lee.bean.Customer.orders"/>

 5、配置ehcache.xml,解压ehcache-1.5.0.jar中获取

配置完成

案例一:证明二级缓存是存在的

@Test
	public void test01() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();
		//SQL
		Customer  c1 = (Customer) session.get(Customer.class, 1);
		System.out.println(c1);
		//从一级缓存中获取
		Customer  c2 = (Customer) session.get(Customer.class, 1);
		System.out.println(c2);
		transaction.commit();//session close
		
		session = HibernateUtils.getCurrentSession();
		 transaction = session.beginTransaction();
		 //从二级缓存中获取
		Customer  c3 = (Customer) session.get(Customer.class, 1);
		System.out.println(c3);
		
		transaction.commit();
	}

 修改hibernate.cfg.xml关闭二级缓存, 再次执行查询

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

案例二:类级别缓冲区的散装数据结构

在Customer类中的toString()中调用Object的toString()打印地址

@Override
	public String toString() {
		return "Customer [id=" + id + ", name=" + name + ", city=" + city+"]"
				+ super.toString();
	}

 还是运行案例一的程序,输出结果

Customer [id=1, name=林允儿, city=SH]rock.lee.bean.Customer@3003e926
Customer [id=1, name=林允儿, city=SH]rock.lee.bean.Customer@3003e926//来自一级缓存的对象
Customer [id=1, name=林允儿, city=SH]rock.lee.bean.Customer@4cf221f6//来自二级缓存的对象

案例三:get/load可以对二级缓存读写,Query的list对二级缓存只能写不能读

@Test
	public void test02() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();
		//查询SQL,将数据写入二级缓存
		List<Customer> list = session.createQuery("from Customer").list();
		System.out.println(list.size());
                transaction.commit();// session close
		session = HibernateUtils.getCurrentSession();
		transaction = session.beginTransaction();
		//不能读取二级缓存数据,执行SQL查询,将数据写入二级缓存
		list = session.createQuery("from Customer").list();
		System.out.println(list.size());

                transaction.commit();// session close
		session = HibernateUtils.getCurrentSession();
		transaction = session.beginTransaction();
		//从二级缓存中获取
		Customer c1 = (Customer) session.get(Customer.class, 1);
		System.out.println(c1);

		transaction.commit();
	}

案例四:集合级别缓冲区的存在

@Test
	public void test03() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		Customer c1 = (Customer) session.get(Customer.class, 1);
		System.out.println(c1.getOrders());
		
		transaction.commit();
		session = HibernateUtils.getCurrentSession();
		session.beginTransaction();
		
		Customer c2 = (Customer) session.get(Customer.class, 1);
		//通过二级换获得Order数据
		System.out.println(c2.getOrders());
		
		transaction.commit();
	}

案例五:集合级别缓冲区依赖类级别缓冲区

在hibernate.cfg.xml中注释掉Order类级别缓存

<!-- 		<class-cache usage="read-write" class="rock.lee.bean.Order" /> -->

 执行案例四同样的代码,如果Order类级别缓冲区关系,集合缓冲区也无法使用

@Test
	public void test03() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		Customer c1 = (Customer) session.get(Customer.class, 1);
		System.out.println(c1.getOrders());
		
		transaction.commit();
		session = HibernateUtils.getCurrentSession();
		session.beginTransaction();
		
		Customer c2 = (Customer) session.get(Customer.class, 1);
		//通SQL获得数据
		System.out.println(c2.getOrders());
		
		transaction.commit();
	}

 集合级别的缓冲区只会缓存id,然后去类级别缓冲区里查询数据

案例六:一级缓存数据自动同步到二级缓存

@Test
	public void test04() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		Customer c1 = (Customer) session.get(Customer.class, 1);
		System.out.println(c1);
		//快照比对后自动更新,并且同步到二级缓存
		c1.setCity("GD");
		
		transaction.commit();
		session = HibernateUtils.getCurrentSession();
		session.beginTransaction();
		
		//从二级缓存中获取修改后的数据
		Customer c2 = (Customer) session.get(Customer.class, 1);
		System.out.println(c2);
	}

 案例七:将二级换数据保存到硬盘

保存到硬盘中的位置在ehcache.xml中配置

#默认目录C:\Users\ADMINI~1\AppData\Local\Temp\
  <diskStore path="java.io.tmpdir"/>

  将D:\ehcache目录作为缓存目录,maxElementsInMemory配置为3

<diskStore path="D:\ehcache"/>
 <defaultCache
            maxElementsInMemory="3"  内存中最大元素数量(当内存对数量超过这个数,才会缓存到硬盘)
            eternal="false"  缓存数据是否永久有效 
            timeToIdleSeconds="120" 设置对象空闲最长时间 ,超过时间缓存对象如果没用,回收
            timeToLiveSeconds="120"  设置对象存活最长时间, 无论对象是否使用,到时间回收 
            overflowToDisk="true"  当内存缓存数据达到上限,是否缓存到硬盘 
            maxElementsOnDisk="10000000"   硬盘上最大缓存对象数量 
            diskPersistent="false"   当jvm结束时是否持久化对象 true false 默认是false
            diskExpiryThreadIntervalSeconds="120" 专门用于清除过期对象的监听线程的轮询时
            memoryStoreEvictionPolicy="LRU" 
            />
 由于JUnit执行后会System.exit(0),所以磁盘文件大小为0,通过断点,让程序有时间将数据保存到磁盘,加休眠也可以
@Test
	@SuppressWarnings("unchecked")
	public void test05() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		System.out.println(System.getProperty("java.io.tmpdir"));
		
		List<Order> list = session.createQuery("from Order").list();
		System.out.println(list.size());
		
		transaction.commit();
	}

 

 案例八:更新时间戳缓冲区

时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳.  Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期

@Test
	public void test06() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		Customer c1 = (Customer) session.get(Customer.class, 2);
		// 不能这么修改,一级缓存会自动同步到二缓存
		// c1.setCity("BJ");

		session.createSQLQuery("update customer set city='BJ' where id=1").executeUpdate();

		transaction.commit();
		session = HibernateUtils.getCurrentSession();
		transaction = session.beginTransaction();

		//判断类别缓冲区中的时间戳和时间戳缓冲区的是否一样,不一样,重新发送SQL查询
		c1 = (Customer) session.get(Customer.class, 1);
		System.out.println(c1);

		transaction.commit();

	}

 案例九:Query接口的iterator(),返回迭代器代理对象,数据中只有id,在访问每个元素时,先查找二级缓存,如果找不到,生成SQL语句

@Test
	public void test07() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		List<Order> list = (List<Order>) session.createQuery("from Order where id < 5").list();
		
		transaction.commit();
		session = HibernateUtils.getCurrentSession();
		transaction = session.beginTransaction();
		
		//不在缓存里的,会执行SQL查询
		Iterator<Order> iterate = session.createQuery("from Order where id <10").iterate();
		while (iterate.hasNext()) {
			Order o = (Order) iterate.next();
			System.out.println(o.getId()+"-->"+o.getAddress());
		}

	}

案例十:查询缓存

二级缓存查询结果,比如以OID作为key ,以对象作为Value 进行缓存 , 查询缓存 以SQL语句为key ,以查询结果作为Value

在hibernate.cfg.xml中配置开启查询缓存

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

 代码中也要启用查询缓存

@Test
	public void test08() {
		Session session = HibernateUtils.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		Query query = session.createQuery("select name from Customer");
		query.setCacheable(true);// 启用查询缓存
		List<String> list = query.list();

		transaction.commit();
		session = HibernateUtils.getCurrentSession();
		transaction = session.beginTransaction();

		//从查询缓存中获取数据,不会有SQL产生
		query = session.createQuery("select name from Customer");
		query.setCacheable(true);// 启用查询缓存
		List<String> lst = query.list();
		System.out.println(lst);

	}

 案例十一:二级缓存性能检测

在hibernate.cfg.xml配置开启性能检测

<property name="hibernate.generate_statistics">true</property>

 二级缓存命中率

// 二级缓存的性能监测
	public void test09() {
		Configuration configuration = new Configuration().configure();
		SessionFactory sessionFactory = configuration.buildSessionFactory();
//		 sessionFactory.getStatistics().setStatisticsEnabled(true);
		Session session = sessionFactory.getCurrentSession();
		Transaction transaction = session.beginTransaction();

		// 放入二级缓存
		Customer customer1 = (Customer) session.get(Customer.class, 1); // 不在一级缓存,不在二级缓存  miss+1
		Customer customer2 = (Customer) session.get(Customer.class, 2); // 不在一级缓存,不在二级缓存 miss+1
		Customer customer3 = (Customer) session.get(Customer.class, 2); // 在一级缓存找到,hit和miss都不+1
		
		transaction.commit();
		session = sessionFactory.getCurrentSession();
		transaction = session.beginTransaction();

		// 读二级缓存
		Customer customer4 = (Customer) session.get(Customer.class, 1); // 在二级缓存找到hit+1
		Customer customer5 = (Customer) session.get(Customer.class, 2); // 在二级缓存找到 hit+1
		

		transaction.commit();
		// 监测
		System.out.println("命中次数: " + sessionFactory.getStatistics().getSecondLevelCacheHitCount()); // 命中次数
		System.out.println("MISS次数:" + sessionFactory.getStatistics().getSecondLevelCacheMissCount()); // 丢失次数

	}

相关推荐