看Hibernate源码 002 - 二级缓存

废话不说,今天花了一个下午的时间, 专门啃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源码 002 - 二级缓存_第1张图片
 
图1

我最初的想法是认为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画出来. 如果用一些工具的逆向工程, 我感觉就是喝了白开水, 看过就完事, 一点印象都没有. 不过类层次关系比较复杂, 我只好以缩略图的方式贴上来.


看Hibernate源码 002 - 二级缓存_第2张图片

图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()的话题似乎总是面试的时候用人单位关心的问题. 我觉得如果能从源码的角度去分析一下, 可能更加清晰一些.

你可能感兴趣的:(数据结构,Hibernate,cache,面试,UML)