Hibernate之一级缓存案例探索

前言

我们学过jdbc都知道,jdbc执行数据库操作的效率非常之快,而Hibernate相对来讲有那么多配置文件需要加载,并且通过代码封装了底层的sql查询语句,间接来进行访问数据库,所以相对来讲,会慢一点。那么Hibernate框架如何通过自身来进行优化数据库操作的呢,让我们先来看看一级缓存吧。

缓存

首先说,为什么要用缓存?
在实际开发中,将会频繁的进行数据库访问操作,那么如果在访问数据库之前,先从缓存中进行查询,如果没有,再去从数据库中找,有了这么一层缓存机制,会大大的减少对数据库的访问次数!从而提升hibernate的执行效率!

Hibernate中缓存分类:
一级缓存
二级缓存
本文先介绍一级缓存。

一级缓存

1)Hibenate中一级缓存,也叫做session的缓存,它可以在session范围内减少数据库的访问次数! 只在session范围有效! Session关闭,一级缓存失效!
2)当调用session的save/saveOrUpdate/get/load/list/iterator方法的时候,都会把对象放入session的缓存中。
3)Session的缓存由hibernate维护, 用户不能操作缓存内容; 如果想操作缓存内容,必须通过hibernate提供的evit/clear方法操作。

特点:
只在(当前)session范围有效,作用时间短,效果不是特别明显!
在短时间内多次操作数据库,效果比较明显!

简单测试:

@Test
    public void testCache() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();
        User user = null;
        // 查询
        user = (User) session.get(User.class, 5);// 先检查缓存中是否有数据,如果有则不查询数据库,直接从缓存中获取
        user = (User) session.get(User.class, 5);// 先检查缓存中是否有数据,如果有则不查询数据库,直接从缓存中获取

        session.getTransaction().commit();
        session.close();
    }

说明:本次案例将打印出只有一条数据库sql查询语句,说明查询会先从session缓存(一级缓存)中查询,如果有数据,则直接拿出,如果没有,则需要执行sql语句访问数据库。

再来看一组代码

@Test
    public void testCache() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();
        User user = null;
        // 查询
        user = (User) session.get(User.class, 5);// 先检查缓存中是否有数据,如果有不查询数据库,直接从缓存中获取
        user.setName(“张三”);
        user.setName(“李四”);     
        session.getTransaction().commit();
        session.close();
    }

说明:数据库执行两条sql语句,一个select,一个update。因为在执行setName方法时,先从缓存中找到该对象,如果有,就直接修改缓存中的数据,然后,在执行下一行代码,发现还是修改,则继续修改缓存中的数据,直到commit方法事务提交了,才将缓存清空同步到数据库中,而不会直接去访问数据库。但是如果缓存中没有该数据,一定会访问数据库。

操作缓存相关几个方法的作用:

session.flush(); 让一级缓存与数据库同步
session.evict(arg0); 清空一级缓存中指定的对象,参数为对象
session.clear(); 清空一级缓存中缓存的所有对象

测试每一个方法:

    @Test
    public void flush() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();

        User user = null;
        user = (User) session.get(User.class, 5);
        user.setUserName("Jack");
        // 缓存数据与数据库同步
        session.flush();

        user.setUserName("Jack_new");

        session.getTransaction().commit();  // session.flush();
        session.close();
    }

上面代码会执行两条update语句。因为在第一个set,或设置缓存中的数据,之后执行了flush方法,会把当前session缓存直接同步到数据库中。然后再set,然后commit方法执行,还会再执行一次update语句。这个就是flush的用法。

@Test
    public void clear() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();

        User user = null;
        // 查询
        user = (User) session.get(User.class, 5);
        // 清空缓存内容 
        session.clear(); // 清空所有

        user = (User) session.get(User.class, 5);


        session.getTransaction().commit();  // session.flush();
        session.close();
    }

说明:会执行两条select查询语句。因为clear方法,将会清空session缓存中的所有数据。

@Test
    public void clear() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();

        User user = null;
        // 查询
        user = (User) session.get(User.class, 5);
        // 清空缓存内容 
//      session.clear(); // 清空所有
        session.evict(user);// 清除指定

        user = (User) session.get(User.class, 5);


        session.getTransaction().commit();  // session.flush();
        session.close();
    }

