zoukankan      html  css  js  c++  java
  • Hibernate【缓存篇】

    一。SQL语句什么时候发出?  

      SQL语句其实并不是在transaction.commit()的时候发出的,而是在一句session.flush()发出。在transaction.commit()的内部就调用了session.flush()方法。之后才会提交SQL语句

      Session.flush()到底做了什么?  

      在Hibernate内部去检查所有的持久化对象,如果持久化对象是由临时状态转换过来的,发出insert语句;如果持久化对象是由get方法得到的,再查看下副本,如果和副本对照一致,什么都不做,如果和副本不一样,则发出update语句


    二。缓存

      缓存的概念:  在内存区域开辟了临时空间。把一些数据临时放在内存区域,需要的时候直接从内存拿

      市面上的缓存有哪些:

        小型缓存-小型应用:oscache,ehcache

        大型缓存-分布式应用:memory cache,redis

        和大数据有关(hadoop生态圈)-分布式应用: hbase

      用缓存需要考虑问题:

         1.需要一些api把数据库中的数据放到缓存中

         2.有一些方法从缓存中把数据提取出来

         3.如果缓存中的数据发生变化,需要把数据同步到数据库中(检查缓存中的数据和数据库中的数据的一致性)

         4.把数据库中的数据同步到缓存中。

           5.hits命中率越低的对象应该从缓存中清空

       分布式缓存:

        缓存放在很多机器(节点)上。

         分布式缓存对于客户端是不可见的,意思是有个黑匣子把节点全部包含了起来。分布式缓存存在的意义,缓存存在与服务器中,并放量太高。导致服务器崩溃,所以我们增加一台服务器,有着相同的缓存(相同缓存数据)。

        应用:tomcat服务器并发量为150个,实际上并发量达到了100速度就很慢了,我们的解决方法就是增加服务器,但问题随之出现,session不一致。我们就把session放在tomcat的分布式缓存中(memory  cache),分布式缓存就帮我们把session在服务器之间同步起来。

        ps:tomcat与memory cache可以进行无缝贴合


    三。一级缓存

      Hibernate的一级缓存又名为session级别的缓存。一级缓存的生命周期和session的生命周期保持一致

      Hibernate的一级缓存对应的源码

      SessionImpl实现类中的private transient StatefulPersistenceContext persistenceContext属性。

      StatefulPersistenceContext类型的private Map entitiesByKey属性;就是一级缓存在内存中的对象

      一级缓存(持久化对象)的再解释:

        前一章,我们知道当我们在内存中创建了临时状态对象,通过session的一些方法进入了Hibernate中变为持久化状态,实际上这么说并不对,在这里就应该更正为并不能说进入了hibernate中,而应该说进入了一级缓存中。换句话说,如果说一个对象是一个持久化状态对象,那么这么对象就在一级缓存中。

      证明持久化对象在一级缓存中。

        证明方法一:连续两次get操作

        证明方法二:hibernate的统计机制。session.getStatistics().getEntityCount()

        结论:1。get(),save(),update().把对象放到了一级缓存当中。clear()也就是清空一级缓存中所有的数据。

           2。当调用了session.close().一级缓存的生命周期就结束了

           3。clear()清空一级缓存中所有的数据

             4。evict()可以把一个对象从session的缓存中清空    

      

      把数据放入一级缓存的意义是什么?

          看个例子:

          假设有一辆xx路公交车,有很多站点。当公交车想要重新换一条线路的时候

          (1)bus-station,一对多的关联关系,通俗的解释就是1个bus对应多个station。每个station对应一个bus

             

         (2)

            SessionFactory sessionFactory=HibernateUtils.getSessionFactory();
            Session session=sessionFactory.openSession();
            Transaction transaction=session.beginTransaction();
            
            Bus bus=(Bus)session.get(Bus.class,1);
            Set<Station> stations=bus.getStations();
            
            System.out.println(session.getStatistics().getEntityCount());
         .....

        注意:可能大家的控制台输出的一级缓存中只有1个对象。因为延迟加载的原因,后续介绍,只需要在相应的映射文件中,把

            <set name="stations" table="STATION" inverse="false" lazy="false" cascade="save-update">

        中lazy的值改为false即可。

         (3)更改线路

            Bus bus=(Bus)session.get(Bus.class,1);
            Set<Station> stations=bus.getStations();
            for(Station station:stations){
                station.setName("新地址");
            }
            transaction.commit();

          查看控制台输出:

            在事务提交以后,与数据库交互了一次,把一级缓存中持久化对象有变化的,发出了所有的update语句进行了更新,优势就是再一次数据库交互中一次性的发送,减少了与数据库的交互次数

          


     

    四。一级缓存的内存结构

      

    ps:注意此图的箭头标注的是 副本对象,表示副本对象和数据库保持一致


       

    五。Session的深入讨论

    1。Session的创建方式:

      方式一:sessionFactory.openSession();用来创建session

      底层原理:

      

      可以发现,实际上openSession(),就是把连接传递过去,直接创建一个Session实例。只要创建一个新的session,就会打开一个新的连接

      这种方式的缺点:

        在某些场合下有很多弊端:当在购物网购物的时候,在结账的时候。要把购物信息保存在购物网的数据库中,还需要把商品总数量减少。etc。这些事情都要在一个事务情况下运行。  订单系统:当你要保存到购物订单数据中,我们需要一个session。session就是openSession()获得的。商品系统:当你购物需要减少总库存,我们也需要一个session。session通过openSession()获得。money系统:钱的转账也需要session,获得同上。   但是这些所有系统的事情都在一个事务下发生,可是我们创建了三个session。通过源码我知道,每次openSession()都是创建一个新的session,相当于一个新的连接。所以这种方式来获得session是不可行的.

      对于上述缺陷的解决,因为在结账的时候产生的操作,他们处于同一线程,因此我们使用线程局部变量来存储session,就这引出了下面这种方式

      方式二:getCurrentSession()

     

       使用方式:

      1。在hibernate.cfg.xml的配置文件中增加:

      <property name="current_session_context_class">thread</property>

      2。调用SessionFactory的getCurrentSession()。

       【注意

          a.要使用线程中的session,增删改查必须在事务的环境下进行

          b.当执行transaction.commit的时候,session自动关闭。这样子不好,如果事物提交以后,再做关于数据库的操作就不可以操作了

     

       先从当前线程中找到map,在从key为sessionFactory所对应的value(也就是session)中获取,然后获取,如果没有取到,则创建session,然后把它在放入线程的map中

       map中为什么key是sessionFactory

       答:1.支持多个数据库连接,一个sessionFactory代表一个数据库连接,如果是单数据库连接,sessionFactory只有一个,如果是单数据库,sessionFactory的key不变,session只有一个 2.尽量保证,从request到response只有一个session比较好。request操作后台,打开一次数据库就够了

      结论:

        如果在一个线程中,共享一个数据,那么把数据放入到threadlocal中是一个不错的选择

                      请移步先看:Hibernate【关联关系】这篇博客,再看下面的内容   


       

    六。二级缓存开篇 

    Hibernate的一级缓存解决了在一起请求与服务器交互的次数最少,二级缓存解决了什么问题?

      适用场合一:12306网站,当你打开网站,需要选择出发地和目的地。当你选择出发地和目的地的时候,网站肯定不会一次一次请求数据库来显示各个省份,而应该直接取内存中去获取这些省份

      适用场合二:MyEclipse的菜单,比如创建工程什么的,菜单栏选项(固定数据)就在数据库中,不应该当点击某个菜单的时候,在一条一条从数据库获取菜单,而应该把这些菜单放在缓存。

      不适用场合三:股票一些实时信息,数据随时都在变,不能放在缓存中  

      不适用场合四:财务系统,个人工资信息应该是保密的,不能放在缓存中 

      这上面的例子说明了三个方面的数据会使用二级缓存

        1.公开的

        2.固定的(数据不会发生变化)

        3.数据保密性不是很强

      ps:如果一个数据一直在变,不适合用缓存。

      扩展:还有一些数据,本身数据是不变的,但是流动的,称这些数据为“流式数据”。比如,我们再去飞机场途中,玩下手机,在玩手机的过程中,信号就产生了,电信产生了信息:某某在什么时间什么地方发了信息。等你坐飞机到了目的地,又玩了下手机,信号又发出,这样子可以知道你的路径,这样就可以给你推送一些需要的信息,这样的数据叫流数据,数据没改,这样的数据适用用storm来做,如果流数据量特别大,比如一个省,几千万个人在玩手机,就需要时时刻刻计算,走到哪就要把信息推送到哪,这些信息就是即时的,这个storm就是在缓存中计算的

    二级缓存的生命周期

      SessionFactory级别的,只要Hibernate一启动,Hibernate就有了,SessionFactory一直不消失,那么二级缓存在哪里?

    二级缓存的位置

      SessionFactoryImpl该类中有个Map,该Map名称为entityPersisters.他就是存放类级别二级缓存的位置;在这个类中我们也看到了一个叫CollectionPersisters的Map,它正是集合二级缓存。

    Hibernate设置二级缓存

      Hibernate并没有实现二级缓存,他是借助了第三方插件完成。

        原因:oscache,ehcache。。市面上有许多缓存了,Hibernate没有必要专门做一个,所以Hibernate利用了ehcache实现了二级缓存

      如何在Hibernate中配置呢?

      1.导入jar包

        ehcache-xxx.jar

      2.在hibernate.cfg.xml配置文件增加一些配置

        a。设置二级缓存的供应商   

    <property name="cache.provider_class">
        org.hibernate.cache.EhCacheProvider
    </property>

        b.开启二级缓存()

    <property name="cache.use_second_level_cache">
        true
    </property>

      3.指定哪个类开启二级缓存(两种方法)

       方式一:在hibernate.cfg.xml中指定(该方式不常用)

    <class-cache usage="read-only" class=""/>  <!-- 类级别的二级缓存 -->
    <collection-cache ...><!-- 集合的二级缓存。集合指的是 一对多,多对多。。。中的对象对集合中的集合 -->

        方式二:在*.hbm.xml中指定

    <hibernate-mapping>
        <class name="domain.Person" table="PERSON">
            <cache usage="read-only"/> <!-- 开启了Person的二级缓存 -->
            <id name="pid" type="java.lang.Integer">
                <column name="PID" />
                <generator class="increment" />
            </id>
            <property name="name" type="java.lang.String">
                <column name="NAME" />
            </property>
    .....

    二级缓存的操作

      用哪些方法可以把对象放入二级缓存?

    •  测试session.get(...)方法,

        

    @Test
    public void updateStudent(){
        sessionFactory=getSessionFactory();
        Session session=sessionFactory.openSession();
        Person person=(Person)session.get(Person.class, 1);
        session.close();
            
        Session session2=sessionFactory.openSession();
        Person person2=(Person)session2.get(Person.class, 1);
        session2.close();
    }

        控制台只发出了一次sql语句,显然当第一次session.close()后,一级缓存中已经没有对象了,第二次get操作没有发出sql语句,直接拿到了数据,通过源码可知(DefaultLoadEvenListener类),get操作会先从一级缓存中获取,再从二级缓存中获取,否则就会向数据库发出sql语句,由此可知,get把对象放入了二级缓存中,并从二级缓存中获取

     查看get操作的源码

        。。

    •   测试save(...)方法

     发现要证明是在哪一步放入了二级缓存很难证明,由此我们引入了统计机制,那么如何使用呢?

      1.在配置文件中开启二级缓存统计机制

    <property name="generate_statistics">true</property>

      2.使用:"sessionFactory.getStatistics().getEntityLoadCount();"

     结论:save()方法并不能操作二级缓存。虽然保存了,数据可能会变化,而二级缓存的数据不能变化,同update一样,
            

    •   测试update(...)操作

        结论:不操作二级缓存

    在实际项目中,在二级缓存中放入什么数据呢?

      权限!一个系统开发完以后,权限是确定的,权限是不需要维护的,我们只需要把权限分配给用户。在tomcat启动的时候,把权限保存在二级缓存中,权限是从数据库中获取的。问题来了,一个系统有400个页面,一个页面有4个功能,一共1600个功能,现在我们只有get方法可以让对象进入二级缓存,一个get方法只能获取一个功能,我们不能get 1600次。那怎么操作?

      利用查询让对象进入二级缓存中

        

        @Test
        public void testQuery(){
            sessionFactory=getSessionFactory();
            Session session=sessionFactory.openSession();
            List<Person> persons=session.createQuery("from domain.Person").list();//HQL语句 hibernate query language
            System.out.println(sessionFactory.getStatistics().getEntityLoadCount());
            session.close();
        }

      "from domian.Person"是HQL语句,值得注意的是这里domain.person是持久化类对应.hbm.xml中class的name名称,而不是table名称。

      list()方法把查询出来的数据都放入了二级缓存中,因为控制台打印出了4(person对应的表中有4条数据),并且只发出了一条sql语句

    但是:

        @Test
        public void testQuery(){
            sessionFactory=getSessionFactory();
            Session session=sessionFactory.openSession();
            List<Person> persons=session.createQuery("from domain.Person").list();//HQL语句 hibernate query language
            System.out.println(sessionFactory.getStatistics().getEntityLoadCount());
            session.close();
            Session session2=sessionFactory.openSession();
            persons=session2.createQuery("from domain.Person").list();//HQL语句 hibernate query language
            System.out.println(sessionFactory.getStatistics().getEntityLoadCount());
        }

      看到第二次执行list()的时候,又发出了一条sql语句,并且二级缓存里的对象变成double份,看出list只能存入二级缓存却不能取出二级缓存中的对象,那么如何取出来呢?

        @Test
        public void testQuery(){
            sessionFactory=getSessionFactory();
            Session session=sessionFactory.openSession();
            List<Person> persons=session.createQuery("from domain.Person").list();//HQL语句 hibernate query language
            System.out.println(sessionFactory.getStatistics().getEntityLoadCount());
            session.close();
            Session session2=sessionFactory.openSession();
            Iterator<Person> iterator=session2.createQuery("from domain.Person").iterate();
            while(iterator.hasNext()){
                Person person=iterator.next();
                System.out.println(person.getName());
            }
            session2.close();
        }

      发现先执行了查询id的sql语句,然后直接得到了我们要的值。

    总结:list方法会让hql语句查询出来的结果进入二级缓存,但是list方法不利用二级缓存查询

       iterator方法的查询策略,不管二级缓存有没有数据,先查询表的所有的id,然后根据id值从二级缓存中查找数据,如果有,则利用二级缓存,如果没有,则直接根据id查询该表中的所有对象

      用哪些方法可以把对象从二级缓存中提取出来?

        iterator

        get

    把集合存放进二级缓存

      1.修改*.hbm.xml配置文件

     <class>
        <id></id>
        <property></property>
         ....    
        <set name="students" table="STUDENT" cascade="save-update" inverse="false">
                <cache usage="read-only"/>
                <key>
                    <column name="PID" />
                </key>
                <one-to-many class="domain.Student" />
        </set>
     </class>

      2.放入

        @Test
        public void testCollection(){
            sessionFactory=getSessionFactory();
            Session session=sessionFactory.openSession();
            Person person=(Person)session.get(Person.class, 1);
            Set<Student> students=person.getStudents();
            for(Student s:students){
                s.getName();
            }
            System.out.println(sessionFactory.getStatistics().getEntityLoadCount());
            System.out.println(session.getStatistics().getCollectionCount());
            System.out.println(sessionFactory.getStatistics().getCollectionLoadCount());
            session.close();
        }

    二级缓存的存储策略

     <cache usage="read-only"/>

    usage:

      read-only:只读策略,正是因为这个策略,所以对二次缓存对象不能修改

       read-write:对二级缓存中的对象能够进行读和写的操作

    二级缓存存放的数据量过大时也会导致查询效率降低,

      解决:移到磁盘上

      ehcache本身就具有这样的功能,我们需要配置

      1.在根目录下创建ehcache.xml的配置文件

    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
             
        <diskStore path="/Users/admin/Documents/workspace/HIbernate/ddd"/>
         <!-- 默认存储策略 -->
        <defaultCache 
                maxElementsInMemory="12"  
                eternal="false"
                timeToIdleSeconds="1200"
                timeToLiveSeconds="1200"
                overflowToDisk="false"
                maxElementsOnDisk="10000000"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU"
                />
                <!-- timeToIdleSeconds:最大空闲时间,这个对象没有用1200s,这个对象如果一直没用超过一定时间就作废
                     maxElementsOnDisk="10000000" 磁盘上最大村多大对象
           <Cache
                name="domain.Person"
                maxElementsInMemory="3" 
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                overflowToDisk="true"
                maxElementsOnDisk="10000000"
                diskPersistent="false"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU"
                />
    </ehcache>

      2.用list方法(),把hql语句查询结果全部放入二级缓存,此时在你设置的路径下,就会创建一个临时文件,用来存放二级缓存


    七。查询缓存

      

    对象缓存的意义

      一级缓存解决再一次请求的过程中和数据库只交互一次,二级缓存解决把固定,公开的数据放进去,方便查询,那么查询缓存是什么的呢?
        一级缓存和二级缓存都是兑现缓存,意思是缓存一个对象,并且这个对象是持久化对象,更进一步解释说,假设有一个持久化对象,这个对象中有300个字段,一级缓存或者二级缓存只要查询出来会把300个字段都放到缓存中,可是我们只需要20个字段,我们就不需要把300个查出来。所以对象缓存就是把对应的数据库表中的所有字段都缓存,在某些场合,这样很浪费效率。显然用一级缓存和二级缓存就不好了。

    数据缓存的意义

      查询缓存也叫数据缓存,内存(页面)中需要多少数据就缓存多少数据,解决的是一张表的部分字段的缓存问题。

    查询缓存的生命周期

      只要一些数据放入到查询缓存中,该缓存会一直存在,直到缓存中的数据被修改,该缓存的生命周期就结束了

    查询缓存的位置

      SessionFactoryImpl中的QueryCache queryCache和UpdateTimestampsCache updateTimestampsCache;

      由这两个对象构成了查询缓存。

      

      

    为什么查询缓存有两个对象呢?

       

      cacheRegion: 缓存的名字

      updateTimestampsCache:时间戳缓存,Hibernate通过时间戳来判断 数据是否更新了

    查询缓存的操作步骤

    一。首先开启缓存

      1.在hibernate.cfg.xml的配置文件开启查询缓存

    <property name="cache.use_query_cache">true</property>

      2.使用查询缓存

        @Test
        public void testList(){
            sessionFactory=getSessionFactory();
            Session session=sessionFactory.openSession();
            Query query=session.createQuery("from domain.Person");
            query.setCacheable(true);//query要使用查询缓存
            query.list();//把数据放入查询缓存中
            session.close();
            
            session=sessionFactory.openSession();
            query=session.createQuery("from domain.Person");
            query.setCacheable(true);//query要使用查询缓存
            query.list();//把数据放入查询缓存中
            session.close();
        }

    八。Hibernate中缓存的总结

      Hibernate中总共有三种缓存:
        一级缓存解决的问题:在一次请求尽量减少与数据库交互的次数,在session.flush()之前,改变的是一级缓存对象的属性,当session.flush()之后,才会和数据库交互。

        一级缓存解决不了的问题:重复查询的问题,查询一次session就关闭,一级缓存就没有了

        二级缓存解决的问题:经常不改变,常用的,公共的数据让入进来,可以进行重复查询,利用get()和iterator()得到二级缓存的数据

        查询缓存解决的问题:可以缓存数据或对象,可以利用list()把查询缓存中的数据放入到缓存中,查询缓存中存放的数据是数据缓存

  • 相关阅读:
    block iOS 块
    面试有感
    Could not automatically select an Xcode project. Specify one in your Podfile like so
    xcrun: error: active developer path
    NSDictionary
    CSS3魔法堂:CSS3滤镜及Canvas、SVG和IE滤镜替代方案详解
    转:CSS盒模型
    转:手机端html5触屏事件(touch事件)
    转: div:给div加滚动条 div的滚动条设置
    转:什么时候用阻止事件冒泡
  • 原文地址:https://www.cnblogs.com/xingdongpai/p/5092551.html
Copyright © 2011-2022 走看看