博客统计信息

51cto推荐博客
用户名:NightWolves
文章数:62
评论数:58
访问量:86466
无忧币:1349
博客积分:1894
博客等级:6
注册日期:2009-11-21

hibernate的一级缓存
2010-02-24 14:25:57
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://yangfei520.blog.51cto.com/1041581/278299
       Hibernate的一级缓存其实就是Session内置的一个Map,用来缓存它操作过的实体对象,对象的主关键字ID是Map的key,实体对象就是对应的值。所以,一级缓存是以实体对象为单位进行存储的,访问时也是以实体为单位的(直接访问属性是不能使用缓存的),并且要求使用主关键字ID来进行访问。
      
       一级缓存是由Session提供的,所以它只存在于Session的生命周期中,当程序调用save(),update(),saveorupdate()等方法以及调用查询接口list,filter,iterate时,如果session缓存中还不存在相应的对象,Hibernate会把该对象加入到一级缓存中,当Session关闭的时候该Session所管理的一级缓存也会立即被清除。当程序调用get(),load(),iterate(查询实体对象才支持一级缓存,查询普通属性则不支持一级缓存)时,Hibernate会先到缓存中去拿,如果缓存中已经存在目标对象,则直接拿来而不再查询数据库,否则,必须发出查询语句到数据库中查。
 
       对于一级缓存的使用,其实大多都是由Hibernate自动维护的,我们能做的是很少的,既不能卸载它,也不能对它进行任何的配置。但是,好在Hibernate给我们提供了两个方法可以对它进行简单的管理:session.clear(),session.evict() 。前者是清空一级缓存中所有的对象,后者是把某一个对象从一级缓存中清除。项目中,当需要进行大批量数据一次性更新时,在不知不觉中hibernate会占用大量内存,这时就应该阶段性地调用clear()方法来清空一级缓存中的对象,控制一级缓存的大小,以避免产生内存溢出的情况。
 
        如果数据量特别大,我们一般考虑采用jdbc实现,因为它不用把大批量的数据事先加载到内存中,然后再进行更新与修改。所以不会消耗大量内存。如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具等其它办法。
1.实体类:Student.java
public class Student {
  private Integer id;
  private String name;
  //一系列的setter.getter方法
}

2.映射文件:
  Student.hbm.xml
  <class name="com.sxt.hibernate.cache.entity.Student" table="sxt_hibernate_student">
    <id name="id" length="4">
      <generator class="native"></generator>
    </id>
    <property name="name" length="10"></property>
  </class>
 
3.Hibernate配置文件
  省略。
 
