首页 > 代码库 > Hibernate之二级缓存

Hibernate之二级缓存

 

时间:2017-1-25 01:47

 

——缓存

1、Hibernate提供的缓存有一级缓存、二级缓存,目的是为了减少对数据库的访问次数,提升程序执行效率。

2、一级缓存
    基于Session的缓存,缓存内容只在当前Session有效,当Session关闭后,缓存内容失效。
    特点:
        作用范围小,缓存时间短,缓存效果不明显。

3、二级缓存
    Hibernate提供了基于应用程序级别的缓存,可以跨多个Session来使用,不同的Session都可以访问缓存中的数据,这个缓存也叫二级缓存。

    Hibernate提供的二级缓存有默认的实现,并且是一种可拔插的缓存框架。
    如果用户想使用二级缓存,只需要在hibernate.cfg.xml中配置即可,如果不想使用,直接移除即可,不影响代码。
    如果用户觉得hibernate提供的缓存框架不好用,可以更换其他的缓存框架或自己实现缓存框架。

——使用二级缓存

    查看hibernate.properties配置文件,查看如何配置二级缓存。

    ##########################
    ### Second-level Cache ###
    ##########################

    #二级缓存默认关闭,将该属性设置为true,表示开启
    #hibernate.cache.use_second_level_cache false 


    #开启查询缓存
    #hibernate.cache.use_query_cache true

    #二级缓存框架的实现

    #hibernate.cache.provider_class org.hibernate.cache.EhCacheProvider
    #hibernate.cache.provider_class org.hibernate.cache.EmptyCacheProvider
    hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider
    #hibernate.cache.provider_class org.hibernate.cache.TreeCacheProvider
    #hibernate.cache.provider_class org.hibernate.cache.OSCacheProvider
    #hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider


使用步骤
    1)开启缓存
        在核心配置文件中配置:
            <property name="hibernate.cache.use_second_level_cache">true</property>
    2)指定使用哪一个缓存框架
        在核心配置文件中配置:
            <property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>

    3)指定哪些类需要加入二级缓存(通常只会放入常用类)
        在核心配置文件中配置:
            <class-cache usage="read-only" class="com.wyc.domain.Customer"/>

    4)测试二级缓存(至少需要两个Session)
        public void fun1(){
 
            Session session1 = HibernateUtils.openSession();
            Transaction tx1 = session1.beginTransaction();
 
            /*
             * 查询一次
             * 因为在配置文件中指定了要缓存这个类,所以会将该类对象缓存到二级缓存中
             *  * <class-cache usage="read-only" class="com.wyc.vo.Customer" />
             */
            Customer customer1 = (Customer) session1.get(Customer.class, 2);
 
            // 使用第二个Session
            Session session2 = HibernateUtils.openSession();
            Transaction tx2 = session2.beginTransaction();
 
            /*
             * 查询第二次
             * 如果没有配置二级缓存,会发送两条SQL语句
             * 如果配置了二级缓存,当查询同一对象时,会发送一条SQL语句
             * 当第二个session在一级缓存查不到数据之后,会到二级缓存查询,如果二级缓存依然查不到,才会访问数据库
             */
            Customer customer2 = (Customer) session2.get(Customer.class, 2);
 
            tx2.commit();
            session2.close();
 
            tx1.commit();
            session1.close();
        }

——缓存策略

    1、<class-cache usage="read-only" />
        只读策略。
        放入二级缓存中的对象,只能读取。
        当其他Session从二级缓存中获取数据之后,不可以进行set操作。
    2、<class-cache usage="read-write" />
        读写策略。
        放入二级缓存中的对象,可以读写。
    3、<class-cache usage="nonstrict-read-write" />
        非严格的读写,效率相对高。
    4、<class-cache usage="transactional" />
        基于事务的策略。
        会对数据进行锁定。
        3.x版本不支持。

    5、也可以在类的映射文件中配置:<cache usage="read-only" />
        表示当前类会缓存到二级缓存中,状态为只读。
 

——类缓存区(测试二级缓存和散装数据)

类缓存区缓存的是对象的散装数据。
查询条件持有对象属性的引用。 

