Hibernate缓存

本文深入探讨了Hibernate的缓存机制,包括一级缓存(Session缓存,默认开启)和二级缓存(SessionFactory缓存,需配置开启)。详细阐述了get()与load()方法的区别,一级缓存的N+1问题及其解决方案,并分析了二级缓存的适用场景和使用策略。此外,还介绍了如何配置和使用二级缓存,以及解决并发访问和数据一致性问题的方法。
摘要由CSDN通过智能技术生成

前言

博主github
博主个人博客http://blog.healerjean.com

1、hibernate get/load

1.1、get方法:

首先在session缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据库中没有就返回null

1.2、load方法:

根据是否懒加载分情况讨论

1.2.1、若为true

1、先在Session缓存中查找,看看该id对应的对象是否存在
2、不存在则使用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由CGLIB动态生成)。
3、等到具体使用该对象(除获取OID以外)的时候,再查询二级缓存和数据库,若仍没发现符合条件的记录,则会抛出一个ObjectNotFoundException。

1.2.2、若为false

和get方法查找顺序一样,首先在session缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据库中没有,则会抛出一个ObjectNotFoundException。

2、缓存分类

1.事务范围(单Session即一级缓存)

事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存作为存储介质,一级缓存就属于事务范围.

2.应用范围(单SessionFactory即二级缓存)

应用程序的缓存可以被应用范围内的所有事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可以使用内存或硬盘作为存储介质,二级缓存就属于应用范围.

3.集群范围(多SessionFactory)

在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致,缓存中的数据通常采用对象的松散数据形式.比如redis

3、一级缓存(session的缓存,默认开启)

1.1、简单介绍

hibernate的一级缓存是session级别的,所以如果session关闭后,缓存就没了,此时就会再次发sql去查数据库。(session关闭还能访问,解决该问题----配置二级缓存)

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

1.2、测试:真实项目

1.2.1、Jpa方法
public interface DemoEntityRepository extends CrudRepository<DemoEntity,Long> {
   

   
    /**
     * 为了验证该sql不会放入缓存
     * @param id
     * @return
     */
    @Query(" FROM DemoEntity d where d.id = :id")
    DemoEntity testCachefindById(@Param("id") Long id) ;
}

1.2.2、测试成功

/**
     * hibernate 一级缓存
     * hibernate的一级缓存是session级别的,所以如果session关闭后,缓存就没了,此时就会再次发sql去查数据库。
     * 作用范围较小! 缓存的事件短。
     * 缓存效果不明显
     *
     * @param id
     */
    public void oneCache(Long id){
   

        DemoEntity demoEntity = demoEntityRepository.findOne(id);
        System.out.println(demoEntity);
        //Hibernate: select demoentity0_.id as id1_0_0_, demoentity0_.age as age2_0_0_, demoentity0_.cdate as cdate3_0_0_, demoentity0_.name as name4_0_0_, demoentity0_.udate as udate5_0_0_ from demo_entity demoentity0_ where demoentity0_.id=?

        //用到了缓存,没有执行sql语句
        DemoEntity demoEntity12 = demoEntityRepository.findOne(id);
        System.out.println(demoEntity12);

        //因为是sql的形式,所以没有用到缓存
        DemoEntity sqlEntity13 = demoEntityRepository.testCachefindById(id);
        System.out.println(sqlEntity13);
        //Hibernate: select demoentity0_.id as id1_0_, demoentity0_.age as age2_0_, demoentity0_.cdate as cdate3_0_, demoentity0_.name as name4_0_, demoentity0_.udate as udate5_0_ from demo_entity demoentity0_ where demoentity0_.id=?
    }



Hibernate: select demoentity0_.id as id1_0_0_, demoentity0_.age as age2_0_0_, demoentity0_.cdate as cdate3_0_0_, demoentity0_.name as name4_0_0_, demoentity0_.udate as udate5_0_0_ from demo_entity demoentity0_ where demoentity0_.id=?
DemoEntity(id=1, name=healerjean, age=25, cdate=2019-03-04 16:22:58.0, udate=2019-03-04 16:22:58.0)
DemoEntity(id=1, name=healerjean, age=25, cdate=2019-03-04 16:22:58.0, udate=2019-03-04 16:22:58.0)

//下面这个是sql查询语句,不会放到缓存中去
Hibernate: select demoentity0_.id as id1_0_, demoentity0_.age as age2_0_, demoentity0_.cdate as cdate3_0_, demoentity0_.name as name4_0_, demoentity0_.udate as udate5_0_ from demo_entity demoentity0_ where demoentity0_.id=?
DemoEntity(id=1, name=healerjean, age=25, cdate=2019-03-04 16:22:58.0, udate=2019-03-04 16:22:58.0)

1.3、N+1(Hibernate原生)

1.3.1、list查询

如果通过list()方法来获得对象,,hibernate会发出一条sql语句,将所有的对象查询出来 ,查询出来的这些对象会根据主键Id放入session一集缓存中去(1条语句)

