`

框架学习之Hibernate 第九节 缓存原理与分析

 
阅读更多

1. 一级缓存:Session中共享

测试:可以通过查看输出的 select 语句的数目来测试Hibernate中的Session级的缓存

使用缓存的三种操作:放,取,删

会从缓存中拿数据的方法:get,load,iterate   [id为native(自增长的形式的话,save方法是不会放入到一级缓存或者二级缓存中的)]

会向缓存中放入数据的方法:save,update,saveOrUpdate,get,load,iterate,lock(还没有使用过,lock是把一个没有更改过的脱管状态的对象变成持久状态)

删除缓存内容的方法:evict(清除某个数据),clear(清除所有  s.clear()  清除一级缓存中的内容)

缺点:Hibernate的缓存内容默认是不会清除的,所以,如果数据内容太大时会造成缓存溢出!

还有一个缺点是:作用范围比较小,只在session关闭之前有效

模拟缓存实现:Map

package cn.itcast.hibernate;

import java.util.HashMap;
import java.util.Map;

import cn.itcast.hibernate.domain.User;

public class CacheDemo {

    static Map cache = new HashMap();
    
    public static void main(String[] args) {
        User u = getUser(1);//第一次是从数据库中取出来
        User u1 = getUser(1);//第二次是直接从缓存中取数据
    }

    public static void update(User user){
        updateDB(user);
        String key = User.class.getName() + user.getId();
        cache.remove(key);
    }

    public static User getUser(int id) {
        String key = User.class.getName() + id;
        User user = (User) cache.get(key);
        if (user != null)
            return user;
        user = getFromDB();
        cache.put(key, user);
        return user;
    }
    
    private static void updateDB(User user) {
        // TODO Auto-generated method stub
    }
    private static User getFromDB() {
        // TODO Auto-generated method stub
        return null;
    }
}

 

 

2. 二级缓存:SessionFactory级共享

实现为可插拔,通过修改cache.provider_class参数来改变;

hibernate内置了对EhCache,OSCache,TreeCache,SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入Hibernate不支持的缓存实现。

Hibernate默认的二级缓存支持类:hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider  采用的是Hashtable,但是性能并不是很好

如果要设置某个类的对象存入缓存中有两种方法:

①在hibernate.cfg.xml中加入:

<class-cache class="className" usage="read-only"/>

②在映射文件的class元素加入子元素:

<cache usage="read-write"/>

其中usage:read-only,read-write,nonstrict-read-write,transactional

 

配置

1.开启二级缓存

2.设置二级缓存的第三方类

3.导入相应的包并配置

4.设置允许放入缓存的类

例如:

hibernate.cfg.xml

        <!-- 开启二级缓存 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <!-- 开启查询语句可以使用二级缓存 -->
        <property name="hibernate.cache.use_query_cache">true</property>
        <!-- 设置二级缓存的支持类 -->
        <property name="hibernate.cache.provider_class">org.hibernate.cache.OSCacheProvider</property>
        <!-- 设置可以放入缓存中的类 -->
        <class-cache usage="read-only" class="com.yinger.domain.User"/>

 

添加 文件 oscache.properties (一般是在Hibernate下载到的etc文件夹中),可以自行设置里面的属性值

 

统计信息:可以得到很多的关于二级缓存的信息(例如:hit,put,miss次数)

用sessionFactory.getSatistics()获取统计信息

示例代码:

Statistics st = HibernateUtils.getSessionFactory().getStatistics();

System.out.println(st.getSecondLevelCachePutCount());

System.out.println(st.getSecondLevelCacheHitCount());

System.out.println(st.getSecondLevelCacheMissCount());

 

 

从二级缓存取数据的方法:get,load,iterator,而存数据的方法很多

Session的save(这个方法不适合native生成方式的主键),update,saveOrUpdate,list,iterator,get,load,以及Query,Criteria都会填充二级缓存,

但(在没打开查询缓存时)只有Session的iterator,get,load会从二级缓存中取数据(iterator可能存在N+1次查询,如果没有取到数据的话)。

Query,Criteria(查询缓存)由于命中率较低,所以hibernate缺省是关闭;修改cache.use_query_cache为true打开对查询的缓存,

并且调用query.setCacheable(true)或criteria.setCacheable(true)。

测试代码: N+1次查询的问题的代码
/**
 * @Author:胡家威  
 * @CreateTime:2011-8-16 上午12:52:21
 * @Description:
 */

package com.yinger.main;

import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.stat.Statistics;

import com.yinger.domain.IdCard;
import com.yinger.domain.Person;
import com.yinger.util.HibernateUtils;

public class One2One {

    public static void main(String[] args) {
        add();
        add();
        System.out.println("-------------");
        ncpp();
        System.out.println("-------------");
        iterate();
        
//        Statistics st = HibernateUtils.getSessionFactory().getStatistics();
//        System.out.println(st.getSecondLevelCachePutCount());
//        System.out.println(st.getSecondLevelCacheHitCount());
//        System.out.println(st.getSecondLevelCacheMissCount());
        
//        IdCard card = queryIdCardById(1);
//        System.out.println(card.getPerson().getName());
    }

    // 添加信息到数据库中
    private static void add() {
        Person p = new Person();
        p.setName("person name");

        IdCard card = new IdCard();
        card.setLife(new Date());

        // p.setIdCard(card); // 外键一对一映射的这种情况下 id_card表中的person_id字段没有值
        card.setPerson(p);

        Session s = null;
        Transaction tr = null;
        try {
            s = HibernateUtils.getSession();
            tr = s.beginTransaction();
            s.save(p);
            s.save(card);

            tr.commit();
        } catch (Exception e) {
            if (tr != null)
                tr.rollback();
        } finally {
            if (s != null)
                s.close();
        }
    }

    // 根据id查询IdCard
    public static IdCard queryIdCardById(int id) {
        IdCard card = null;
        Session s = null;
        try {
            s = HibernateUtils.getSession();
            card = (IdCard) s.get(IdCard.class, id);
            Hibernate.initialize(card.getPerson()); // 可以使用这个方法来初始化代理对象
//            System.out.println(card.getPerson().getName());
            return card;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (s != null) {
                s.close();
            }
        }
        return card;
    }
    
    
    // 测试 N+1 次查询
    @SuppressWarnings("unchecked")
    private static void ncpp() {
        Session s = HibernateUtils.getSession();
        Query q = s.createQuery("from IdCard");
        q.setCacheable(true);
        List<IdCard> ics = q.list();
        for (IdCard ic : ics) {
            System.out.println(ic.getPerson().getName());
        }
        s.close();
    }
    
    // 测试 N+1 次查询
    @SuppressWarnings("unchecked")
    private static void iterate() {
        Session s = HibernateUtils.getSession();
        Query q = s.createQuery("from IdCard");
        q.setCacheable(true);
        Iterator<IdCard> it = q.iterate();
        while(it.hasNext()) {
            System.out.println(it.next().getPerson().getName());
        }
        s.close();
    }

}

 

如果在hibernate.cfg.xml中配置了

<!-- 设置可以放入缓存中的类 -->
<class-cache usage="read-only" class="com.yinger.domain.Person"/>
<class-cache usage="read-only" class="com.yinger.domain.IdCard"/>

 

测试结果:

log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: insert into Person (name) values (?)
Hibernate: insert into id_card (life) values (?)
Hibernate: insert into Person (name) values (?)
Hibernate: insert into id_card (life) values (?)
-------------
Hibernate: select idcard0_.id as id, idcard0_.life as life4_ from id_card idcard0_
Hibernate: select person0_.id as id0_, person0_.name as name3_0_ from Person person0_ where person0_.id=?
person name
Hibernate: select person0_.id as id0_, person0_.name as name3_0_ from Person person0_ where person0_.id=?
person name
-------------
Hibernate: select idcard0_.id as col_0_0_ from id_card idcard0_
person name
person name

 

 

如果调换一下 ncpp 和 iterate 方法的调用顺序,结果是:

log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
Hibernate: insert into Person (name) values (?)
Hibernate: insert into id_card (life) values (?)
Hibernate: insert into Person (name) values (?)
Hibernate: insert into id_card (life) values (?)
-------------
Hibernate: select idcard0_.id as col_0_0_ from id_card idcard0_
Hibernate: select idcard0_.id as id0_, idcard0_.life as life4_0_ from id_card idcard0_ where idcard0_.id=?
Hibernate: select person0_.id as id0_, person0_.name as name3_0_ from Person person0_ where person0_.id=?
person name
Hibernate: select idcard0_.id as id0_, idcard0_.life as life4_0_ from id_card idcard0_ where idcard0_.id=?
Hibernate: select person0_.id as id0_, person0_.name as name3_0_ from Person person0_ where person0_.id=?
person name
-------------
Hibernate: select idcard0_.id as id, idcard0_.life as life4_ from id_card idcard0_
person name
person name

 

很清楚的发现两次查询实现了SessionFactory级别(也就是二级缓存)的共享,iterate方法共需要执行5个select,ncpp需要调用3个select

如果没有在hibernate配置文件中加上那两句,是不行的,默认这两个类是不会放入二级缓存的,结果总是3+5=8条select语句

[不过奇怪的是我删掉了q.setCacheable(true);语句之后结果不变,按理应该不会存入到二级缓存中的啊]

 

 

 

3.其他的缓存

分布式缓存(每台服务器保存自己的缓存):如果数据更新了,并且需要更新的其他服务器很多,那么广播出去进行更新的话就很耗时 [读取次数大于更新次数]

中央缓存(利用一台服务器来进行缓存):读取数据时要从另一台服务器得到,这个比较耗时

使用缓存的条件

1.读取大于修改。

2.数据量不能超过内存容量。

3.对数据要有独享的控制。

4.可以容忍出现无效数据。

 

 

 

优秀的文章推荐:

Hibernate3一级缓存和二级缓存的理解!


Hiberante3
一级缓存总结

1. Session 级别的缓存,它同session邦定。它的生命周期和session相同。Session消毁,它也同时消毁;管理一级缓存,一级缓存无法取消,用两个方法管理,clear(),evict()

2. 两个session 不能共享一级缓存,因它会伴随session的生命周期的创建和消毁;

3. Session缓存是实体级别的缓存,就是只有在查询对象级别的时候才使用,如果

使用HQL和SQL是查询属性级别的,是不使用一级缓存的!

4 . iterate 查询使用缓存,会发出查询Id的SQL和HQL语句,但不会发出查实体的,

它查询完会把相应的实体放到缓存里边,一些实体查询如果缓存里边有,就从缓存中查询,但还是会发出查询id的SQL和HQL语句。如果缓存中没有它会数据库中查询,然后将查询到的实体一个一个放到缓存中去,所以会有N+1问题出现。

5 . List()和iterate 查询区别:

使用iterate,list查询实体对象

*N+1问题,在默认情况下,使用query.iterate查询,有可以能出现N+1问题

所谓的N+1是在查询的时候发出了N+1条sql语句1:首先发出一条查询对象id列表的sqlN:

根据id列表到缓存中查询,如果缓存中不存在与之匹配的数据,那么会根据id发出相应的sql语句

list和iterate的区别?

list每次都会发出sql语句,list会向缓存中放入数据,而不利用缓存中的数据

iterate:在默认情况下iterate利用缓存数据,但如果缓存中不存在数据有可以能出现N+1问题

6.get()和load(),iterate方法都会使用一级缓存

7.hiberate3 session 存储对象的过程如下:

例如 object 对象

       Session.save(object);

这时候不会把数据放到数据库,会先放到session缓存中去,数据库中没有相应记录,session.flush();才发SQL和HQL语句,数据库中有了相应记录,

但是数据库用select查不到,这是跟数据库事务级别有关系


Hiberante3 二级缓存总结

1.Hibernate3的(sessionFactory)二级缓存和session级别的缓存一样都只对实体对象做缓存,不对属性级别的查询做缓存;二级缓存的生命周期和sessionFactory的生命周期是一样的,sessionFactory可以管理二级缓存;

2.sessionFactory级别的缓存,需要手动配置;所有的session可以共享sessionFactory 级别的缓存;(一般把一些不经常变化的实体对象放到sessionFactory级别的缓存中

3.Hiberante3二级缓存的配置和使用方法如下:

1. 必须把ehcache.jar包导入,然后到Hibernate3.2的etc文件下把ehcache.xml复制到工程src目录下(ehcache.xml里边的参数里边有详细英文说明);

(说明:ehcache.jar是第三方法的缓存产品,hiberante只是把它做了集成,还有好多第三方hibernate集成的缓存产品,相关说明请查阅hiberante3开发手册;ehcache支持分布应用的(这个和Hibernate3.2开发手册有出入,经过官网查证确实支持了),如果有分布式需求,请换成支持分布式的二级缓存产品,hiberate3开发手册都有相头说明。配置方法都类似);

4.Hibernate3的二级缓存默认是开起的,也可以指定开起。在hibernate.cfg.xml 文件下配置如下:

*修改hibernate.cfg.xml文件,开启二级缓存;

<property name=”hibernate.cache.use_second_level_cache”>true</property>

*指定二级缓存产品的提供商;

<property name=”hibernate.cache.provider_class”> org.hibernate.cache.EhCacheProvider

</property>

要让那些实体使用二级缓存,在hibernate.cfg.xml配置文件中加入:

<!—

让这个实体用二级缓存 也可以在实体中映射文件去配置即:

<cache usage="read-only"/>

-->

<class-cache class=”com.zzz.hibernate.ClassT” usage=”read-only”/>

Read-only一般使用这个策略,其它的hibernate3开发手册中也有详细介绍;

CacheMode去hibernate3开发手册中搜索这个关键字,可以找到一级缓存和二级缓存交互使用的问题;

 

 

分享到:
评论

相关推荐

    java必了解的六大问题

    *第十四阶段:Hibernate框架学习,三大框架之一,包括检索映射技术,多表查询技术,缓存技术以及性能方面的优化; *第十五阶段:Spring框架的学习,三大框架之一,包括了IOC,AOP,DataSource,事务,SSH集成以及...

    低清版 大型门户网站是这样炼成的.pdf

    1.4 ssh 2组合框架—门户网站开发之首选 28 1.4.1 mvc混血宠儿struts 2 28 1.4.2 幕后的财政部长spring 2.5 30 1.4.3 orm中间件香馍馍hibernate 3.2 31 1.5 小结 32 第2章 mvc混血宠儿struts 2 33 2.1 初识mvc...

    基于J2EE框架的个人博客系统项目毕业设计论文(源码和论文)

    我们掌握了数据库及其应用技术、数据库原理、计算机网络技术等课程,对数据库的设计、应用、维护及局域网的组成有了深刻的认识与一定的动手实践能力,考取了信息处理、程序设计、数据库技术等国家IT认证。...

    lamp-cloud微服务脚手架

    采用J2Cache操作缓存,第一级缓存使用内存(Caffeine),第二级缓存使用 Redis。 由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。 该缓存框架主要用于集群环境中。...

    支持多数据库的ORM框架ef-orm.zip

    阅读推荐:第9、17章 特点四,分库分表 开发过程中参照了Hibernate Shards、Alibaba TDDL、Cobar等框架,也是基于词法分析器来提取SQL参数,并计算路由。 能支持分库维度含糊等场景下的分库分表。以及包括多库多...

    iBATIS实战

    第9章 使用高速缓存提高性能 156 9.1 一个简单的iBATIS高速缓存示例 156 9.2 iBATIS高速缓存的理念 157 9.3 理解高速缓存模型 158 9.3.1 type属性 158 9.3.2 readOnly属性 159 9.3.3 serialize属性 159 9.3.4 联合...

    JAVA上百实例源码以及开源项目源代码

     Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲区、写入数据到文件、关闭输入流、关闭套接字关闭输出流、输出错误信息等Java编程小技巧。 Java数组倒置...

    Java学习笔记-个人整理的

    {9}Java网络编程}{135}{chapter.9} {10}反射}{141}{chapter.10} {10.1}Class}{141}{section.10.1} {10.1.1}Field}{145}{subsection.10.1.1} {10.1.2}Method}{145}{subsection.10.1.2} {10.1.3}Constructor}{...

    JAVA上百实例源码以及开源项目

     Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲区、写入数据到文件、关闭输入流、关闭套接字关闭输出流、输出错误信息等Java编程小技巧。 Java数组倒置...

    lamp-cloud微服务脚手架-其他

    采用J2Cache操作缓存,第一级缓存使用内存(Caffeine),第二级缓存使用 Redis。 由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。 该缓存框架主要用于集群环境中。...

Global site tag (gtag.js) - Google Analytics