Hibernate学习笔记--------3.缓存

时间:2023-03-08 17:38:30

一、一级缓存

一级缓存又称为“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.Final\lib\optional\ehcache 中有三个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.Final\project\hibernate-ehcache\src\test\resources 目录下,可以找到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,那么就可以关闭二级缓存。

三、一级缓存和二级缓存比较

Hibernate学习笔记--------3.缓存

补充:在映射文件中的cache标签的usage有四种属性

read-only: 对于永远不会被修改的数据可以采用这种并发访问策略,它的并发性能是最高的。但必须保证数据不会被修改,否则就会出错,使用场景可以是OA系统中比较常用的字典表/枚举表
nonstrict-read-write: 非严格读写不能保证缓存与数据库中数据的一致性,如果存在两个事务并发地访问缓存数据的可能,则应该为该数据配置一个很短的过期时间,以减少读脏数据的可能。对于极少被修改,并且可以容忍偶尔脏读的数据可以采用这种并发策略。
read-write: 对于经常被读但很少修改的数据可以采用这种策略,它可以防止读脏数据。
transactional:它可以防止脏读和不可重复读这类的并发问题。

transactional策略是事务隔离级别最高,read-only的隔离级别最低。事务隔离级别越高,并发性能就越低。