缓存(Cache):计算机领域的通用概念。它介于应用程序和永久性数据存储源(如硬盘上的数据库或文件)之间。其作用是减少应用程序直接读写永久性数据存储源的频率,从而提高应用程序的运行性能。缓存中的数据是数据存储源中的数据的拷贝。缓存的物理介质通常是内存。
Hibernate中提供了两个级别的缓存:
- 第一级别的缓存是Session级别的缓存,它属于事务范围内的缓存。这一级别的缓存由Hibernate进行管理。
- 第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围内的缓存。
SessionFactory级别的缓存
SessionFactory的缓存分为两类:
- 内置缓存:Hibernate自带的,不可卸载。通常在Hibernate初始化阶段,Hibernate会把映射元数据和预定义的SQL语句放到SessionFactory的缓存中,映射元数据是映射文件(.hbm.xml)中的数据的复制,该内置缓存是只读的。
- 外置缓存(二级缓存):一个可配置的缓存插件,在默认情况下,SessionFactory不会启动这个缓存插件,外置缓存中的数据是数据库数据的复制,外置缓存的物理介质可以是内存或硬盘。
适合放入二级缓存的数据:
- 很少被修改
- 不是很重要的数据,允许出现偶尔的并发问题。
不适合放入二级缓存中的数据
- 经常被修改
- 财务数据,决不允许出现并发问题。
- 与其它应用程序共享的数据。
二级缓存的并发访问策略
两个并发的事务同时访问持久层的缓存的相同的数据时,也有可能出现各种各样的并发问题。
二级缓存可以设定为以下四种类型的并发访问策略,每一种访问策略对应一种事务的隔离级别。
- 非严格读写(Nonstrict-read-write):不保证缓存与数据库中的数据的一致性,提供Read-uncommited级别的数据库隔离级别。对于极少修改而且允许脏读的数据,可以采用这种策略。
- 读写型(Read-write):提供Read-commited数据隔离级别。对于经常读但很少修改的数据,可以使用这种隔离类型,因为它可以防止脏读。
- 事务性(Transactional):仅在受管理环境下适用,它提供了Repeatable-read事务隔离级别。对于经常读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读。
- 只读型(Read-only):提供Serializable数据库隔离类型,对于从来不会被修改的数据,可以采用这种访问策略。
配置进程范围内的二级缓存
- 选择合适范围的缓存插件:EHCache(jar包和配置),并编译器配置文件。
- 在Hibernate的配置文件中启用二级缓存,并指定EHCache对应的缓存适配器。
//启动Hibernate的二级缓存
<property name="cache.use_second_level_cache">true</property>
//配置Hibernate二级缓存使用的产品
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
//配置对那些类使用Hibernate的二级缓存
<class-cache usage="read-write" class="com.hibernate.unionsubclass.Student"></class-cache>
也可以在.hbm.xml类中配置二级缓存。
- 选择需要使用二级缓存的持久化类,设置它的二级缓存的并发访问策略
- <class>元素的cache子元素表明Hibernate会缓存对象的简单属性,但不会缓存集合属性,如果希望缓存集合属性,必须在<set>元素中加入<cache>子元素。
- 在hibernate配置文件中通过<class-cache/>结点配置使用缓存。
设置集合缓存
我们看下面的代码
SessionFactory sessionFactory = null;
Configuration cfg = new Configuration().configure();
sessionFactory = cfg.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Category category = session.get(Category.class, 42);
System.out.println(category.getItems().size());
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Category category2 = session.get(Category.class, 42);
System.out.println(category2.getItems().size());
transaction.commit();
session.close();
sessionFactory.close();
运行后发现生成了四条SQL语句。此时我们给Category类添加入二级缓存。
<class-cache usage="read-write" class="com.hibernate.n2n.Category"></class-cache>
发现少了一条SQL语句,但在获取items的时候还是两条SQL语句,此时我们需要将Item类添加到二级缓存中。
<collection-cache usage="read-write" collection="com.hibernate.n2n.Category.items"></collection-cache>
此时发现又是四条SQL语句。因为我们仅仅缓存了items集合类,没有缓存其中的类型。
<class-cache usage="read-write" class="com.hibernate.n2n.Item"></class-cache>
这个时候控制台只输出了两条SQL语句。
集合缓存也可以在.hbm.xml中配置
<set name="items" table="category_item">
<cache usage="read-write"/>
<key>
<column name="Category_Id"/>
</key>
<many-to-many class="Item" column="Item_Id"/>
</set>
注意,还需要在集合包含的类中配置缓存。
ehcache.xml文件
<diskStore path="java.io.tmpdir"/>
diskStore表示磁盘的存储路径。当有大量的数据的时候我们需要设置临界值,如果低于这个临界值,则将数据存入到内存中。高于这个数据则放到磁盘上。该路径表示磁盘的位置。
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
默认的存储策略。
<cache name="sampleCache1"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
设置具体的命名缓存数据的策略,每个命名缓存代表一个缓存区域。
缓存区域(region):一个具有名称的缓存块,可以给每个缓存块设置不同的缓存策略。如果没有设置任何缓存区域,则所有的缓存对象将使用默认的缓存策略。
配置项解释:
name:设置缓存的名字,它的取值为类的全限定名或类的集合名字。
maxElementInMemory:在内存中最多可以存储对象的数量。
eternal:设置对象在缓存中是否为永久的,true表示永不过期。此时将忽略timeToIdleSeconds和timeToLiveSecond,默认值为false。
timeToIdleSeconds:设置对象空闲的最长时间,单位是秒。如果超过这个时间,对象将从缓冲中清除。
timeToLiveSecond:设置对象的最长生存时间,超过这个时间,对象过期。如果值为0表示对象可以永久的存在缓存中。
overflowToDisk:设置内存中的缓存中的对象的数量达到上限后,是否把溢出的对象写到基于硬盘的缓存中。
查询缓存
当执行以下的代码
Query query = session.createQuery("from Category ");
List<Category>categories = query.list();
System.out.println(categories.size());
categories = query.list();
System.out.println(categories.size());
我们会发现执行了两条SQL。说明缓存没有起作用。
此时我们需要设置查询缓存。
默认情况下设置的缓存对HQL和QBC查询是无效的。通过以下方式配置
在Hibernate配置文件中声明开启查询缓存
<property name="cache.use_query_cache">true</property>
调用Query或Criteria的setCacheable方法,设置为true。
query.setCacheable(true);
注意:查询缓存依赖二级缓存。
时间戳缓存
时间戳缓存区域存放了对于查询结果相关的表进行插入,更新,删除操作的时间戳。Hibernate通过时间戳缓存区域判断缓存的查询结果是否过期,其运行过程如下:
- T1时刻执行查询操作,把查询结果放在QueryCache区域,记录该区域的时间戳为T1。
- T2时刻对查询结果相关的表进行更新操作,Hibernate把T2时刻放在UpdateTimestampCache区域。
- T3时刻在执行查询结果前,先比较QueryCache区域的时间戳和UpdateTimestampCache区域的时间戳,若T2>T1,那么丢弃原先存放在QueryCache区域的查询结果,重新懂啊数据库中查询数据,再把结果存放到QueryCache区域;若T2<T1,直接从QueryCache区域取出查询结果。
Query接口的iterator方法
当遍历访问结果集的时候,该方法先到Session缓存及二级缓存中查看是否存在特定OID的对象,如果存在,就直接返回该对象。如果不存在该对象,则通过相应的SQL select语句到数据库中加载特定的实体类对象。
大多数情况下应使用list方法执行查询操作。iterator方法仅在满足以下条件的场景中适合使用,可以稍微提高查询的性能:
- 要查询的数据表中含有大量的字段
- 启用了二级缓存,且二级缓存可能已经包含了待查询的对象。
网友评论