hibernate一级缓存及N+1问题

2019-03-03 14:40|来源: 领悟书生

Hibernate的一级缓存是由Session提供的,因此它只存在于Session的生命周期中,也就是当Session关闭的时候该Session所管理的一级缓存也会立即被清除。

@Test
public void test01() {
    Session session = null;
    try {
        /**
         * 此时会发出一条sql取出所有的学生信息
         */
        session = HibernateUtil.openSession();
        List<Student> ls = session
                    .createQuery("from Student")
                .setFirstResult(0).setMaxResults(50).list();
        Iterator<Student> stus = ls.iterator();
        for(;stus.hasNext();) {
            Student stu = stus.next();
            System.out.println(stu.getName());
        }
        /**
         * id=1的Student对象已经在session的缓存(一级缓存)中,
                 * 此时就不会发sql去取Student
         */
        Student stu = (Student)session.load(Student.class, 1);
        System.out.println(stu.getName()+",---");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        HibernateUtil.close(session);
    }
    try {
        session = HibernateUtil.openSession();
        /**
         * 上一个Session已经关闭,此时又得重新取Student
         */
        Student stu = (Student)session.load(Student.class, 1);
        System.out.println(stu.getName()+",---");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        HibernateUtil.close(session);
    }
}


典型的N+1问题


如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql

在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息,这就是典型的N+1问题

