废话不说,今天花了一个下午的时间, 专门啃Hibernate的二级缓存部分. Hibernate要支持好几种缓存, 那么它肯定封装了各种不同的缓存策略, 根据不同的缓存产品(如EHCache,Jboss Cache)相应处理.
我手上的源码版本是Hibernate-3.3.1GA, 按照org.hibernate.cache包里面的说明文件package.html的说明, 这个版本相比以前, 已经不再使用org.hibernate.cache.Cache/CacheProvider接口, 转而使用了org.hibernate.cache.Region/RegionFactory接口, 并且每个Region独立创建自己对缓存的访问策略. (Hibernate为每个实体类创建一个Region作为缓存区, 默认情况下使用类的全路径名做为这个Region的名称)
我个人认为, 看代码应该要带着自己的问题去跟踪, 这样才能做到线索清晰, 目的明确. Hibernate的二级缓存有实体类的缓存和集合的缓存, 下面以实体类的缓存为例, 跟踪Hibernate的二级缓存处理过程.
* 问题1 , 既然Hibernate使用二级缓存, 那么他的二级缓存是什么时候创建的?
答案很简单, 是在SessionFactoryImpl实例化的时候创建的. org.hibernate.cache.RegionFactory有个方法 public EntityRegion buildEntityRegion(String regionName, Properties properties, CacheDataDescription metadata) throws CacheException; 顾名思义, 是用来创建缓存区的, 跟踪一下什么方法调用的就行.
从SessionFactoryImpl 244行开始有一段:
我最初的想法是认为Hibernate在我们使用到某个类的时候(比如通过session.load(class,id)方法)才会创建缓存区并且 加载缓存对象, 这样可能更符合"懒"加载的特性. 这里我不是很明白为什么Hibernate提前初始化了这些缓存区. 不过, 实际情况中, 很多的应用都是在一开始就加载业务数据缓存, 比如角色定义, 规则, 模板等等的对象. 相对这些操作而言, Hibernate创建缓存区并不算大的开销.(我见过某些系统初始加载耗时半个钟)
* 问题二 , Hibernate创建缓存区的时候, 里面发生了什么事情?
Hibernate的作者说"创建SessionFactory的代价非常昂贵, 而创建Session的代价很低". 此话至少前半句不假. 看上面的代码, 就知道实例化的时候创建缓存区和对应的访问策略, 如果实体类超过100个, 就要创建100个缓存区和策略, 相比创建一个Session, 确实就有些耗费不起. 我们跟踪到settings.getRegionFactory().buildEntityRegion(..)方法内部看看发生了什么.
1) buildEntityRegion() 方法
org.hibernate.cache.RegionFactory是一个接口, 它的实现类有两个. org.hibernate.cache包的 NoCachingRegionFactory和RegionFactoryCacheProviderBridge. NoCachingRegionFactory是一个不提供缓存的实现, RegionFactoryCacheProviderBridge则是正常使用缓存的实现. 看它的buildEntityRegion()方法:
public EntityRegion buildEntityRegion(
String regionName,
Properties properties,
CacheDataDescription metadata) throws CacheException {
return new EntityRegionAdapter( cacheProvider.buildCache( regionName, properties ), settings, metadata );
}
很简单, 返回一个EntityRegionAdapter对象. 这个对象需要三个参数:Cache, Settings, CacheDataDescription. Cache对象由cacheProvider.buildCache( regionName, properties )代劳, 其他对象则是SessionFactory提供的.
此处有两个奇怪的地方, 为什么是EntityRegionAdapter? 一般而言, adapter模式是为协调两种不同结构的对象而设计的, 其次, 这里涉及到了Cache, CacheProvider接口, Hibernate在org.hibernate.cache包的说明里面已经声明Cache, CacheProvider接口作废. 显然, 结论只能是, Hibernate用来一套新的API替代了原来的API, 但是接口下面的实现, 仍旧依赖原来的实现!!
于是, 花点时间, 先把org.hibernate.cache包地下的类的结构搞清楚. 我的方法比较笨, 就是一个一个类去看,然后用UML画出来. 如果用一些工具的逆向工程, 我感觉就是喝了白开水, 看过就完事, 一点印象都没有. 不过类层次关系比较复杂, 我只好以缩略图的方式贴上来.
图2
很明显, Hibernate的cache源码分类五块:
第一块是访问策略, 在org.hibernate.cache.access包内. 几个类的名字很清晰的表明了意思: AccessType -- 访问类型, read-only, read-write等. EntityRegionAccessStrategy -- 实体类访问策略. 其实就是class的缓存访问策略. CollectionRegionAccessStrategy -- 集合的访问策略. 其实就是class内的集合的缓存访问策略. 对象的获得都是通过这两种方式来代理给Cache对象处理的.
第二块: 代理层, 在org.hibernate.cache.impl.bridge包内. 可以见到中间有一块, 全是xxxAdapter, 分别用于代理实体, 集合, 查询结果的缓存, 透过org.hibernate.cache.CacheConcurrencyStrategy给Cache对象处理请求.
第三块: 原来的缓存实现部分. 在org.hibernate.cache包内.主要由org.hibernate.cache.CacheConcurrencyStrategy/ Cache/ CacheProvider三个接口组成.
第四块: 新的缓存API, 在org.hibernate.cache包内.包括几个主要的接口: org.hibernate.cache.Region/ TransactionalDataRegion/ EntityRegion/ CollectionRegion/ QueryResultRegion/ RegionFactory/ CacheKey. 在CollectionRegion和EntityRegion两个类上分别创建了CollectionRegionAccessStrategy和 EntityRegionAccessStrategy.
第无块: 缓存对象入口. 在org.hibernate.cache.entry包内. 这些对象是SessionFactory和二级缓存打交道的封装. 在每个对象被放入缓存的时刻, 会被封装到一个Entry内, 透过Region来传递给真正的Cache Provider(如EHCache). 但是, 这里我们并没有看到具体的Entry从Region传递到EHCache的代码, 这个版本里已经把原来的org.hibernate.cache.EHCacheProvider分离出去了,作为一个单独的hibernate- ehcache.jar包.
有了这个图, 缓存的结构已经很清晰了. 上面代码中的cacheProvider.buildCache( regionName, properties ), 其实就是按照Hibernate.cfg.xml中提供的CacheProvider创建的Cache. 看一下我们很熟悉的配置
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.use_query_cache">false</property>
对应的EHCacheProvider代码:
import net.sf.ehcache.CacheManager;
...
public class EhCacheProvider implements CacheProvider {
private CacheManager manager;
...
public Cache buildCache(String name, Properties properties) throws CacheException {
try {
net.sf.ehcache.Cache cache = manager.getCache(name);
if (cache == null) {
log.warn("Could not find configuration [" + name + "]; using defaults.");
manager.addCache(name);
cache = manager.getCache(name);
log.debug("started EHCache region: " + name);
}
return new EhCache(cache);
}
catch (net.sf.ehcache.CacheException e) {
throw new CacheException(e);
}
}
...
}
基本上, Cache接口提供的方法就是get(), put(), remove()之类的, 和java.util.Map接口类似, 另外还提供lock(), unlock()方法. 到这里, Hibernate对于实体类的缓存操作都交给EHCache去打理了.
创建好实体类缓存区后, 谁来和它打交道呢? 答案就是EntityRegionAccessStrategy. 类层次结构见上图2. 从图1中就能发现, SessionFactoryImpl在创建好缓存区后, 就开始使用EntityRegion.buildAccessStrategy()方法创建EntityRegionAccessStrategy.
2) buildAccessStrategy () 方法
我们已经知道对应EntityRegion接口的实现类是EntityRegionAdapter, 看看它的代码, 很简单
public class EntityRegionAdapter extends BaseTransactionalDataRegionAdapter implements EntityRegion {
private static final Logger log = LoggerFactory.getLogger( EntityRegionAdapter.class );
public EntityRegionAdapter(Cache underlyingCache, Settings settings, CacheDataDescription metadata) {
super( underlyingCache, settings, metadata );
if ( underlyingCache instanceof OptimisticCache ) {
( ( OptimisticCache ) underlyingCache ).setSource( new OptimisticCacheSourceAdapter( metadata ) );
}
}
public EntityRegionAccessStrategy buildAccessStrategy (AccessType accessType) throws CacheException {
CacheConcurrencyStrategy ccs;
if ( AccessType.READ_ONLY.equals( accessType ) ) {
if ( metadata.isMutable() ) {
log.warn( "read-only cache configured for mutable entity [" + getName() + "]" );
}
ccs = new ReadOnlyCache();
}
else if ( AccessType.READ_WRITE.equals( accessType ) ) {
ccs = new ReadWriteCache();
}
else if ( AccessType.NONSTRICT_READ_WRITE.equals( accessType ) ) {
ccs = new NonstrictReadWriteCache();
}
else if ( AccessType.TRANSACTIONAL.equals( accessType ) ) {
ccs = new TransactionalCache();
}
else {
throw new IllegalArgumentException( "unrecognized access strategy type [" + accessType + "]" );
}
ccs.setCache( underlyingCache );
return new EntityAccessStrategyAdapter ( this, ccs, settings );
}
}
好戏发生在最后一行代码, 生成一个EntityAccessStrategyAdapter对象, 并由它去处理一个CacheConcurrencyStrategy , 而且CacheConcurrencyStrategy 包含了刚刚创建的Cache(其实就是EHCache对象). 这里的accessType对应着我们在xxx.hbm.xml中定义的<cache>元素, 例如
<hibernate-mapping package="work">
<class name="Comment" lazy="false">
<cache usage="read-write"/>
...
</hibernate-mapping>
* 问题三 . 当我们使用session去对实体类操作的时候, 二级缓存的部分发生了什么事情? 比如session.load(), session.get(), session.save(), session.delete(), session.evit()等方法
session.load(), session.get()的话题似乎总是面试的时候用人单位关心的问题. 我觉得如果能从源码的角度去分析一下, 可能更加清晰一些.