Hibernate缓存

来源:互联网 发布:c语言break跳出if 编辑:程序博客网 时间:2024/06/16 19:08

1.什么是缓存?

       缓存(Cache )是计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝,应用程序在运行时直接读写缓存中的数据,只在某些特定时刻按照缓存中的数据来同步更新数据存储源。
        缓存的物理介质通常是内存,而永久性数据存储源的物理介质通常是硬盘或磁盘,应用程序读写内在的速度显然比读写硬盘的速度快,如果缓存中存放的数据量非常大,也会用硬盘作为缓存的物理介质。
       缓存的实现不仅需要作为物理介质的硬件,同时还需要用于管理缓存的并发访问和过期等策略的软件。因此,缓存是通过软件和硬件共同实现的。

2. hibernate中的缓存的分类

          2.1 . 为什么要用缓存

        Hibernate在查询数据时,首先到缓存中去查找,如果找到就直接使用,找不到的时候就会从物理数据源中检索,所以,把频繁使用的数据加载到缓存区后,就可以大大减少应用程序对物理数据源的访问,使得程序的运行性能明显的提升.

          2.2 . 一级缓存

      Hibernate的缓存包括Session的缓存和SessionFactory的缓存,其中SessionFactory的缓存又可以分为两类:内置缓存和外置缓存。Session的缓存是内置的,不能被卸载,也被称为Hibernate的第一级缓存。

         2.3  二级缓存

        SessionFactory的内置缓存和Session的缓存在实现方式上比较相似,前者是SessionFactory对象的一些集合属性包含的数据,后者是指Session的一些集合属性包含的数据。SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的拷贝,而预定义SQL语句是在Hibernate初始化阶段根据映射元数据推导出来,SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的拷贝,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的第二级缓存。

3. 持久化层的缓存的范围

     缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。


        3.1  事务范围:

        缓存只能被当前事务访问。缓存的生命周期依赖于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的对象形式。


         3.2进程范围:

        缓存被进程内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依赖于进程的生命周期,进程结束时,缓存也就结束了生命周期。进程范围的缓存可能会存放大量的数据,所以存放的介质可以是内存或硬盘。缓存内的数据既可以是相互关联的对象形式也可以是对象的松散数据形式。松散的对象数据形式有点类似于对象的序列化数据,但是对象分解为松散的算法比对象序列化的算法要求更快。


        3.3 集群范围:

        在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式。
对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问的速度不一定会比直接访问数据库数据的速度快多少。
        持久化层可以提供多种范围的缓存。如果在事务范围的缓存中没有查到相应的数据,还可以到进程范围或集群范围的缓存内查询,如果还是没有查到,那么只有到数据库中查询。事务范围的缓存是持久化层的第一级缓存,通常它是必需的;进程范围或集群范围的缓存是持久化层的第二级缓存,通常是可选的。


 4.  持久化层的缓存的并发访问策略


      当多个并发的事务同时访问持久化层的缓存的相同数据时,会引起并发问题,必须采用必要的事务隔离措施。
在进程范围或集群范围的缓存,即第二级缓存,会出现并发问题。因此可以设定以下四种类型的并发访问策略,每一种策略对应一种事务隔离级别。
       1) 事务型(Transactional)策略:仅仅在受管理环境中适用。它提供了Repeatable Read事务隔离级别。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读这类的并发问题。
       2) 读写型(read-write)策略:提供了Read Committed事务隔离级别。仅仅在非集群的环境中适用。对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题。
       3) 非严格读写型(nonstrict-read-write)策略:不保证缓存与数据库中数据的一致性。如果存在两个事务同时访问缓存中相同数据的可能,必须为该数据配置一个很短的数据过期时间,从而尽量避免脏读。对于极少被修改,并且允许偶尔脏读的数据,可以采用这种并发访问策略。
       4) 只读型策略(read-only):对于从来不会修改的数据,如参考数据,可以使用这种并发访问策略。
事务型并发访问策略是事务隔离级别最高,只读型的隔离级别最低。事务隔离级别越高,并发性能就越低。


5.一级缓存的管理

       当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的list()、iterate()或filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,Hibernate会根据缓存中对象的状态变化来同步更新数据库。
Session为应用程序提供了两个管理缓存的方法:
evict(Object obj):从缓存中清除参数指定的持久化对象。
clear():清空缓存中所有持久化对象.

其他的方法:

   contains()判断指定的对象是否存在于缓存中。
   flush()刷新缓存区的内容,使之与数据库数据保持同步。

  测试一级缓存

