• Hibernate:Hibernate缓存策略详解


    一:为什么使用Hibernate缓存:

      Hibernate是一个持久层框架,经常访问物理数据库。

      为了降低应用程序访问物理数据库的频次,从而提高应用程序的性能。

      缓存内的数据是对物理数据源的复制,应用程序在运行时从缓存中读取数据,在特定时间或事件会同步缓存和物理数据源的数据

    二:什么是Hibernate缓存:

      Hibernate缓存分为两种:一级缓存,二级缓存。

        1.一级缓存:又称为Session缓存,

        Session缓存是Hibernate内置的缓存,不能被卸载,生命周期也就是在open和close之间,也就是每打开一个Session,内存中就会创建一块Session缓存区。

        Session缓存中,每个持久化类都拥有一个唯一的OID。(在Hibernate中,类对象分为四种状态:持久化,游离,临时,销毁)。

          ①.对于刚创建的一个对象,如果session中和数据库中都不存在该对象,那么该对象就是临时

          对象(Transient)

        ②.临时对象调用save方法,或者游离对象调用update方法可以使该对象变成持久化对象,如果
          对象是持久化对象时,那么对该对象的任何修改,都会在提交事务时才会与之进行比较,
          如果不同,则发送一条update语句,否则就不会发送语句

        ③.游离对象就是,数据库存在该对象,但是该对象又没有被session所托管

        ④.销毁状态顾名思义,就是被Delete的数据,只有临时状态和游离状态才能转换为销毁状态

        通过OID查询到数据都会存放在Session缓存(一级缓存)中。

        2.二级缓存:又称为SessionFactory缓存。

        SessionFactory缓存是用户可选的,默认情况下不会开启,可以选择不同的缓存提供商来进行配置。

        SessionFactory缓存的声明周期是在应用程序运行到程序结束之间,就是说每一个程序只会拥有一个SessionFactory缓存,因为二级缓存是在进程范围或者

        说集群范围,所以有可能出现并发问题,因此需要采用适当的并发缓存策略,该策略为被缓存的数据提供了事务隔离级别。

        Hibernate提供了org.hibernate.cache.CacheProvider接口,它充当缓存插件与Hibernate之间的适配器。

        什么样的数据适合存放在二级缓存中

        1>不会经常被修改的数据

        2>常量数据

        3>不是很重要的数据,允许偶尔出现并发的数据

        4>不能被并发访问的数据,例如:银行账户

        

        3.延迟加载

        延迟加载是在用户查询某个实体时,加载这个实体的同时,并不会加载该实体所关联的其它对象,而是会产生一个代理对象,在真正使用到这个对象的时候,

        才会通过在缓存中存放的该对象的OID去查询数据库,并返回查询结果,如果查询条件是除了主键OID外,都会直接去查询数据库,通过延迟加载也是大大

        提高了应用程序的运行效率。

        

        4.应用程序查询数据

        在应用程序通过OID查询数据的时候,会先从一级缓存中查询,如果查不到就会从二级缓存中查询,都查不到才会从数据库中查找。

    一级缓存和二级缓存的对比:

      一级缓存 二级缓存
    存放数据的形式 相互关联的持久化对象 对象的散装数据
    缓存的范围

    由于每个事务都拥有单独的一级缓存

    不会出现并发问题,因此无须提供并发访问策略

    由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别

    数据过期策略

    处于一级缓存中的对象永远不会过期,除非应用程

    序显示清空或者清空特定对象

    必须提供数据过期策略,如基于内存的缓存中对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间

    物理介质 内存

    内存和硬盘,对象的散装数据首先存放到基于内存的缓存中,当内存中对象的数目达到数据过期策略的maxElementsInMemory值,就会把其余的对象写入基于硬盘的缓存中

    缓存软件实现 在Hibernate的Session的实现中包含

    由第三方提供,Hibernate仅提供了缓存适配器,用于把特定的缓存插件集成到Hibernate中

    启用缓存方式

    只要通过Session接口来执行保存,更新,删除,

    加载,查询,Hibernate就会启用一级缓存,对

    于批量操作,如不希望启用一级缓存,直接通过

    JDBCAPI来执行

    用户可以再单个类或类的单个集合的粒度上配置第二级缓存,如果类的实例被经常读,但很少被修改,就可以考虑使用二级缓存,只有为某个类或集合配置了二级缓存,Hibernate在运行时才会把它的实例加入到二级缓存中

    用户管理缓存的方式

    一级缓存的物理介质为内存,由于内存的容量有限

    ,必须通过恰当的检索策略和检索方式来限制加载对象的数目,Session的evit()方法可以显示的清空缓存中特定对象,但不推荐

    二级缓存的物理介质可以使内存和硬盘,因此第二级缓存可以存放大容量的数据,数据过期策略的maxElementsInMemory属性可以控制内存中的对象数目,管理二级缓存主要包括两个方面:选择

    需要使用第二级缓存的持久化类,设置合适的并发访问策略;选择缓存适配器,设置合适的数据过期策略。SessionFactory的evit()方法也可以显示的清空缓存中特定对象,但不推荐

    并发访问策略 由于每个事务都拥有单独的一级缓存不会出现并发问题,因此无须提供并发访问策略

    由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别

        

        

      

         

    接下来我们通过例子来讲解一下缓存的具体使用

      1. 一级缓存的讲解

      >>一级缓存主要分为两种:使用HQL语句操作和不使用HQL操作,两者差别主要在于是否是通过OID来操作物理数据库

      先贴上代码:添加一个对象实体,以下Session对象都已经通过SessionFactory静态获取

    public void addUser(){
            Session session = null;
            Transaction tran = null;
            try{
                session = sf.openSession(); // 创建一个Session
                tran = session.beginTransaction(); //开启事务
                
                User user = new User();
                user.setName("王五");
                session.save(user);
                User user2 = (User) session.get(User.class, 4);
                System.out.println(user2);
                
                tran.commit();//事务提交
            }catch(Exception e){
                tran.rollback(); //事务回滚
                throw(e);
            }finally{
                session.close(); //关闭session
            }
        }

      结果:

        Hibernate: insert into user_1 (name) values (?)
        User [id=4, name=王五]

      结果很明显,数据库只有插入的操作,并没有查询的操作,之前有讲过,只要是通过OID操作的数据,都会保存在Session缓存中(即一级缓存),

      既然缓存中有这条数据,那就没必要在数据库中找了

      获得一个实体对象:获得对象Session中有get()和load()两个方法,load()方法比较特殊,会有延迟加载的特效(延迟加载在上面有讲

    @Test
        public void getUser(){
            Session session = null;
            Transaction tran = null;
            try{
                session = sf.openSession(); // 创建一个Session
                tran = session.beginTransaction(); //开启事务
                
                /*这里指明你要获得哪个类型,Hibernate会根据类名查询映射配置文件到数据库查询哪张表,根据指定
                 * id查询实体,通过反射机制创建实体对象
                */
                User user1 = (User) session.get(User.class, 1); //执行查询,get
                System.out.println(user1);
                User user2 = (User) session.get(User.class, 1);
                System.out.println(user2);
                
                tran.commit();//事务提交
            }catch(Exception e){
                tran.rollback(); //事务回滚
                throw(e);
            }finally{
                session.close(); //关闭session
            }
        }

      >>结果:

        Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
        User [id=1, name=李四]
        User [id=1, name=李四]

      从结果可以看出:session通过OID查询只查询了一次,因为在第一次查询查询到的数据已经存放在了Session缓存中(一级缓存)中了,所以再次

              获得该实体对象,应用程序会先查询Session缓存,既然查询到了,就不会再到数据库中查找,所以这里只有一条查询语句。

      

      ----以上的代码,我在获取两个User中间加上一行代码:

    user1.setName("测试_2");

      结果:

        Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
        User [id=1, name=测试_1]
        User [id=1, name=测试_2]
        Hibernate: update user_1 set name=? where id=?

      从结果看出:更新一条数据的时候,并不会立即去数据库进行更新操作,而是先更新Session缓存中的数据,在事务提交

            或Session关闭的时候应用程序会发现Session缓存中的数据有改变,然后才进行更新数据库操作,在Session类中还有一个

            flush()这个方法,和IO流相似,立即刷新数据到目的地,也就是立即把数据更新到数据库中,在这里我并没有使用。

      更新操作和删除操作是一样的,就不多说了。。

      >>使用Hibernate的HQL操作数据

      HQL和SQL基本上一样的,区别:SQL是针对数据库表查询,而HQL是针对类查询;SQL不区分大小写,HQL对类名区分大小写。

      使用HQL操作数据会把查询到的数据保存在缓存区中,但是不会从缓存中查找,而是直接到数据库查找

        接下来使用HQL操作一下

    List<User> list1 = session.createQuery("FROM User WHERE id=3").list();
    System.out.println(list1);
    List<User> list2 = session.createQuery("FROM User WHERE id=3").list();
    System.out.println(list2);

      结果:     

        Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id=3
        [User [id=3, name=张三]]
        Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id=3
        [User [id=3, name=张三]]

      结果可以看出:这里进行了两次查询,说明第一次查询到的数据并没有进缓存,即使限定了id。

      使用迭代器来查询

    Iterator<User> it1 = session.createQuery("FROM User").iterate();
    while(it1.hasNext()){
        System.out.println(it1.next());
    }
    Iterator<User> it2 = session.createQuery("FROM User").iterate();
    while(it2.hasNext()){
        System.out.println(it2.next());
    }

      结果:

        Hibernate: select user0_.id as col_0_0_ from user_1 user0_
        Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
        User [id=2, name=张三]
        Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
        User [id=3, name=张三]
        Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user_1 user0_ where user0_.id=?
        User [id=4, name=王五]
        Hibernate: select user0_.id as col_0_0_ from user_1 user0_
        User [id=2, name=张三]
        User [id=3, name=张三]
        User [id=4, name=王五]

      这里第一次是先查询到所有User的id,然后再根据id去查到所有实体对象,使用迭代器能够迫使程序能够通过id去查询

      ,只有通过OID操作的数据才会进缓存,尽管如此,通过这种方式提高的效率还是有限的,还是会产生大量的查询语句。

      2. 二级缓存

        二级缓存在Hibernate中默认是关闭的,需要在Hibernate.hbm.xml中配置开启,并配置缓存的提供商,除此之外还

        要配置需要添加到缓存的类或集合(class-cache | collection-cache)

      在hibernate.cfg.xml中的配置

    <!-- 默认情况下二级缓存是关闭的 -->
    <!-- 选择使用二级缓存的提供商 -->
    <property name="cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
    <!-- 默认是false,这里选择的值是 查询缓存 -->
    <property name="cache.use_query_cache">true</property>

     前面我们使用HQL进行查询获得一个List集合,虽然也能缓存,但是又有局限性,接下来我们使用二级缓存进行查询

      

      虽然配置了二级缓存,但是并没有指定要缓存的类,所以还要添加缓存类的配置,具体usage的值可以查询API文档

    <!-- 在这里要指定缓存类的全限定名 -->
    <class-cache usage="read-write" class="com.a_helloworld.User"/>
    Session session1  = sf.openSession(); // 创建一个Session
            Transaction tran1 = session1.beginTransaction(); //开启事务
    
            List<User> list1 = session1.createQuery("FROM User WHERE id<10")
                    .setCacheable(true)
                    .list();
            System.out.println(list1);                
    
            tran1.commit();//事务提交
            session1.close(); //关闭session
            
         //第二个Session Session session2
    = sf.openSession(); // 创建一个Session Transaction tran2 = session2.beginTransaction(); //开启事务 List<User> list2 = session2.createQuery("FROM User WHERE id<10") .setCacheable(true) .list(); System.out.println(list2); tran2.commit();//事务提交 session2.close(); //关闭session

      结果: 

        Hibernate: select user0_.id as id0_, user0_.name as name0_ from user_1 user0_ where user0_.id<10
        [User [id=2, name=张三], User [id=3, name=张三], User [id=4, name=测试2], User [id=5, name=呵呵]]
        [User [id=2, name=张三], User [id=3, name=张三], User [id=4, name=测试2], User [id=5, name=呵呵]]

        结果看出只有一条查询语句,第二次查询并没有通过数据库查询,而是从缓存区直接拿到了数据。但是,如果

          把第二次查询的条件修改一下,就需要从数据库查询,说明这里存储的只是HQL语句。

     

    接下来就不贴代码了,说一下我自己经过测试的问题,大家可以自己亲身测试一下。

       >>只要是经过配置的类,所有查询到的数据都会更新二级缓存中

       >>进行更新或者删除操作,程序会通知缓存进行更新

       >>现在很晚了,一点多了,有时间再改进吧。。。

     如需转载,请说明出处:http://www.cnblogs.com/gudu1/p/6882155.html 

      

      

      

  • 相关阅读:
    MySQL优化查询语句Explain
    Spring Kafka(二)操作Topic以及Kafka Tool 2的使用
    集群作业管理OpenPBS与OpenPBS Interface Library
    OpenPBS 脚本样本 非常值得参考
    mpirun 与 PBS 问题
    MPIRUN 与 PBS 脚本
    什么是IA架构服务器
    集群的管理pbs
    软件包 javax.servlet 不存在
    An introduction to PORTABLE BATCH SYSTEM
  • 原文地址:https://www.cnblogs.com/gudu1/p/6882155.html
Copyright © 2020-2023  润新知