@Test
public void test02() {
    Session session = null;
    try {
        session = HibernateUtil.openSession();
        Iterator<Student> stus = session.createQuery("from Student")
            .setFirstResult(0).setMaxResults(50).iterate();
        for(;stus.hasNext();) {
            Student stu = stus.next();
            System.out.println(stu.getName());
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        HibernateUtil.close(session);
    }
}


存在iterator的原因是

有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来,而是要iterator仅仅只会查询id,此时所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取。如下例:

/**
 * 此时会发出一条sql取出所有的学生信息
 */
session = HibernateUtil.openSession();
List<Student> ls = session.createQuery("from Student")
    .setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stus = ls.iterator();
for(;stus.hasNext();) {
    Student stu = stus.next();
    System.out.println(stu.getName());
}
/**
 * 使用iterate仅仅只会去Student的id,
 * 此时Student的数据已经在缓存中,所以不会在出现N+1
 */
stus = session.createQuery("from Student")
        .setFirstResult(0).setMaxResults(50).iterate();
for(;stus.hasNext();) {
    Student stu = stus.next();
    System.out.println(stu.getName());
}

如果上面不使用iterate,而直接使用list,会查出很多数据

/**
 * 此时会发出一条sql取出所有的学生信息
 */
session = HibernateUtil.openSession();
List<Student> ls = session.createQuery("from Student")
        .setFirstResult(0).setMaxResults(50).list();
Iterator<Student> stus = ls.iterator();
for(;stus.hasNext();) {
    Student stu = stus.next();
    System.out.println(stu.getName());
}
/**
 * 会发出SQL取完整的学生对象,占用内存相对较多
 */
ls = session.createQuery("from Student")
        .setFirstResult(0).setMaxResults(50).list();
stus = ls.iterator();
for(;stus.hasNext();) {
    Student stu = stus.next();
    System.out.println(stu.getName());
}


本文链接:hibernate一级缓存及N+1问题,领悟书生学习笔记,转载请注明出处http://www.656463.com/article/419

相关问答

更多
  • spring只是把它的hibernate的sessionfactory封装了一下,而session还是由hibernate管理的 所以说有没spring hibernate对一级缓存还是一样做的
  • Hibernate的一级缓存其实就是Session内置的一个Map,用来缓存它操作过的实体对象,对象的主关键字ID是Map的key,实体对象就是对应的值。所以,一级缓存是以实体对象为单位进行存储的,访问时也是以实体为单位的(直接访问属性是不能使用缓存的),并且要求使用主关键字ID来进行访问。 一级缓存是由Session提供的,所以它只存在于Session的生命周期中,当程序调用save(),update(),saveorupdate()等方法以及调用查询接口list,filter,iterate时,如果se ...
  • 缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。 缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。 Hibernate的缓存包括Session的缓存和SessionFactory的缓存,其中Se ...
  • 把它归结为1个查询是非常困难的(即我不知道一个可移植的解决方案),但将其归结为2个查询(不论n)是非常简单的: Criteria criteria = this.getSession().createCriteria(Mother.class); criteria.addOrder(Order.asc("title")) .setMaxResults(details.getMaxRows()) .setFirstResult(details.getStartResult()) .se ...
  • 1.1)一级缓存 一级缓存总是与Session对象关联。 默认情况下,Hibernate使用此缓存。 在这里,它处理一个事务,一个又一个,意味着不会处理一个事务多次。 主要是减少了在给定事务中需要生成的SQL查询的数量。 这不是在事务中完成每次修改之后进行更新,而是仅在事务结束时更新事务。 1.2)二级缓存 第二级缓存始终与Session Factory对象关联。 在运行事务时,在它之间加载会话工厂级别的对象,以便这些对象将可用于整个应用程序,而不会绑定到单个用户。 由于对象已经加载到缓存中,所以每当查询返 ...
  • 是。 第一级缓存仅用于会话。 第二个getList()调用将转到数据库。 编辑:第二个getList()调用将转到数据库,在第二级缓存的情况下,以及由mR_fr0g在他的答案中指出。 Yes. The 1st level cache is for the session only. The second getList() call will go to the database. EDIT: The second getList() call will go to the database, in the ...
  • 您是否只是为了自己的利益和学习而使用这些信息? 就我所知,在任何一种标准的JPA批准方法中,您将无法访问这些信息。 但是,如果你正在使用Hibernate,你可以设置一个断点并深入研究Hibernate的PersistenceContext.java实现,它被称为StatefulPersistenceContext.java。 应该有一个实体和集合的地图加载。 您当然需要Hibernate源代码。 这些字段的名称应该是...... private Map entitiesByKey; private Map ...
  • 不,Hibernate没有做任何事情来同步会话缓存中的实体的状态与数据库,除非你明确地请求它。 通常这不是问题,因为活动工作通常发生在事务内部,并且事务内的操作不应该看到其他并发事务所做的更改(尽管详细信息取决于隔离级别)。 因此,在这种情况下,Hibernate的行为补充了事务隔离的典型语义。 当实体的状态需要显式同步以反映在同一事务内进行的更改时,也可能会出现这种情况。 这可能是由批量更新查询或数据库触发器的执行引起的。 在这种情况下,您需要通过调用refresh()明确请求这样的同步。 No, Hib ...
  • 不 - 我相信你不能覆盖EAGER映射,既不使用查找,查询也不能使用条件查询。 例如,请参阅StackOverflow线程 。 覆盖LAZY虽然容易。 hbm.xml文件,fetch =“select”表示所有关系表的一对多 ...似乎是一个糟糕的默认选择 - 这就是为什么相反的是Hibernate自己的默认选择。 通常,在映射中保持低位和懒惰是一种很好的做法,并在您渴望时使用查询。 No - I believe you cannot override EAGER mappings, neither usi ...
  • 您需要使用@Transactional注释测试方法或使用Spring TransactionTemplate 。 即使检索实体不需要事务 ,在一个实体中对多个调用进行分组也会明确指定持久性上下文边界(它应该从哪里开始以及它应该在哪里结束)。 要复制第一级缓存,您需要在整个测试方法中使用相同的UnitOfWork,并使用@Transactional注释测试将启动一个事务(一个绑定到当前执行的持久性上下文事务的Spring逻辑事务,该事务与实际的物理数据库事务相关) 。 当测试方法结束时,事务通常会回滚,因此不 ...