Hibernate3.6 缓存

 

一级缓存

    一级缓存和get,load,save,iterator都有关系。

    使用HQL,SQL进行属性查询时,跟一级缓存无关。

    一级缓存的生命周期是事务

 

二级缓存

        Hibernate的二级缓存本质上就是存储对象实例,对象的id作为key,使用二级缓存就是按照id去加载。

        注意查询缓存的配置方法。网上很多文章都是试验说这个用不了缓存那个用不了的,就下结论说用不了,实际上多是配置有问题。我是结合了源代码和实验来看的。

 

查询缓存

    如果想使用query cache我们需要配置 hibernate.cfg.xml文件:


<class ......>

   <property nam="hibernate,cache.use_query_cache">True</property>

</class>

 
   在编码当中使用:Query.setQueryCache(true);即可。

 

 

Get和Load

session的load和get方法最底层都是调用org.hibernate.event.def.DefaultLoadEventListene 的dooad方法。

(题外话:load(entityame,id)方法如果找不到对象,则抛出异常,load(entityObject,id)实际上是重新加载对象,而get方法如果找不到则会返回null)

 

结论

 

1 通过下面代码可以看到先加从一级缓存加载,再从二级缓存加载,如果都没有则从数据库加载。  

2 Get和Load都跟查询缓存无关

 

但是Get和Load是不同的,Get方法会调用:

 

protected Object doLoad(
			final LoadEvent event,
			final EntityPersister persister,
			final EntityKey keyToLoad,
			final LoadEventListener.LoadType options) {

		if ( log.isTraceEnabled() ) {
			log.trace(
					"attempting to resolve: " +
					MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
				);
		}

		Object entity = 





loadFromSessionCache





( event, keyToLoad, options );
		if ( entity == REMOVED_ENTITY_MARKER ) {
			log.debug( "load request found matching entity in context, but it is scheduled for removal; returning null" );
			return null;
		}
		if ( entity == INCONSISTENT_RTN_CLASS_MARKER ) {
			log.debug( "load request found matching entity in context, but the matched entity was of an inconsistent return type; returning null" );
			return null;
		}
		if ( entity != null ) {
			if ( log.isTraceEnabled() ) {
				log.trace(
						"resolved object in session cache: " +
						MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory()  )
					);
			}
			return entity;
		}

		entity = 





loadFromSecondLevelCache





(event, persister, options);
		if ( entity != null ) {
			if ( log.isTraceEnabled() ) {
				log.trace(
						"resolved object in second-level cache: " +
						MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
					);
			}
			return entity;
		}

		if ( log.isTraceEnabled() ) {
			log.trace(
					"object not resolved in any cache: " +
					MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
				);
	}
	return 





loadFromDatasource





(event, persister, keyToLoad, options);
	}





 

  而Load如果使用延迟加载的化,那么则会生成proxy。

  这是由于LoadType的不同:

 

public static final LoadType GET = new LoadType("GET")
			.setAllowNulls(true)
			.setAllowProxyCreation(false)
			.setCheckDeleted(true)
			.setNakedEntityReturned(false);
	
	public static final LoadType LOAD = new LoadType("LOAD")
			.setAllowNulls(false)
			.setAllowProxyCreation(true)
			.setCheckDeleted(true)
			.setNakedEntityReturned(false);
 

  其中AllowProxyCreation在Get中设置为false,Load设置为POST。

所以对于Get而言,对延迟加载无效,直接查询,然后按顺序从Session缓存,二级缓存,数据库中读取;Load,则使用延迟加载。


  List和Find

find底层调用的就是list。

1 当开启查询缓存时,二级缓存才能派上用场,过程如下:

 1) 第一次执行sql时,会从数据库中获得数据,然后将数据放入到二级缓存中,将sql语句,查询参数(位置,类型),以及最大行数信息做成key放到查询缓存中,并缓存这个key对应的所有entity的id

 2) 当第二次执行这个sql时,发现查询缓存里有,则调用load方法,按查询缓存里的id一个一个去load对象。可见,如果只开了查询缓存没开二级缓存就惨了,上面已经说了,load会先到一二级缓存里去找,没有再到数据库找,如果没开二级缓存或者被flush掉了,意味着每个entity都要到数据库里去找,速度更慢

 

2 如果没有开启查询缓存,而开启了二级缓存呢?那么每次查询都会从数据库中获得,然后flush二级缓存。

3 如果都没开启,那么就只是查。

 

由上面还可以推出load和get都可以获得list之后带来的二级缓存中的对象。

 

 

Hibernate官方文档 写道
大多数查询并不会从缓存中获得什么好处,所以默认查询是不进行缓存的。要进行缓存,调用 Query.setCacheable(true)。这个调用会让查询在执行时去从缓存中查找结果,或者把结果集放到缓存去。
如果你要对查询缓存的失效政策进行精确的控制,你必须调用Query.setCacheRegion()来为每个查询指定一个命名的缓存区域。

 

 