说明:也是两条select查询语句。因为evict()方法可以清空缓存中指定的user对象。

那么学了这三个方法在什么情况用上面方法?
会在批量操作使用:
Session.flush(); // 先与数据库同步
Session.clear(); // 再清空一级缓存内容
保证缓存中的数据不会过多。

附:面试题一

不同的session是否会共享缓存数据?
不会。每个session有自己的缓存区域,只能维护自己的缓存区。

测试:

@Test
    public void sessionTest() throws Exception {
        Session session1 = sf.openSession();
        session1.beginTransaction();
        Session session2 = sf.openSession();
        session2.beginTransaction();

        // user放入session1的缓存区
        User user = (User) session1.get(User.class, 1);
        // user放入session2的缓存区
        session2.update(user);

        // 修改对象
        user.setUserName("New Name");  // 2条update



        session1.getTransaction().commit();  // session1.flush();
        session1.close();
        session2.getTransaction().commit();  // session2.flush();
        session2.close();
    }
}

将user对象分别放入session1和session2缓存中,然后修改对象,如果共享同一份缓存去,那么修改对象,将只会update一条sql语句。但是实践证明,会产生两条sql修改语句。那么也就是说,不同session维护了不同的缓冲区,不能共享数据,执行了两次修改对象的操作。

附:面试题二

前面说了操作缓存的方法有list和iteratior方法,那么list与iterator查询的区别?
list()
一次把所有的记录都查询出来,
会放入缓存,但不会从缓存中获取数据
Iterator
N+1查询; N表示所有的记录总数
即会先发送一条语句查询所有记录的主键(1),
再根据每一个主键再去数据库查询(N)!
会放入缓存,也会从缓存中取数据!

测试代码:

//1.  list 方法
    @Test
    public void list() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();
        // HQL查询
        Query q = session.createQuery("from User ");
        // list()方法
        List<User> list = q.list();

        for (int i=0; i<list.size(); i++){
            System.out.println(list.get(i));
        }

        session.getTransaction().commit();  
        session.close();
    }

    //2. iterator 方法
    @Test
    public void iterator() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();
        // HQL查询
        Query q = session.createQuery("from User ");
        // iterator()方法
        Iterator<User> it = q.iterate();
        while(it.hasNext()){
            // 得到当前迭代的每一个对象
            User user = it.next();
            System.out.println(user);
        }



        session.getTransaction().commit();  
        session.close();
    }


    //3. 缓存
    @Test
    public void cache() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();

        /**************执行2次list*****************
        Query q = session.createQuery("from User");
        List<User> list = q.list();      // 【会放入?】
        for (int i=0; i<list.size(); i++){
            System.out.println(list.get(i));
        }
        System.out.println("=========list===========");
        list = q.list();                // 【会放入?】
        for (int i=0; i<list.size(); i++){
            System.out.println(list.get(i));
        }

        /**************执行2次iteator******************/
        Query q = session.createQuery("from User ");
        Iterator<User> it = q.iterate();        // 【放入缓存】
        while(it.hasNext()){
            User user = it.next();
            System.out.println(user);
        }
        System.out.println("==========iterate===========");
        it = q.iterate();                       // 【也会从缓存中取】
        while(it.hasNext()){
            User user = it.next();
            System.out.println(user);
        }

        session.getTransaction().commit();  
        session.close();
    }

    // 测试list方法会放入缓存
    @Test
    public void list_iterator() throws Exception {
        Session session = sf.openSession();
        session.beginTransaction();

        // 得到Query接口的引用
        Query q = session.createQuery("from User ");

        // 先list  【会放入缓存,但不会从缓存中获取数据】
        List<User> list = q.list(); 
        for (int i=0; i<list.size(); i++){
            System.out.println(list.get(i));
        }

        // 再iteraotr  (会从缓存中取)
        Iterator<User> it = q.iterate();
        while(it.hasNext()){
            User user = it.next();
            System.out.println(user);
        }

        session.getTransaction().commit();  
        session.close();
    }
}

将上面的代码跑一遍,然后观察sql语句的数量,将会明白list和iterator的区别在哪了。这里就不再赘述,请小伙伴们自行验证吧。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值