一级缓存和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);即可。
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,则使用延迟加载。
find底层调用的就是list。
1 当开启查询缓存时,二级缓存才能派上用场,过程如下:
1) 第一次执行sql时,会从数据库中获得数据,然后将数据放入到二级缓存中,将sql语句,查询参数(位置,类型),以及最大行数信息做成key放到查询缓存中,并缓存这个key对应的所有entity的id
2) 当第二次执行这个sql时,发现查询缓存里有,则调用load方法,按查询缓存里的id一个一个去load对象。可见,如果只开了查询缓存没开二级缓存就惨了,上面已经说了,load会先到一二级缓存里去找,没有再到数据库找,如果没开二级缓存或者被flush掉了,意味着每个entity都要到数据库里去找,速度更慢
2 如果没有开启查询缓存,而开启了二级缓存呢?那么每次查询都会从数据库中获得,然后flush二级缓存。
3 如果都没开启,那么就只是查。
由上面还可以推出load和get都可以获得list之后带来的二级缓存中的对象。
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方法。
代码调用的过程如下:
List list (CriteriaImpl criteria )
根据criteria获得entity的名字或class名字创建一个implementors字符串数组,然后为每个implementor创建一个CriteriaLoader,然后循环loader调用list方法。
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
在save时首先会到session缓存(也就是一级缓存)中去看看这个对象是不是已经是持久态,如果是就不调用insert方法,否则insert之后放到session缓存中。
那么跟二级缓存的关系呢?
简单的说,如果使用主键删除修改增加,比如save,update方法,那么就会更新缓存,改了哪个更新哪个
如果使用HQL,那就通通干掉了。(网上说法不一,也有说不会更新二级缓存,我也没看到源代码,自己做实验把)
今天看了一下debug信息,使用saveOrUpdate方法,其不会更新二级缓存,而是在load时才会将结果放入到二级缓存中:
DEBUG adding entity to second-level cache:
只有有更改,查询缓存全被干掉。也有地说是跟这个表有关的查询缓存才会被干掉,这个还得自己试验一下,也没跟到相应的代码。
自然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