List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
    .setEntity("blogger", blogger)
    .setMaxResults(15)
    .setCacheable(true)
    .setCacheRegion("frontpages")
    .list();

 

  QBC 与QBE

  底层调用来session的list方法。

 

  代码调用的过程如下:

  • org.hibernate.impl.SessionImpl

 

List





 





list





(CriteriaImpl





 criteria





)

   根据criteria获得entity的名字或class名字创建一个implementors字符串数组,然后为每个implementor创建一个CriteriaLoader,然后循环loader调用list方法。

  • org.hibernate.loader.Loader
    loader的list方法会对cache(这里指查询缓存)作出判断,走连个不同的分支:
   
		final boolean cacheable = factory.getSettings().isQueryCacheEnabled() &&

		queryParameters.isCacheable();

		if ( cacheable ) {

			return listUsingQueryCache( session, queryParameters, querySpaces, resultTypes );
		}
		else {
			return listIgnoreQueryCache( session, queryParameters );
 

		}
   如果是listIgnoreQueryCahce这个分支 ,那么接下来就是走了doList,doList里面调用了doQuery,如下:







doQuery





    这个方法里面会根据传入的QueryParemeter,和session构建jdbc的PreparedStatement,然后获得jdbc ResultSet,再迭代ResultSet获得hibernate的Row,放入到list返回。

   在调用getResultList从返回的list里获得结果返回。

   整个过程即:

 

	private List  listIgnoreQueryCache(SessionImplementor session, QueryParameters queryParameters) { 

		return getResultList( doList( session, queryParameters ), queryParameters.getResultTransformer() ); 

	}
 

 

    如果是走listUsingQueryCache这个分支 ,则先调用

getResultFromQueryCache




  

  如果获得的result为空,则跟上一种情况相同,执行doList,不过会把结果放入到queryCache中,然后调用调用getResultList从返回的list里获得结果返回。

 

   刚才说的Loader是个基类,它有两个子类,一个是CustomLoader,一个是BasicLoader,后者还有三个实现子类:org.hibernate.hql.classic.QueryTranslatorImpl, org.hibernate.loader.hql.QueryLoader, org.hibernate.loader.OuterJoinLoader,

,最后这个还有四个子类。

   对于缓存这块,它们都没有做更多事情。

 

查询缓存

 读取查询缓存是调用来QueryCahce这个接口,hibernate自己的标准实现类是org.hibernate.cache.StandardQueryCache

 

Iterator

    这个方法会先用一个sql把所有的id都查出来,然后一个一个load,这就可以利用二级缓存。
    可以配合

 

  1. session.evict(youObject);  
  2. sessionFactory.evice(YouObject.class, youObject.getId());  

 

   清除session缓存和二级缓存。

关于使用Hibernate API修改数据库

    一级缓存

 

     在save时首先会到session缓存(也就是一级缓存)中去看看这个对象是不是已经是持久态,如果是就不调用insert方法,否则insert之后放到session缓存中。

       二级缓存

    那么跟二级缓存的关系呢?

    简单的说,如果使用主键删除修改增加,比如save,update方法,那么就会更新缓存,改了哪个更新哪个

   如果使用HQL,那就通通干掉了。(网上说法不一,也有说不会更新二级缓存,我也没看到源代码,自己做实验把)

     今天看了一下debug信息,使用saveOrUpdate方法,其不会更新二级缓存,而是在load时才会将结果放入到二级缓存中:

DEBUG adding entity to second-level cache:

       查询缓存

 

          只有有更改,查询缓存全被干掉。也有地说是跟这个表有关的查询缓存才会被干掉,这个还得自己试验一下,也没跟到相应的代码。

 

   不通过Hibernate API更新数据库对缓存的影响

 

   自然Hibernate不知到数据库变了,那么就只能通过下面的缓存API 搞定来

   对于二级缓存就跟介绍Iterator时说的那样清除缓存。

 

    对于查询缓存,就需要执行QueryCache的clear方法才能避免脏数据,这时就会清除所有的缓存,也就是那堆id,如果想精细控制,就要用CacheRegion。实际上调用sessionFactory里的evictQueries。

 

 

参考资料

虽然是参考资料,但是参考资料上说的也不都对,所以有冲突的地方,以我的为准

Hibernate缓存原理  http://myoraclex.blog.51cto.com/2288027/413183

 

Hibernate CRUD,帖子中有很多错误  http://developer.51cto.com/art/200909/154150.htm

 

Hiberante 查询缓存的使用条件 http://www.blogjava.net/os586/archive/2006/07/25/60019.html

 

  总结了一下各种缓存 http://www.iteye.com/topic/249465

  这个帖子对CRUD的缓存情况说的比较好http://elf8848.iteye.com/blog/805351

 

有关Get,Load的讲解,细微之处还需进一步验证http://fantasyyong.iteye.com/blog/146685

你可能感兴趣的:(hibernate3)