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 

  

  

  

原文地址:https://www.cnblogs.com/gudu1/p/6882155.html