Hibernate缓存

    |     2017年1月16日   |   Hibernate   |     0 条评论   |    1484

缓存夹在应用程序和物理数据源之间,用来降低对数据库的访问次数。缓存里是数据源的一份拷贝,运行时先读写缓存,再在特定时刻把两边同步。

介质一般是内存,数据量很大时也会落到硬盘。实现时还要管并发和生命周期,所以既要硬件也要软件。

Hibernate 这边分两层:Session 一级缓存默认开、卸不掉;SessionFactory 二级缓存是可选插件,默认关。查对象时先一级、再二级、最后才打 SQL。

一、缓存

1. Hibernate 缓存机制

Hibernate 缓存包括 Session 缓存和 SessionFactory 缓存。SessionFactory 缓存又分内置和外置。

Session 缓存是内置的,不能卸载,也叫第一级缓存。SessionFactory 内置缓存和 Session 缓存实现上很像:前者是 SessionFactory 一些集合属性里的数据,后者是 Session 集合属性里的数据。内置缓存放映射元数据和预定义 SQL——元数据是映射文件的拷贝,SQL 在初始化阶段根据元数据推出来。内置缓存只读,应用改不了,因此不必和映射文件同步。

SessionFactory 外置缓存是可配置插件,默认不启用。数据是库表拷贝,介质可以是内存或硬盘。外置缓存也叫第二级缓存。

2. 两种缓存

一级缓存默认开启,开发者配置不了、也卸不掉。范围是 Session:同一个 Session 里 get/load 过一次,短时间内再查同一条,直接从缓存拿,不再发 SQL。Session 关掉就没了。

二级缓存是 SessionFactory 级别。SessionFactory 比较重,内存里通常单例。查询结果可以放进二级缓存,即便不是同一个 Session,只要工厂还在,会先查二级,没有再发 SQL。

  • 一级属于事务范围,Hibernate 自己管,程序员不用干预。
  • 二级属于进程范围和集群范围,可以配置,也能动态卸载/加载。

二、Hibernate 缓存原理

1. 一级缓存:Session 的缓存

Session 内置、不能卸载。生命周期通常对应一个数据库事务或一个应用事务。一级缓存里,持久化类的每个实例都有唯一 OID。

2. 二级缓存:SessionFactory 的缓存

SessionFactory 生命周期覆盖整个应用,所以二级缓存是进程范围或集群范围,可能并发,需要并发访问策略来提供事务隔离级别。它是可选插件,默认不启用。

Hibernate 提供 org.hibernate.cache.CacheProvider,充当缓存插件和框架之间的适配器。

什么样的数据适合放二级缓存:

适合:
1) 很少被修改的数据
2) 不是很重要的数据,允许出现偶尔并发
3) 不会被并发访问的数据
4) 常量数据

不适合:
1) 经常被修改的数据
2) 绝对不允许并发访问的数据,如财务数据
3) 与其他应用共享的数据

3. 延迟加载要解决的两个问题

Hibernate Session 是 java.sql.Connection 的一层封装,一个 Session 对应一个 Connection。

  • HTTP 请求结束后正确关闭 Session(过滤器负责关)。
  • 延迟加载必须保证是同一个 Session(Session 绑在 ThreadLocal 上)。

4. 按 ID 查找时怎么用缓存

  1. 先从 Session 一级缓存查
  2. 没有,且配置了二级缓存,再从二级查
  3. 都没有,再查数据库,把结果按 ID 放入缓存

删除、更新、增加时,同时更新缓存。

5. 一级缓存与二级缓存对比

对比项 一级缓存 二级缓存
存放数据的形式 相互关联的持久化对象 对象的散装数据
缓存的范围 事务范围,每个事务都有单独的一级缓存 进程范围或集群范围,同一进程或集群内所有事务共享
并发访问策略 每个事务单独一份,无并发,无须策略 多事务会同时访问相同数据,必须提供并发策略以保证隔离级别
数据过期策略 对象不会过期,除非应用显式清空 必须提供过期策略:内存最大数目、最长存活、最长空闲
物理介质 内存 内存和硬盘;内存数目达到 maxElementsInMemory 后其余写入硬盘缓存
软件实现 包含在 Hibernate Session 实现中 第三方提供,Hibernate 只给适配器
启用方式 通过 Session 做保存、更新、删除、加载、查询就会启用;批量操作不想走缓存可直接用 JDBC API 可按单个类或集合配置;经常读、很少改的类才值得开;没配置就不会进二级缓存
用户管理方式 内存有限,要用检索策略限制加载数量;Session.evict() 可清空指定对象,但不推荐 选哪些类开二级缓存并设并发策略;选适配器并设过期策略。SessionFactory.evict() 也能清空,但不推荐

三、缓存机制应用

1. 一级缓存的管理

evict(Object obj)   // 将指定持久化对象从一级缓存清除,释放内存,对象从持久化变为脱管
clear()             // 清除一级缓存中所有持久化对象
contains(Object obj)// 判断指定对象是否在一级缓存中
flush()             // 刷新一级缓存,使之与数据库同步

2. 一级缓存应用

save() 之后对象会进 Session 缓存;get() / load() 从库取出后也会进缓存。HQL、QBC 查出来的同样如此。

