美文网首页
Hibernate二级缓存

Hibernate二级缓存

作者: BlueSkyBlue | 来源:发表于2020-03-25 17:18 被阅读0次

缓存(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方法仅在满足以下条件的场景中适合使用,可以稍微提高查询的性能:

  • 要查询的数据表中含有大量的字段
  • 启用了二级缓存,且二级缓存可能已经包含了待查询的对象。

相关文章

网友评论

      本文标题:Hibernate二级缓存

      本文链接:https://www.haomeiwen.com/subject/reeayhtx.html