框架学习之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关闭之前有效

幻灯片38

 

模拟缓存实现: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_classorg.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);语句之后结果不变,按理应该不会存入到二级缓存中的啊]

幻灯片39

 

3.其他的缓存

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

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

使用缓存的条件

1.读取大于修改。

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

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

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

幻灯片40

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

优秀的文章推荐:

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开发手册中搜索这个关键字,可以找到一级缓存和二级缓存交互使用的问题;

猜你喜欢

转载自wcxt2012.iteye.com/blog/1870141