@Test
public void testQueryOneCache() {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();
    Query query = session.createQuery("FROM Student");
    List<Student> list = query.list();
    for (Student stu : list) {
        System.out.println(stu);
        // if (stu.getNumber() != 1) {
        //     session.evict(stu); // 清除指定缓存对象
        // }
    }
    Student student = (Student) session.get(Student.class, 1);
    System.out.println(student);
    // 清除 session 缓存两种方式:clear()、关闭 session
    session.clear();

    System.out.println("--------------------");
    Student stu2 = (Student) session.get(Student.class, 1);
    System.out.println(stu2);

    session.getTransaction().commit();
}

3. 二级缓存的管理

sessionFactory.evict(Customer.class, new Integer(1));
// evict(Class, Serializable) 将某个类指定 ID 的对象从二级缓存清除

sessionFactory.evict(Customer.class);
// evict(Class) 将指定类的所有持久化对象从二级缓存清除

sessionFactory.evictCollection("Customer.orders");
// evictCollection(String) 将指定类的指定集合从二级缓存清除

4. 二级缓存的配置

常用插件:

插件 Provider
EHCache org.hibernate.cache.EhCacheProvider
OSCache org.hibernate.cache.OSCacheProvider
SwarmCache org.hibernate.cache.SwarmCacheProvider
JBossCache org.hibernate.cache.TreeCacheProvider

四、二级缓存以 EHCache 为例

1. 导入 ehcache 包

ehcache-core-2.4.3.jar
hibernate-ehcache-4.3.11.Final.jar
slf4j-api-1.6.1.jar

2. 创建 ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
  <!-- 缓存对象超过指定数量后,落到磁盘的地址 -->
  <diskStore path="E:/ehcache"/>

  <!-- 默认 cache:没有对应特定区域时使用 -->
  <defaultCache maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="300"
                timeToLiveSeconds="600"
                overflowToDisk="false"/>
  <!-- 指定区域 cache:name 对应 Hibernate 中的区域名 -->
  <cache name="com.ittx.hibernate001.model.Student"
                eternal="false"
                maxElementsInMemory="100"
                timeToIdleSeconds="1200"
                timeToLiveSeconds="1200"
                overflowToDisk="false">
  </cache>
</ehcache>

参数含义:name 区域名;maxElementsInMemory 内存最大数目;maxElementsOnDisk 磁盘最大数目;eternal 是否永不过期;overflowToDisk 是否溢出到硬盘;timeToIdleSeconds 空闲多少秒后销毁;timeToLiveSeconds 存活多少秒后销毁;memoryStoreEvictionPolicy 缓存算法。

常见算法三种:

  • LRU(Least Recently Used):新对象替换掉最近很少使用的对象(默认)
  • LFU(Least Frequently Used):替换命中率较低的对象
  • FIFO(First In First Out):替换最早进入二级缓存的对象

3. hibernate.cfg.xml

<!-- 高速缓存提供程序 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- 设置二级缓存插件 EHCache 的 Provider 类 -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!-- enable second level cache and query cache -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 启动查询缓存:findall()、list()、Iterator()、createCriteria()、createQuery() 的结果集要缓存必须配这项 -->
<property name="hibernate.cache.use_query_cache">true</property>
<!-- 指定缓存配置文件位置 -->
<property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
<!-- 强制 Hibernate 以更人性化的格式将数据存入二级缓存 -->
<property name="hibernate.cache.use_structured_entries">true</property>
<!-- Hibernate 将收集有助于性能调节的统计数据 -->
<property name="hibernate.generate_statistics">true</property>

4. 配置 model.hbm.xml

在 class 标签下添加:

<cache usage="read-write"/>

5. 测试代码

@Test
public void testQueryCacheTwo() {
    Session session = HibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();

    System.out.println("第一次查询Student");
    Query q = session.createQuery("from Student");
    q.setCacheable(true);
    q.list();
    System.out.println("放进二级缓存");

    session.getTransaction().commit();

    session = HibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();

    System.out.println("第二次查询Student,将不会发出sql");
    q = session.createQuery("from Student");
    q.setCacheable(true);
    q.list();

    session.getTransaction().commit();

    // 配置文件打开 generate_statistics 后,可以得到二级缓存命中次数等数据
    Statistics s = HibernateUtil.getSessionFactory().getStatistics();
    System.out.println(s);
    System.out.println("put:" + s.getSecondLevelCachePutCount());
    System.out.println("hit:" + s.getSecondLevelCacheHitCount());
    System.out.println("miss:" + s.getSecondLevelCacheMissCount());
}

关于查询缓存:一级与二级只能缓存实体对象;查询缓存可以缓存结果集(含普通属性)。只有经常使用同样参数的查询才划算,生命周期直到属性被修改为止。对实体结果集,查询缓存只缓存 id。

<!-- 开启查询缓存,默认是关闭的 -->
<property name="hibernate.cache.use_query_cache">true</property>
// 程序中应开启:
Query query = session.createQuery("select s.name from Student s");
query.setCacheable(true); // 开启查询缓存

一句话总结:按 ID 查对象时先 Session 一级、再 SessionFactory 二级、最后才打 SQL;二级缓存用 EHCache 时要同时开 use_second_level_cache,查询缓存还要 setCacheable(true)。

转载请注明来源:Hibernate缓存
本文链接地址:https://ai.zhousir.top/?p=1895
回复 取消