一、一级缓存
一级缓存又称为“Session缓存”或者“会话级缓存”,通过Session从数据库查询实体时,会把实体在内存中存储起来,下一次查询同一实体时不再再次执行sql语句查询数据库,而是从内存中获取。一级缓存的生命周期和Session相同。一级缓存是无法取消的。
1.一级缓存中的数据可适用范围在当前会话之类,例如用同一个session查询两次user表和同两个session查询俩次user表,查询数据库的次数是不一样的。首先,像这样查询两次,可以看见打印的sql语句只有一次,因为第二次是直接从当前session缓存中取的。
但是把注释去掉,就会发现,sql打印了两次,因为他执行了两次数据库的查询操作。
/** * 一级缓存 */ @Test public void cacheTest() { Tb_User u = session.get(Tb_User.class, "1"); System.out.println(u.getName()); //session = sessionFactory.openSession(); u = session.get(Tb_User.class, "1"); System.out.println(u.getName()); }
2.evict 、clear
evict方法:清除一级缓存中的指定对象;
clear方法:清除一级缓存的所有对象。
这段代码打印的sql语句打印了两次,结果很明显。
@Test public void cacheTest() { Tb_User u = session.get(Tb_User.class, "1"); //清除一级缓存中的指定对象 //session.evict(u); //清楚一级缓存中的所有对象 session.clear(); u = session.get(Tb_User.class, "1"); }
3.query.list | query.getResultList
这两个方法是一样的,但是现在我看提示说query.list是废弃的。他们都不使用缓存,执行下面这段代码,可以很明确的看见打印了两次sql,这两个方法都是不使用缓存的,但是会把查询出来的数据存入缓存中。
/** * 一级缓存 */ @Test public void cacheTest() { Query query = session.createQuery("From Tb_User"); List<Tb_User> list = query.getResultList(); for(Tb_User u : list){ System.out.println(u.getName()); } list = query.getResultList(); for(Tb_User u : list){ System.out.println(u.getName()); } }
4.query.iterate()
若用迭代器Iterator,从打印的sql来看,他只查询了id,然后还是打印了Name,因为query.iterate()用到了缓存,他也会把查询出来的数据存入缓存中。
/** * 一级缓存 */ @Test public void cacheTest() { Query query = session.createQuery("From Tb_User"); List<Tb_User> list = query.getResultList(); for(Tb_User u : list){ System.out.println(u.getName()); } Iterator it = query.iterate(); while(it.hasNext()){ Tb_User u = (Tb_User)it.next(); System.out.println(u.getName()); } }
query.iterate()怎么使用缓存?
把代码query.getResultList()相关的几行注释掉,执行后,可以看见打印的sql语句,先查询了id,然后根据id用where去数据挨着查询出来,我的数据库里有3条数据,所有一共查询了4次数据库。
query.iterator会先去查询数据库中的id,然后根据id,先在缓存中查找是否有相应的数据,有则直接用,没有则从数据库中找。
解释下前面的代码结果:
若query.iterator遍历方法之前执行了list方法,list方法查询出来的数据被缓存了,但list方法不使用缓存,因此再次执行list时会重新查询数据库,而iterator方法只从数据库查询id,这时每个id在内存中都有对应的值,所以Name属性是从内存中取出来的。
若query.iterator遍历方法之前没有执行了list方法,那么此时数据并没有缓存在内存中,那么iterator方法依然会先查询id,在遍历时,发现内存中根据id找不到该数据,于是就发送sql到数据库中找,此时Name属性就是真的从数据库中现场查找出来的了。
若query.iterator遍历方法之前执行了query.iterator遍历方法,那么结果和第一种情况类似,他会先依次在数据库中查找,缓存到内存,然后第二次执行时,只查找了id值,其余的在内存中找到。
@Test public void cacheTest() { Query query = session.createQuery("From Tb_User"); // List<Tb_User> list = query.getResultList(); // for(Tb_User u : list){ // System.out.println(u.getName()); // } Iterator it = query.iterate(); while(it.hasNext()){ Tb_User u = (Tb_User)it.next(); System.out.println(u.getName()); } // it = query.iterate(); // while(it.hasNext()){ // Tb_User u = (Tb_User)it.next(); // System.out.println(u.getName()); // } }
二、二级缓存
hibernate的二级缓存又称为“全局缓存”,“应用级缓存”,二级缓存中的数据可适用方位是当前应用的所有会话,不随session关闭而关闭,他是可插拔式的缓存。二级缓存需要其他的jar包,自己去配置。配置步骤:
①.添加二级缓存对应的jar包
jar包在 hibernate-release-5.2.1.Finalliboptionalehcache 中有三个ehcache-2.10.1.jar,hibernate-ehcache-5.2.1.Final.jar,slf4j-api-1.7.7.jar都需要添加进去
②.在hibernate的配置文件中添加Provider类的描述,即hibernate.cfg.xml中 添加属性
<!-- 开启二级缓存 --> <property name="cache.use_second_level_cache">true</property> <!-- 开启查询缓存 --> <property name="hibernate.cache.use_query_cache">true</property> <!-- 配置RegionFactory为Ehcache的RegionFactory --> <property name="cache.region.factory_class">org.hibernate.cache.EhCacheRegionFactory</property>
③.添加二级缓存的属性配置文件
在 hibernate-release-5.2.1.Finalprojecthibernate-ehcachesrc est esources 目录下,可以找到ehcache.xml的配置文件,复制粘贴到hibernate.cfg.xml统一目录下,打开xml文件看到这么一段,注释写得很清楚,每个属性干嘛的。这个配置是默认的缓存策略。
<!--Default Cache configuration. These will applied to caches programmatically created through the CacheManager. The following attributes are required for defaultCache: maxInMemory - Sets the maximum number of objects that will be created in memory eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element is never expired. timeToIdleSeconds - Sets the time to idle for an element beforeQuery it expires. Is only used if the element is not eternal. Idle time is now - last accessed time timeToLiveSeconds - Sets the time to live for an element beforeQuery it expires. Is only used if the element is not eternal. TTL is now - creation time overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache has reached the maxInMemory limit. --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" />
④.在需要被缓存的表所对应的映射文件中添加<cache />标签,作为class的子节点。usage属性表示的是事务模式,还有include属性,设置是否加载延迟加载的属性,region属性是Ehcache配置中,可以为表单独增加缓存策略,否则全部都是默认策略。
<cache usage="read-only" region="Tb_User"/>
若需要单独配置缓存策略,需要在ehcache.xml中加上一段,表示Tb_User使用当前的缓存策略,不用默认的。(不是必须配置的)
<cache name="Tb_User" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" />
此时基本的配置就完成了,依然用第一个例子做测试
/** * 一级缓存 */ @Test public void cacheTest() { Tb_User u = session.get(Tb_User.class, "1"); System.out.println(u.getName()); //session = sessionFactory.openSession(); u = session.get(Tb_User.class, "1"); System.out.println(u.getName()); }
此时,无论是否打开一个新的session,都只会打印一条sql语句,若把cache.use_second_level_cache 属性值设为false,那么就可以关闭二级缓存。
三、一级缓存和二级缓存比较
补充:在映射文件中的cache标签的usage有四种属性
read-only: 对于永远不会被修改的数据可以采用这种并发访问策略,它的并发性能是最高的。但必须保证数据不会被修改,否则就会出错,使用场景可以是OA系统中比较常用的字典表/枚举表
nonstrict-read-write: 非严格读写不能保证缓存与数据库中数据的一致性,如果存在两个事务并发地访问缓存数据的可能,则应该为该数据配置一个很短的过期时间,以减少读脏数据的可能。对于极少被修改,并且可以容忍偶尔脏读的数据可以采用这种并发策略。
read-write: 对于经常被读但很少修改的数据可以采用这种策略,它可以防止读脏数据。
transactional:它可以防止脏读和不可重复读这类的并发问题。
transactional策略是事务隔离级别最高,read-only的隔离级别最低。事务隔离级别越高,并发性能就越低。