public class Client{    public static void main(String[] args)    {        Session session = HibernateUtil.getSessionFactory().openSession();        Transaction tx = null;        try        {            /*开启一个事务*/            tx = session.beginTransaction();            /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/            Customer customer1 = (Customer)session.get(Customer.class, "402881e534fa5a440134fa5a45340002");            System.out.println("customer.getUsername is"+customer1.getUsername());            /*事务提交*/            tx.commit();                        System.out.println("-------------------------------------");                        /*开启一个新事务*/            tx = session.beginTransaction();            /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/            Customer customer2 = (Customer)session.get(Customer.class, "402881e534fa5a440134fa5a45340002");            System.out.println("customer2.getUsername is"+customer2.getUsername());            /*事务提交*/            tx.commit();                        System.out.println("-------------------------------------");                        /*比较两个get()方法获取的对象是否是同一个对象*/            System.out.println("customer1 == customer2 result is "+(customer1==customer2));        }        catch (Exception e)        {            if(tx!=null)            {                tx.rollback();            }        }        finally        {            session.close();        }    }}


 程序控制台输出结果:

Hibernate:     select        customer0_.id as id0_0_,        customer0_.username as username0_0_,        customer0_.balance as balance0_0_     from        customer customer0_     where        customer0_.id=?customer.getUsername islisi-------------------------------------customer2.getUsername islisi-------------------------------------customer1 == customer2 result is true


      输出结果中只包含了一条SELECT SQL语句,而且customer1 == customer2 result is true说明两个取出来的对象是同一个对象。其原理是:第一次调用get()方法,Hibernate先检索缓存中是否有该查找对象,发现没有,Hibernate发送SELECT语句到数据库中取出相应的对象,然后将该对象放入缓存中,以便下次使用,第二次调用get()方法,Hibernate先检索缓存中是否有该查找对象,发现正好有该查找对象,就从缓存中取出来,不再去数据库中检索。

6.Hibernate使用二级缓存

   Hibernate的二级缓存策略的一般过程如下:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
  Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache

适合存放到第二级缓存中的数据:
1. 很少被修改的数据。
2. 不是很重要的数据,允许出现偶尔并发的数据。
3. 不会被并发访问的数据。
4. 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。
不适合存放到第二级缓存的数据:
1. 经常被修改的数据。
2. 财务数据,绝对不允许出现并发。
3. 与其他应用共享的数据。

  Hibernate如何将数据库中的数据放入到二级缓存中?注意,你可以把缓存看做是一个Map对象,它的Key用于存储对象OID,Value用于存储POJO。首先,当我们使用Hibernate从数据库中查询出数据,获取检索的数据后,Hibernate将检索出来的对象的OID放入缓存中key中,然后将具体的POJO放入value中,等待下一次再次向数据查询数据时,Hibernate根据你提供的OID先检索一级缓存,若没有且配置了二级缓存,则检索二级缓存,如果还没有则才向数据库发送SQL语句,然后将查询出来的对象放入缓存中。

配置hibernate.cfg.xml

        <property name="hibernate.current_session_context_class">thread</property><property name="hibernate.cache.use_second_level_cache">true</property>        <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

指定使用缓存的vo

<class-cache usage="read-only" class="com.bawei.demo.vo.User"/>


配置 ehcache.xml

<?xml version="1.0" encoding="UTF-8"?><ehcache>    <!--        缓存到硬盘的路径    -->    <diskStore path="d:/ehcache"></diskStore>        <!--        默认设置        maxElementsInMemory : 在內存中最大緩存的对象数量。        eternal : 缓存的对象是否永远不变。        timeToIdleSeconds :可以操作对象的时间。        timeToLiveSeconds :缓存中对象的生命周期,时间到后查询数据会从数据库中读取。        overflowToDisk :内存满了,是否要缓存到硬盘。    -->    <defaultCache maxElementsInMemory="200" eternal="false"         timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></defaultCache>           </ehcache>


测试:

public static void main(String[] args) {// 1.初始化配置文件Configuration configuration = new Configuration().configure();// 2.建立session工厂SessionFactory sessionFactory = configuration.buildSessionFactory();// 3.从工厂中取得一个连接Session session = sessionFactory.getCurrentSession();//UserPK usersPK = new UserPK();usersPK.setName("zhangsan");usersPK.setCardId("110");Session session1 = sessionFactory.getCurrentSession();session1.beginTransaction();User user = (User) session1.get(User.class, usersPK);session1.getTransaction().commit();session1 = sessionFactory.getCurrentSession();session1.beginTransaction();User user1 = (User) session1.get(User.class, usersPK);session1.getTransaction().commit();}


 

原创粉丝点击