• 框架学习之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_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);语句之后结果不变,按理应该不会存入到二级缓存中的啊]

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

  • 相关阅读:
    easyui loadFilter 使用
    控件setText与setValue赋值顺序先后区别
    JS选中和取消选中checkbox
    easyui 解决连弹两个dialog时候,第二个dialog居中问题
    bootstrap基础学习【导航条、分页导航】(五)
    bootstrap基础学习【菜单、按钮、导航】(四)
    sublime设置
    《啊哈!算法》笔记
    《编码的奥秘》笔记
    Effective Objective-C 2.0 — 第14条:理解“类对象“的用意
  • 原文地址:https://www.cnblogs.com/yinger/p/2145158.html
Copyright © 2020-2023  润新知