public void fun3(){
    Session session = HibernateUtils.openSession();
    Transaction tx = session1.beginTransaction();
 
 
    /*
     * 查询两个对象,测试是否同一个对象
     */
    Customer customer1 = (Customer) session.get(Customer.class, 1); // 发送SQL
    Customer customer2 = (Customer) session.get(Customer.class, 1); // 不发送SQL
    // 因为一级缓存保存的是对象的引用,所以是同一个对象
    System.out.println(customer1 == customer2);
    tx1.commit();
    session.close();

    session = HibernateUtils.openSession(); 
 
    /*
     * 使用第二个Session查询Customer cid = 1
     *
     * 因为对象已经被散装到二级缓存中,所以不会发送SQL,而是从二级缓存中组装数据
     * 
     * 散装数据:
     *  * 并不是将对象的引用保存到二级缓存中,而是将对象的数据分散开保存到二级缓存中,并维持映射关系
     *  * 例如:cid = 1, cname = 张三...
     */
    Customer customer3 = (Customer) session.get(Customer.class, 1);
    Customer customer4 = (Customer) session.get(Customer.class, 1);
    /*
     * 组装后的对象是同一个对象,结果为true
     */
    System.out.println(customer3 == customer4);
    /*
     * 此时二级缓存中的对象和一级缓存中的对象不是同一个对象,结果为false
     */
    System.out.println(customer1 == customer3);
 
 
 
    tx.commit();
    session.close();
 
}

——集合缓存区

集合缓存区需要依赖类缓存区。
集合缓存区缓存的是集合中元素的id。

如果配置了类缓存区,当查询到集合数据之后,会将集合元素的id保存到类缓存区,并建立集合元素id与集合元素数据的映射关系。然后再将集合元素的所有id缓存到集合缓存区。当再次查询集合时,会根据集合元素的id查询类缓存区,如果有数据,则取出。

如果没有配置类缓存区,当查询到集合数据之后,会将集合元素的id保存到集合缓存区,此时类缓存区中没有集合元素的数据。当再次查询集合时,会到集合缓存区中获取所有集合元素的id,并根据元素的id发送SQL语句依次查询,有多少id,就发送多少SQL。

1、如果想要缓存当前类对象中的某个集合属性,可以在配置文件中添加配置:
        <collection-cache usage="read-write" collection="com.wyc.vo.Customer.orders" />
        类名为:完整类名.集合属性的名称

        集合所对应的类也必须被缓存:
             <collection-cache usage="read-write" collection="com.wyc.vo.Order" />

        也可以在类的映射文件中配置:
            <set> <cache usage="read-only" /> </set>
            表示当前类的集合属性会缓存到二级缓存中,状态为只读。

2、示例代码:
        public void fun1(){
 
            Session session1 = HibernateUtils.openSession();
            Transaction tx1 = session1.beginTransaction();
 
            Customer customer1 = (Customer) session1.get(Customer.class, 2);
            /*
             * 当使用了Customer对象中的Order集合属性时,因为Order类和orders属性设置了二级缓存,所以会缓存到二级缓存中
             * 此时发出第一条SQL
             */
            customer1.getOrders().size();
 
            // 使用第二个Session
            Session session2 = HibernateUtils.openSession();
            Transaction tx2 = session2.beginTransaction();
 
            Customer customer2 = (Customer) session2.get(Customer.class, 2);
            /*
             * 因为集合被放入二级缓存中,所以不会再次查询
             * 不会发送发送第二条SQL
             */
            customer2.getOrders().size();
 
            tx2.commit();
            session2.close();
 
            tx1.commit();
            session1.close();
 
        }



——list()与iteratr()方法

