Hibernate缓存策略之一级缓存

缓存相当于Map结构,讲的是命中率,就像Entryset中的key和Value。

Hibernate中的缓存:

  1. 一级缓存,也叫session级别的缓存,缓存的是实体
  2. 二级缓存,是SessionFactory级别的缓存,缓存的也是实体
  3. 查询缓存,也是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();
  1.  再看看下面更新关联丢失的解决之道:
//这是一个离线对象,假设这个对象从呈现层中传过来的!
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);
  1.  在批量插入数据时如有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();
	}
}
 

相关推荐