4.  测试方法:
  public static void main(String[] args) {
    Session session = null;
    Transaction t = null;
    try {
      session = HibernateUtils.getSession();
      t = session.beginTransaction();
      /**
        * 在同一个session中发出两次load查询
        */

/*      //如果前面没有用session操作过此student对象,在这里会发出sql语句.
      Student student = (Student)session.load(Student.class, 1);
      System.out.println("student.name=" + student.getName());
        
      //不会发出sql,因为load使用一级缓存
      student = (Student)session.load(Student.class, 1);
      System.out.println("student.name=" + student.getName());*/

        
      /**
        * 在同一个session中发出两次get查询
        */

/*      Student student = (Student)session.get(Student.class, 1);
      System.out.println("student.name=" + student.getName());
        
      //不会发出sql,因为get也使用一级缓存
      student = (Student)session.get(Student.class, 1);
      System.out.println("student.name=" + student.getName());*/

        
      /**
        * 在同一个session中发出两次iterate查询实体对象
        */

/*      Student student = (Student)session.createQuery("from Student s where s.id=1").iterate().next();
      System.out.println("student.name=" + student.getName());
        
      //会发出查询id的sql,不会发出查询实体对象的sql,因为iterate在查询实体对象时也使用缓存
      student = (Student)session.createQuery("from Student s where s.id=1").iterate().next();
      System.out.println("student.name=" + student.getName());*/

        
      /**
        * 在同一个session中发出两次iterate查询实体对象
        */

/*      //发出sql直接插name,不再插id
      //select student0_.name as col_0_0_ from sxt_hibernate_student student0_ where student0_.id=1
      String name = (String)session.createQuery("select s.name from Student s where s.id=1").iterate().next();
      System.out.println("student.name=" + name);
        
      //iterate查询普通属性,一级缓存不会缓存,所以发出sql
      //由此可见,一级缓存是缓存实体对象的
      name = (String)session.createQuery("select s.name from Student s where s.id=1").iterate().next();
      System.out.println("student.name=" + name);*/

        
      /**
        * 在同一个session中先save,在发出load查询save过的数据
        */

/*      Student stu = new Student();
      stu.setName("吴奇隆");
        
      Serializable id = session.save(stu);//主键生成方式采用native,是个序列化的id.
        
      //不会发出sql,因为save是使用缓存的.save之后,会把对象放到一级缓存中.
      //再load时,直接到一级缓存中去拿就可以了.
      Student student = (Student)session.load(Student.class, id);
      System.out.println("student.name=" + student.getName());*/

        
      /**
        * 向数据库中批量加入5000条数据
        */

/*      for (int i=0; i<5000; i++) {
        Student student = new Student();
        student.setName("stu_" + i);
        session.save(student);
        //每20条数据就强制session将数据持久化
        //同时清除缓存,避免大量数据造成内存溢出
        if ( i % 30 == 0) {
          session.flush();
          session.clear();
        }
      }*/

        
      /**
        * 在数据库中一次性更新大批量数据
        */

/*      Iterator<Student> students=session.createQuery("from Student s where s.id>100").iterate();
      while(students.hasNext()){
        Student stu =(Student)students.next();
        stu.setName("n_"+stu.getName());
        //将本批插入的对象立即写入数据库并释放内存
        session.flush();
        session.clear();
      }*/


      /**
        * Hibernate并不适合处理大批量的数据,通常我们都跳过Hibernate API,而直接采用JDBC API来做.
        */

/*      Connection conn =session.connection();
      PreparedStatement    pstmt = conn.prepareStatement("update sxt_hibernate_student set name='s'||substr(name,4,8) "+"where id >100");
      pstmt.executeUpdate();*/

        
      /**
        * 其实批处理大量数据更新最好的解决方法就是用创建存储过程,直接利用底层数据库.这样性能最好.
        */

      /*
        *create or    replace procedure StudentUpdate(s_id in number) as    
             *begin
                *update sxt_hibernate_student set name='n_'||name where id>s_id;
                *end;
        */

      Connection conn=session.connection();
                        String str="{call StudentUpdate(?)}";
                        CallableStatement cstmt= conn.prepareCall(str);
                        cstmt.setInt(1,100);
                        cstmt.executeUpdate();

      t.commit();
    } catch (Exception e) {
      e.printStackTrace();
      t.rollback();
    } finally {
      HibernateUtils.closeSession(session);
    }
    /**
     * 开启两个session中发出load查询
     */

/*    try{
      session=HibernateUtils.getSession();
      t=session.beginTransaction();
      Student student = (Student)session.load(Student.class, 1);
      System.out.println("student.name=" + student.getName());
    }catch(Exception e){
      e.printStackTrace();
      t.rollback();
    }finally{
      HibernateUtils.closeSession(session);
    }
    try{
      session=HibernateUtils.getSession();
      t=session.beginTransaction();
      //会发出查询语句,因为一级缓存时和session绑定的,每个session都有自己的一级缓存,不同session间不能共享一级缓存.
      //上一个session已经关闭了,所以此处还要发出查询语句.
      Student student = (Student)session.load(Student.class, 1);
      System.out.println("student.name=" + student.getName());
    }catch(Exception e){
      e.printStackTrace();
      t.rollback();
    }finally{
      HibernateUtils.closeSession(session);
    }*/

  }

本文出自 “夜狼” 博客,请务必保留此出处http://yangfei520.blog.51cto.com/1041581/278299

分享至
更多
一键收藏,随时查看,分享好友!
0人
了这篇文章
类别:hibernate技术圈()┆阅读()┆评论() ┆ 推送到技术圈返回首页

文章评论

 
2010-02-25 16:00:34
博主讲的是clear()清缓存的方法吧?
那能介绍下evict()吗?
博主回复:
2010-02-26 08:53:55
session的evict()方法作用是把指定对象从session缓存中清除,所以在使用的时候你要先给它传一个对象过去。如果该对象时持久化的,它会从缓存(entityEntries)中清除;如果不是持久化的,也不会报错。
但是在evict()和save()方法联合使用时容易报线程方面的异常。比如:
        Student stu=new Student();
        session.save();
              session.evict();
就会报错:org.hibernate.AssertionFailure: possible nonthreadsafe .....
这是因为事务提交,需要将所有缓存flush入数据库,Session启动一个事务,并按照insert,update,……,delete的顺序提交所有之前登记的操作(注意:所有insert执行完毕后才会执行update,这里的特殊处理也可能会将你的程序搞得一团糟,如需要控制操作的执行顺序,要善于使用flush),现在对象不在entityEntries中,但在执行insert的行为时只需要访问insertions就足够了,所以此时不会有任何的异常。异常出现在插入后通知Session该对象已经插入完毕这个步骤上,这个步骤中需要将entityEntries中对象的existsInDatabase标志置为true,由于对象并不存在于entityEntries中,此时Hibernate就认为insertions和entityEntries可能因为线程安全的问题产生了不同步。
所以我们在做此类操作时一定要清楚Hibernate什么时候会将数据flush入数据库,在未flush之前不要将已进行操作的对象从Session上拆离。解决办法是在save之后,添加session.flush。即
          Student stu=new Student();
                session.save();
                session.flush();
                session.evict();
谢谢光临,希望对你有所帮助,呵呵。

2010-02-26 09:07:46
谢谢博主!讲的很细!

 

发表评论            

【技术门诊】专家解析:软考重点难点及应试技巧
昵  称:
登录  快速注册
验证码:

请点击后输入验证码博客过2级,无需填写验证码

内  容: