Hibernate缓存策略之一级缓存
缓存相当于Map结构,讲的是命中率,就像Entryset中的key和Value。
Hibernate中的缓存:
- 一级缓存,也叫session级别的缓存,缓存的是实体
- 二级缓存,是SessionFactory级别的缓存,缓存的也是实体
- 查询缓存,也是SessionFactory级别的缓存,它缓存的是普通结果集,但如果缓存的是实体则缓存实体的Id列表
session级别的缓存即一级缓存,不能被取消,它一直存在,随着session的关闭,缓存也会随之失效!sessionFactory级别的缓存是可以取消的,甚至不用。
现在让我们先来看看session级别的缓存,也就是一级缓存:
- 可以通过session的load/get操作加载实体对象,通过session的list/iterate查询实体对象,这四种操作都能把实体对象放入一级缓存
如:
//将发出SQL查询语句查询实体对象的数据 Person p1 = (Person)session.load(Person.class, 1); System.out.println(p1.getName()); //实体对象已经被缓存,所以下列操作不再发出SQL语句 Person p2 = (Person)session.load(Person.class, 1); System.out.println(p2.getName());
因此可以这样理解,持久化对象都在一级缓存里边,如果session关闭之后,那么当前session对象中的一级缓存已经被清空,这时候你再启动一个session的话,他还是会发送sql语句到内存中查找那个实体,因为当前的session对象已被一级缓存给清空了。
接着看看list和iterate操作:他们都能够缓存实体到一级缓存中
//下面这个操作,会导致hibernate发出SQL语句查询两个实体对象到一级缓存中 List persons = session.createQuery( "select p from Person p where p.id in (1,2)") .list(); for (Iterator iterator = persons.iterator(); iterator.hasNext();) { Person p = (Person ) iterator.next(); System.out.println(p.getName()); } //不会再发出查询语句 Person cp = (Person )session.load(Person .class, 1); System.out.println(cp.getName()); //实体对象已经被缓存,所以下列操作不再发出SQL语句 Person cp1 = (Person )session.get(Person .class, 1); System.out.println(cp1.getName()); //实体对象已经被缓存,所以下列操作不再发出SQL语句 Person cp2 = (Person )session.get(Person .class, 2); System.out.println(cp2.getName()); //因为本实体对象不在一级缓存中,所以下面将发出SQL语句查询实体对象 Person cp3 = (Person )session.get(Person .class, 3); System.out.println(cp3.getName());
具体的list和iterate区别见1+N文章!
- 接下来研究下一级缓存的管理:
清空一级缓存,表示这个对象不再是持久化对象了,他应该是个离线的对象。
session.clear()清空所有的一级缓存,session.evict()清楚一级缓存中某个实体缓存。
Person p = (Person)session.get(Person.class, 4); p.setName("士兵乙"); //清空一级缓存里面的所有的持久化对象! //session.clear(); //清楚一级缓存里面的某个持久化对象! session.evict(p); session.getTransaction().commit();
- 再看看下面更新关联丢失的解决之道:
//这是一个离线对象,假设这个对象从呈现层中传过来的! Person p = new Person(); p.setId(1); p.setQq("1234567"); p.setAddress("上海!"); p.setName("xxxxx"); //假设为了不丢失Person与Group对象之间的关联,我们先加载原来的Person Person oldp = (Person)session.get(Person.class, p.getId()); //把原来的关联关系,设置到新的对象中 p.setGroup(oldp.getGroup()); //更新新的对象! //update方法会把p对象放入一级缓存 //上面的get操作,把oldp对象也放入了一级缓存 //p对象和oldp对象,不是同一个对象 //但p对象和oldp对象具有相同的数据库标识! //所以下面的update操作,将抛出异常:org.hibernate.NonUniqueObjectException : a different object //with the same identifier value was already associated with the session翻译过来如下: //因为在Hibernate中,同一个session中,不允许存在两个具有相同数据库标识的同一种类型的实体对象 //session.update(p); session.getTransaction().commit();
那应该怎么解决呢?方法一:
//解决上述问题的方法之一:就是 //利用session一级缓存的管理机制,先把oldp对象从一级缓存中清除 session.evict(oldp); session.update(p);
解决它的第二个办法:
//解决上述问题的方法之二: //利用session中的merge方法来更新p对象! //merge方法,相当于把某个离线状态的对象拷贝到某个对应的持久化对象中 //相当于把p拷贝到oldP中,从而更新oldP中所有的属性 session.merge(p);
- 在批量插入数据时如有100万条数据时,这时不可能一次性把这么多数据全部持久化到数据库中,我们应当批量地插入,如100条的插入,当插入了100条后,即全部写进去后,也就是flush干净 了,这时候把一级缓存清空一下以便下一批数据的批量插入,从而保证缓存的可用,而不会导致一次性插入大量数据致使内存溢出的情况
for(int i=0; i<1000000000; i++){ Person p = new Person(); p.setAddress("xxx"); //...p.setXXX session.save(p); if(i % 100 == 0){ session.flush(); session.clear(); } }