Hibernate 缓存机制(N+1问题)详解

Hibernate 缓存机制:

    担心:两张表查询(关联查询)起来效率会不会低呢??hibernate 缓存机制非常强大。。。

缓存:介于应用程序和物理数据源之间为了降低应用程序对物理数据源的访问频率,从而提高应用的运行性能(缓存内的数据是对物理数据源的复制,缓存的介质一般是内存,所以读取速度很快;如果缓存中的年数据量很大 将硬盘作为介质)。缓存的实现不仅考虑介质,换应该考虑缓存的并发访问和缓存数据的生命周期。

Hibernate是一个持久层框架,经常访问物理数据库,为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。

 

N+1问题:

N+1问题:

   返回查询列表时获取对象的两种方法:

                list() 方法会发出sql 查询出该对象;(一条sql)

                iterator()方法 会发出取 id 列表的sql ,在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息;

      iterator()方法 来获得对象时,hibernate会先发出取出所有对象id 值 的sql ,大会需要查询某个对象 的具体信息时,hibernate会根据取出的 id 值 发出相应的SQL 语句从数据库中去取具体的对象信息,这就是典型的N+1 问题!

  iterator()方法 获取对象是产生N+1问题 为什么不直接用list()  还要保留iterator()方法 ??

  list() 方法每次返回对象,而iterator()方法先返回对象id ,需要查询具体对象信息才会发出相应的sql 语句去查询 ,既节省了内存花费;例如:再一个session中要两次查询多个对象时,如果两条都用list() ,一定会发出两条sql 而且两条语句一样,但是如果你第一条使用list() 方法(查出了所有对象),第二条使用iterator() 方法(查询出对象 id) ,也是两条sql 但是明显第二条只是根据id 对应第一条查出来的对象而已,可以节省内存;而如果再要获取对象的时候,因为第一条语句已经将对象都查询出来了,此时会将对象保存到session的一级缓存中去,所以再次查询时,就会首先去缓存中查找,如果找到,则不发sql语句了。

hibernate缓存机制:hibernate有两级缓存。。。。。。

1.一级缓存(session的缓存):默认是开启的,

      <1>发出sql 查询后,将查出的信息保存到一级缓存中 再次查询时 直接从一级缓存中拿出信息  不需要再次发出sql;

      <2>由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存,

即当session关闭后 缓存就不存在了 如果再需要查询,此时就会再次发sql查询数据库(解决该问题----配置二级缓存);

      <3>在第一级缓存中,持久化类的每个实例都具有唯一的OID;

2.二级缓存(sessionFactory的缓存):默认不会开启(存在可配置插件,需要进行配置)

  

             

    

            

                  <1>SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别;

                   <2>二级缓存的访问策略有这几种:read-only、nonstrict-read-write、read-write、transactional。。。。。一般都将其配置成read-only(一般都将二级缓存加在不需要修改的实体类上,否则如果对 缓存进行读写的话会降低性能);

                   <3>sessionFactory缓存,当session关闭以后,进行查询时会先去二级缓存中查看是否有该对象  因为二级缓存是sessionFactory级别的缓存;

                   <4>二级缓存  缓存的仅仅是对象(不会缓存对象的属性)如果我们只是取出了对象的一些属性的话,则不会将其保存到二级缓存中去,因为二级缓存缓存的仅仅是对象。

                   <5>二级缓存解决 N+1问题:

              由于对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条取id的语句,然后在获取对象
              时去二级缓存中,如果发现就不会再发SQL,这样也就解决了N+1问题 而且内存占用也不多;

                   <6>二级缓存不会缓存hql 语句:通过 list() 去查询两次对象时,二级缓存虽然会缓存查询出来的对象,但是我们看到发出了两条相同的查询语句,这是因为二级缓存不会缓存我们的hql查询语句,要想解决这个问题,我们就要配置查询缓存了。

   

   查询缓存(sessionFactory 缓存级别):

         

<1>此时如果发出两条相同的语句 hibernate也只会发出一条sql,因为已经开启了查询缓存(sessionFactory级别)了;

<2>查询缓存缓存的不是对象而是 id ;缓存了一个id 之后  发出sql 语句根据id 取对象(第一条sql 语句查询出对象所有 id ,后面如果需要查询对象的其他信息 会在此发出sql 查询),所以查询缓存必须开启二级缓存才能避免N+1 问题;

<3>只有当HQL 完全相同时,包括参数设置都相同时,查询缓存才会起效;       

            

二级缓存的应用:

适合二级缓存的数据存储:

   1 很少被修改的数据
   2 不是很重要的数据,允许出现偶尔并发的数据
   3 不会被并发访问的数据

不适合二级缓存的数据存储:

   1 经常被修改的数据
   2 .绝对不允许出现并发访问的数据,如财务数据,绝对不允许出现并发
   3 与其他应用共享的数据。

hibernate查找对象应用缓存??

   通过id 查找对象时 先在一级缓存中找------->二级缓存中找(配置了的话)----------->数据库中查找  并更新缓存。。。。

hibernate中的持久化对象 :

hibernate中的持久化对象 (三种状态):

transient(瞬时态):通过new关键字直接获取;尚未与Session关联对象,失去引用的话,就会被JVM回收。一般就是直接New创建的对象。

persistent(持久态):通过get/load、Query查询获得;已经与当前session产生关联,并且相关联的session没有关闭,并且事务尚未提交。

detached(脱管态):无法直接获得;存在持久化OID,但没有与当前session关联,脱管状态改变hibernate不能检测到

        Session session = HibernateUtils.openSession();  
        // 开启事务  
        Transaction transaction = session.beginTransaction();  
  
        Cat cat = new Cat(); // 瞬时态(没有OID,未与Session关联)  
        cat .setName("哈哈");  
        cat .setAge(3);  
  
        session.save(cat );// 持久态(具有OID,与Session关联)  
  
        // 提交事务,关闭Session  
        transaction.commit();  
        session.close();  
  
        System.out.println(cat .getId()); // 脱管态(具有 OID,与Session断开关联)  

持久化对象之间的转变:

瞬时——持久:save、saveOrUpdate(都是通过session获得)

瞬时——脱管:对象.setID(1);为瞬时对象设置新的OID

持久——瞬时:delete(被删除持久化对象,不建议再次使用)

持久——脱管:evict(清除一级缓存中某个对象)、close(关闭Session,清除一级缓存)、clear(清除一级缓存所有对象)

脱管——瞬时:对象.setID(null);删除对象OID

脱管——持久:update、saveOrUpdate、lock

参考:hibernate缓存机制详细分析

猜你喜欢

转载自blog.csdn.net/weixin_42249629/article/details/81840715