list()方法默认会将数据放入二级缓存,但是不会使用二级缓存中的数据,如果不使用二级缓存,list()方法效率要比iterate()方法效率高。
如果使用了二级缓存,则iterate()方法要比list()方法效率高,因为iterate()方法会从二级缓存中获取数据。

 示例代码:
    1、list()方法会将查询结果保存到二级缓存,但不会使用二级缓存中的数据
        // 验证list()会将查询结果放入二级缓存
        List<Customer> list = session.createQuery("from Customer").list(); // 会发送一条SQL来查询全部记录
        System.out.println(session.get(Customer.class, 1)); // 没有发送SQL查询客户信息,所以数据是从二级缓存中获取的
 
        // list()没有使用二级缓存中的数据
        List<Customer> list2 = session.createQuery("from Customer").list(); // 会发送第二条SQL来查询全部记录
 
    2、iterate()方法会发送N+1条SQL进行查询,但是会使用二级缓存中的数据
        // 发送一条查询id的SQL
        Iterator<Customer> it = session.createQuery("from Customer").iterate();
        // 通过id查询客户的信息
        while(it.hasNext()){
            System.out.println(it.next());
        }
        tx.commit();
 
        session = HibernateUtils.openSession();
        // 发送一条查询id的SQL
        Iterator<Customer> it2 = session.createQuery("from Customer").iterate();
        // 不会发送SQL,而是直接使用二级缓存中的数据
        while(it2.hasNext()){
            System.out.println(it2.next());
        } 
 
——配置ehcache

1、导入ehcachejar包
    依赖 backport-util-concurrent 和 commons-logging

2、开启二级缓存
    <property name="hibernate.cache.use_second_level_cache">true</property>

3、指定缓存框架
    <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
 
4、在核心配置文件中指定使用二级缓存的类
    <class-cache usage="read-write" class="com.wyc.vo.Customer" />

5、将ehcache的配置文件ehcache.xml放到src目录下

6、配置信息:
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
 
        <diskStore path="c:/ehcache"/>
        <defaultCache
                maxElementsInMemory="5"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                overflowToDisk="true"
                maxElementsOnDisk="10000000"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU"
            />
    </ehcache>

7、配置属性介绍
    diskStore:文件保存路径

    name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字。
 
    maxElementsInMemory:设置基于内存的缓存中可存放的对象最大数目。

    eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds和timeToLiveSeconds属性,默认值是false。

    timeToIdleSeconds:设置对象空闲最长时间,以秒为单位,超过这个时间,对象会过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。该属性值必须大于或等于timeToIdleSeconds 属性值。

    overflowToDisk:设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中。

    diskPersistent:当jvm结束时是否持久化对象,取值为true、false,默认是false。

    diskExpiryThreadIntervalSeconds:指定专门用于清除过期对象的监听线程的轮询时间。

    memoryStoreEvictionPolicy:当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)。
 
——更新时间戳区

Hibernate通过时间戳缓存区来判断被缓存的查询结果是否过期。

示例代码:
    // 从数据库中查询一条数据,并将对象数据散装保存到类缓存区中,并生成一个时间戳T1
    Customer customer = (Customer) session.get(Customer.class, 1);
    System.out.println(customer);
    /*
     * 使用HQL进行更新操作,可以避开二级缓存
     * 当执行了更新操作后,在时间戳缓存区生成一个时间戳T2
     */
    session.createQuery("update Customer set cname = ‘赵六‘ where cid = 1").executeUpdate();
 
    tx.commit();
    session = HibernateUtils.openSession();
    // 当再次查询时会比较T1和T2,如果T2 > T1,则表示数据已被更改,会再发送一条SQL查询数据库
    customer = (Customer) session.get(Customer.class, 1);
    System.out.println(customer);



——查询缓存

查询缓存依赖二级缓存。
二级缓存:主要针对类、对象的缓存。
查询缓存:可以缓存类和对象,也可以缓存属性,例如:select c.cname from Customer;

使用setCacheable()方法设置使用查询缓存。

在核心配置文件中的配置:
    <!-- property一定要放到<class-cache><mapping>之前 -->
    <property name="hibernate.cache.use_query_cache" >true</property>

示例代码:
    public void fun(){
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
 
 
        // 二级缓存无法缓存属性
        Query query = session.createQuery("select c.cname from Customer c");
        // 使用查询缓存
        query.setCacheable(true);
        // 发送第一条SQL语句
        query.list();
 
        tx.commit();
 
        session = HibernateUtils.openSession();
        tx = session.beginTransaction();
 
        query = session.createQuery("select c.cname from Customer c");
        query.setCacheable(true);
        // 不发送SQL,直接使用查询缓存中的数据
        query.list();
 
        tx.commit();
        session.close();
    }

Hibernate之二级缓存