控制台
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?


 /**
   * 此时会发出一条sql,将30个学生全部查询出来,并放到session的一级缓存当中(按照主键Id的key缓存放入)
   * 
  * 当再次查询某个Id的学生信息时,会首先去缓存中看是否存在,如果不存在,再去数据库中查询
   * 这就是hibernate的一级缓存(session缓存)
   */
   
  List<Student> ls = (List<Student>)session.createQuery("from Student")
                      .setFirstResult(0).setMaxResults(30).list();
                      
                  
  Iterator<Student> stus = ls.iterator();
  for(;stus.hasNext();)
  {
   
      Student stu = (Student)stus.next();
      System.out.println(stu.getName());
  }
  
  //从缓存中拿
 Student stu = (Student)session.load(Student.class, 1);

1.3.2、iterator()获取对象

1、如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql (1条语句)

2、在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息(同事每个都放入session缓存中),
这就是典型的N+1问题 (N条语句)

ibernate: select student0_.id as col_0_0_ from t_student student0_ limit ?
Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
沈凡
Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
王志名
Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
叶敦
.........

/**
   * 如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql
   * 在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息
   * 这就是典型的N+1问题
   * 
   * 存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来(根据Id主键放入session缓存), iterator仅仅只会查询id,这样关于id的所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取
   */
  Iterator<Student> stus = (Iterator<Student>)session.createQuery("from Student")
                      .setFirstResult(0).setMaxResults(30).iterate();
                      

  for(;stus.hasNext();)
  {
   
      Student stu = (Student)stus.next();
      System.out.println(stu.getName());
  }


1.3、 为什么存在iterator

list() 方法每次返回集合对象,而iterator()方法先返回对象id ,需要查询具体对象信息才会发出相应的sql 语句去查询 ,节省了内存花费;

例如:再一个session中要两次查询多个对象时,如果两条都用list() ,一定会发出两条sql(list集合查询语句不使用缓存存放,因为是根据对象的主键Id存放的缓存,list会将集合取出来,再根据Id放到session一级缓存中),而且两条语句一样

但是如果你第一条使用list() 方法(查出了所有对象),第二条使用iterator() 方法(查询出对象 id) ,也是两条sql(在执行iterator的第二条sql的是,开始使用lsit查询出来的每个对象的缓存进行查询) 但是明显第二条只是根据id 对应第一条查出来的对象而已,可以节省内存;

1.4、解决 N+1 ,使用二级缓存

1.5、哪些方法会放数据

1.5.1、get()、load()均会向缓存中存放以及读取数据,
1.5.2、query.list()和query.uniqueResult()会向缓存存放数据但不会从缓存中读取数据
1.5.3、save()和saveOrUpdate()进行添加时,均会向缓存中存放数据。

4、二级缓存(sessionFactory的缓存):默认不会开启(需要进行配置)

4.1、简单介绍

二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。

注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。


事务型(transactional):隔离级别最高,对于经常被读但很少被改的数据,可以采用此策略。因为它可以防止脏读与不可重复读的并发问题。发生异常的时候,缓存也能够回滚(系统开销大)。

读写型(read-write):对于经常被读但很少被改的数据,可以采用此策略。因为它可以防止脏读的并发问题。更新缓存的时候会锁定缓存中的数据。

非严格读写型(nonstrict-read-write):不保证缓存与数据库中数据的一致性。对于极少被改,并且允许偶尔脏读的数据,可采用此策略。不锁定缓存中的数据。

只读型(read-only):对于从来不会被修改的数据,可使用此策略。(只能放入一次)

1、二级缓存是sessionFactory级别的缓存
2、二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性(name,age),则不会被加到缓存中去

4.2、解决N+1问题

只是说明这个迭代器不会再N+1,其实执行的sql还是 list(n) +1

@Test
    public void testCache3()
    {
   
        Session session = null;
        try
        {
   
            session = HibernateUtil.openSession();
            /**
             * 将查询出来的Student对象缓存到二级缓存中去
             */
            List<Student> stus = (List<Student>) session.createQuery(
                    "select stu from Student stu").list();
        }
        catch (Exception e)
        {
   
            e.printStackTrace();
        }
        finally
        {
   
            HibernateUtil.close(session);
        }
        try
        {
   
            /**
             * 由于学生的对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条
             * 取id的语句,然后在获取对象时去二级缓存中,如果发现就不会再发SQL,这样也就解决了N+1问题 
             * 而且内存占用也不多             */
            session = HibernateUtil.openSession();
            Iterator<Student> iterator = session.createQuery("from Student")
                    .iterate();
            for (; iterator.hasNext();)
            {
   
                Student stu = (Student) iterator.next();
                System.out.println(stu.getName());
            }
        }
        catch (Exception e)
        {
   
            e.printStackTrace();
        }
    }

4.3、二级缓存会缓存 hql 语句吗(除非配置查询缓存)

4.3。1、连续使用两个list查询,测试的时候,这里讲session关闭了,再查的

@Test
    public void testCache4()
    {
   
        Session session = null;
        try
        {
   
        
        //
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值