Hibernate源碼節選(一) Session的get與load

本文基於Hibernate 5.2

LoadEventListener.LoadType類

衆所周知Session中不同的查詢方式在細節上有所區別,如Session.get()查找緩存和數據庫,而Session.load()則查找緩存,如果緩存沒有該實體則返回一個代理,在使用實體其他字段時才查找數據庫。

LoadType類定義了這些區別。

public interface LoadEventListener extends Serializable {

    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 );
	
	//其他類型的LoadType
    
    public static final class LoadType {
		private String name;

		private boolean nakedEntityReturned;
		private boolean allowNulls;
		private boolean checkDeleted;
		private boolean allowProxyCreation;
    }
    
    //其他代碼
}

Session查詢

1.Session生成LoadEvent和LoadType

2.通知LoadListener

3.LoadListenr根據LoadType進行查找和返回結果

1.生成LoadEvent和LoadType

在源碼中,load與get方法都是委託給一個內部類IdentifierLoadAccessImpl來實現的

  • SessionImpl#load
public <T> T load(Class<T> entityClass, Serializable id) throws HibernateException {
	return this.byId( entityClass ).getReference( id );
}
  • SessionImpl#get
public <T> T get(Class<T> entityClass, Serializable id) throws HibernateException {
   return this.byId( entityClass ).load( id );
}
  • SessionImpl#byId
public <T> IdentifierLoadAccessImpl<T> byId(Class<T> entityClass) {
	return new IdentifierLoadAccessImpl<T>( entityClass );
}
  • IdentifierLoadAccessImpl類
private class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T> {
   private final EntityPersister entityPersister;// 映相當於實體映射配置文件
   private LockOptions lockOptions;
   private CacheMode cacheMode;
   
}

IdentifierLoadAccessImpl生成一個loadEvent並附帶特定的LoadType去觸發監聽器

  • IdentifierLoadAccessImpl#getReference(SessionImpl#load路線)
  public final T getReference(Serializable id) {
      CacheMode sessionCacheMode = getCacheMode();
      boolean cacheModeChanged = false;
      if ( cacheMode != null ) {
          // naive check for now...
          // todo : account for "conceptually equal"
          if ( cacheMode != sessionCacheMode ) {
              setCacheMode( cacheMode );//將session的cacheMode臨時改爲IdentifierLoadAccessImpl的
              cacheModeChanged = true;
          }
      }
  
      try {
          return doGetReference( id );//重點
      }
      finally {
          if ( cacheModeChanged ) {
              // change it back
              setCacheMode( sessionCacheMode );
          }
      }
  }
  • IdentifierLoadAccessImpl#doGetReference
protected T doGetReference(Serializable id) {
   if ( this.lockOptions != null ) {
      LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this );//生成一個LoadEvent
      fireLoad( event, LoadEventListener.LOAD );//以LOAD類型觸發監聽器
      return (T) event.getResult();
   }

   LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, SessionImpl.this );//生成一個LoadEvent
   boolean success = false;
   try {
      fireLoad( event, LoadEventListener.LOAD );//以LOAD類型觸發監聽器
      if ( event.getResult() == null ) {
         getFactory().getEntityNotFoundDelegate().handleEntityNotFound(
               entityPersister.getEntityName(),
               id
         );
      }
      success = true;
      return (T) event.getResult();
   }
   finally {
      afterOperation( success );
   }
}

Session.get這邊也大同小異,區別在LoadType爲GET類型

  • IdentifierLoadAccessImpl#load(SessionImpl#get路線)
public final T load(Serializable id) {
   CacheMode sessionCacheMode = getCacheMode();
   boolean cacheModeChanged = false;
   if ( cacheMode != null ) {
      // naive check for now...
      // todo : account for "conceptually equal"
      if ( cacheMode != sessionCacheMode ) {
         setCacheMode( cacheMode );
         cacheModeChanged = true;
      }
   }

   try {
      return doLoad( id );
   }
   finally {
      if ( cacheModeChanged ) {
         // change it back
         setCacheMode( sessionCacheMode );
      }
   }
}
  • IdentifierLoadAccessImpl#doLoad
protected final T doLoad(Serializable id) {
   if ( this.lockOptions != null ) {
      LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this );
      fireLoad( event, LoadEventListener.GET );//GET類型
      return (T) event.getResult();
   }

   LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, SessionImpl.this );
   boolean success = false;
   try {
      fireLoad( event, LoadEventListener.GET );//GET類型
      success = true;
   }
   catch (ObjectNotFoundException e) {
      // if session cache contains proxy for non-existing object
   }
   finally {
      afterOperation( success );
   }
   return (T) event.getResult();
}

2.通知Listener

最後Session.load和Session.get都會匯入fireLoad方法中

  • SessionImpl.fireLoad(LoadEvent event, LoadType loadType)
private void fireLoad(LoadEvent event, LoadType loadType) {
   checkOpenOrWaitingForAutoClose();
   checkTransactionSynchStatus();
   for ( LoadEventListener listener : listeners( EventType.LOAD ) ) {
      listener.onLoad( event, loadType );
   }
   delayedAfterCompletion();
}

EventType.LOAD表示這次是事件是查詢事件,還有很多種EventType,比如SAVE、UPDATE、FLUSH等。

3.LoadListenr根據LoadType進行查找和返回結果

  • DefaultLoadEventListener#onLoad

做一些檢查,重點是doOnLoad方法

public void onLoad(
    final LoadEvent event,
    final LoadEventListener.LoadType loadType) throws HibernateException {
	
    //根據event裏的EntityName,從工廠獲取持久器
    final EntityPersister persister = getPersister( event );

    if ( persister == null ) {
        throw new HibernateException( "Unable to locate persister: " + event.getEntityClassName() );
    }

    final Class idClass = persister.getIdentifierType().getReturnedClass();
    if ( idClass != null && !idClass.isInstance( event.getEntityId() ) ) {
        checkIdClass( persister, event, loadType, idClass );
    }

    doOnLoad( persister, event, loadType );
}
  • DefaultLoadEventListener#doOnLoad

從這裏開始,LoadType將發揮作用

private void doOnLoad(
    final EntityPersister persister,
    final LoadEvent event,
    final LoadEventListener.LoadType loadType) {

    try {
        final EntityKey keyToLoad = event.getSession().generateEntityKey( event.getEntityId(), persister );
        if ( loadType.isNakedEntityReturned() ) {
            //do not return a proxy!
            //(this option indicates we are initializing a proxy)
            event.setResult( load( event, persister, keyToLoad, loadType ) );
        }
        else { //LOAD和GET的isNakedEntityReturned均爲false,走這個else分支
            
            //return a proxy if appropriate
            if ( event.getLockMode() == LockMode.NONE ) {
                event.setResult( proxyOrLoad( event, persister, keyToLoad, loadType ) );
            }
            else {
                event.setResult( lockAndLoad( event, persister, keyToLoad, loadType, event.getSession() ) );
            }
        }
    }
    catch (HibernateException e) {
        LOG.unableToLoadCommand( e );
        throw e;
    }
}

依次分析load、proxyOrLoad、lockAndLoad三種情況

load

load不創建代理,它將嘗試去查找實體

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

    if ( event.getInstanceToLoad() != null ) {
        if ( event.getSession().getPersistenceContext().getEntry( event.getInstanceToLoad() ) != null ) {
            throw new PersistentObjectException(
                "attempted to load into an instance that was already associated with the session: " +
                MessageHelper.infoString(
                    persister,
                    event.getEntityId(),
                    event.getSession().getFactory()
                )
            );
        }
        persister.setIdentifier( event.getInstanceToLoad(), event.getEntityId(), event.getSession() );
    }

    final Object entity = doLoad( event, persister, keyToLoad, options );

    boolean isOptionalInstance = event.getInstanceToLoad() != null;

    //找不到實體且LoadType不允許空值時,要進行空值處理(GET允許空,LOAD不允許)
    if ( entity == null && ( !options.isAllowNulls() || isOptionalInstance ) ) {
        event.getSession()
            .getFactory()
            .getEntityNotFoundDelegate()
            .handleEntityNotFound( event.getEntityClassName(), event.getEntityId() );
    }
    else if ( isOptionalInstance && entity != event.getInstanceToLoad() ) {
        throw new NonUniqueObjectException( event.getEntityId(), event.getEntityClassName() );
    }

    return entity;
}

  • DefaultLoadEventListener#doLoad

真正查找實體的地方,依次在第一緩存(Session)、第二緩存和數據庫中查找。

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

    if ( traceEnabled ) {
        LOG.tracev(
            "Attempting to resolve: {0}",
            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 ( traceEnabled ) {
            LOG.tracev(
                "Resolved object in session cache: {0}",
                MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
            );
        }
        return entity;
    }

    //在第二緩存查找
    entity = loadFromSecondLevelCache( event, persister, keyToLoad );
    if ( entity != null ) {
        if ( traceEnabled ) {
            LOG.tracev(
                "Resolved object in second-level cache: {0}",
                MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
            );
        }
    }
    else {
        if ( traceEnabled ) {
            LOG.tracev(
                "Object not resolved in any cache: {0}",
                MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
            );
        }
        
        //從數據庫中查找
        entity = loadFromDatasource( event, persister );
    }

    if ( entity != null && persister.hasNaturalIdentifier() ) {
        event.getSession().getPersistenceContext().getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad(
            persister,
            event.getEntityId(),
            event.getSession().getPersistenceContext().getNaturalIdHelper().extractNaturalIdValues(
                entity,
                persister
            )
        );
    }


    return entity;
}

這裏留意一下查找Session緩存的細節。Session的緩存是其persistenceContext成員,我們後面會再次遇到persistenceContext這個東西。

protected Object loadFromSessionCache(
    // 其他代碼
    Object old = session.getEntityUsingInterceptor( keyToLoad );
    // 其他代碼
    }
public Object getEntityUsingInterceptor(EntityKey key) throws HibernateException {
    // 其他代碼
    final Object result = persistenceContext.getEntity( key );
    // 其他代碼
}

proxyOrLoad

可能返回一個已經存在的代理、新代理或者實體。GET和LOAD走這裏。

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

    if ( traceEnabled ) {
        LOG.tracev(
            "Loading entity: {0}",
            MessageHelper.infoString( persister, event.getEntityId(), event.getSession().getFactory() )
        );
    }

    // this class has no proxies (so do a shortcut)
    // 分支1:如果這個類的配置文件中沒有設置代理,直接從一二級緩存和數據庫查實體
    if ( !persister.hasProxy() ) {
        return load( event, persister, keyToLoad, options );
    }

    final PersistenceContext persistenceContext = event.getSession().getPersistenceContext();

    // look for a proxy
    // 分支2:一級緩存已經存在這個代理
    Object proxy = persistenceContext.getProxy( keyToLoad );
    if ( proxy != null ) {
        return returnNarrowedProxy( event, persister, keyToLoad, options, persistenceContext, proxy );
    }
	
    // 分支3:如果允許創建代理,先看緩存中有沒有實體或者已經初始化的代理,如果沒有就新建代理。第一次LOAD時走這裏
    if ( options.isAllowProxyCreation() ) {
        return createProxyIfNecessary( event, persister, keyToLoad, options, persistenceContext );
    }

    // return a newly loaded object
    // 分支4:查實體。第一次GET的時候,走這裏
    return load( event, persister, keyToLoad, options );
}

分支1、4的查實體前面已經講過

  • 分支3 比較直觀,先看它
private Object createProxyIfNecessary(
    final LoadEvent event,
    final EntityPersister persister,
    final EntityKey keyToLoad,
    final LoadEventListener.LoadType options,
    final PersistenceContext persistenceContext) {
    Object existing = persistenceContext.getEntity( keyToLoad );//從Session緩存取實體
    if ( existing != null ) {
        // return existing object or initialized proxy (unless deleted)
		// 如果已經緩存了實體,就返回這個實體
        if ( traceEnabled ) {
            LOG.trace( "Entity found in session cache" );
        }
        if ( options.isCheckDeleted() ) {
            EntityEntry entry = persistenceContext.getEntry( existing );
            Status status = entry.getStatus();
            if ( status == Status.DELETED || status == Status.GONE ) {
                return null;
            }
        }
        return existing;
    }
    if ( traceEnabled ) {
        LOG.trace( "Creating new proxy for entity" );
    }
    // return new uninitialized proxy
    // 返回一個未初始化的代理
    Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
    persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
    persistenceContext.addProxy( keyToLoad, proxy );
    return proxy;
}
  • 分支2
private Object returnNarrowedProxy(
    final LoadEvent event,
    final EntityPersister persister,
    final EntityKey keyToLoad,
    final LoadEventListener.LoadType options,
    final PersistenceContext persistenceContext,
    final Object proxy) {
    if ( traceEnabled ) {
        LOG.trace( "Entity proxy found in session cache" );
    }
    LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();
    if ( li.isUnwrap() ) {
        return li.getImplementation();//代理初始化並返回其中的實體
    }
    Object impl = null;
    if ( !options.isAllowProxyCreation() ) { //GET進入這個if,而LOAD不會
        impl = load( event, persister, keyToLoad, options );
        if ( impl == null ) {
            event.getSession()
                .getFactory()
                .getEntityNotFoundDelegate()
                .handleEntityNotFound( persister.getEntityName(), keyToLoad.getIdentifier() );
        }
    }
    return persistenceContext.narrowProxy( proxy, persister, keyToLoad, impl );
}

  • StatefulPersistenceContext#narrowProxy

嘗試將proxy的類型“收窄“

爲什麼要做這個narrow的步驟?:

1.調用Sesion.load(Pet.class,catId)後,Session緩存的proxy將繼承Pet類

2.繼續Session.load(Cat.class,catId),Session將上一步繼承Pet類的proxy直接返回是不妥的,應該將proxy繼承的類”收窄“

關於narrow proxy的概念可以參考https://marcin-chwedczuk.github.io/HHH000179-narrowing-proxy-to-class-this-operation-breaks-equality

public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key, Object object)
    throws HibernateException {

    final Class concreteProxyClass = persister.getConcreteProxyClass();
    final boolean alreadyNarrow = concreteProxyClass.isInstance( proxy );//已有的proxy繼承的類和本次要查的類是否足夠“具體”(比如曾經用Pet.class來創建過這個proxy,而現在要用Cat.class查詢)

    if ( !alreadyNarrow ) {
        LOG.narrowingProxy( concreteProxyClass );

        // If an impl is passed, there is really no point in creating a proxy.
        // It would just be extra processing.  Just return the impl
        // 舊代理的類型“不夠窄”,且已經給出了新的實現,那就沒必要新建一個代理了
        if ( object != null ) {
            proxiesByKey.remove( key );
            return object;
        }

        // Similarly, if the original HibernateProxy is initialized, there
        // is again no point in creating a proxy.  Just return the impl
        // 同樣的,舊代理已經初始化了,就沒有必要新建一個代理,只取裏面的實例即可
        final HibernateProxy originalHibernateProxy = (HibernateProxy) proxy;
        if ( !originalHibernateProxy.getHibernateLazyInitializer().isUninitialized() ) {
            final Object impl = originalHibernateProxy.getHibernateLazyInitializer().getImplementation();
            // can we return it?
            if ( concreteProxyClass.isInstance( impl ) ) {
                proxiesByKey.remove( key );
                return impl;
            }
        }


        // Otherwise, create the narrowed proxy
        // 新建一個未初始化的,更“窄”的代理
        final HibernateProxy narrowedProxy = (HibernateProxy) persister.createProxy( key.getIdentifier(), session );

        // set the read-only/modifiable mode in the new proxy to what it was in the original proxy
        final boolean readOnlyOrig = originalHibernateProxy.getHibernateLazyInitializer().isReadOnly();
        narrowedProxy.getHibernateLazyInitializer().setReadOnly( readOnlyOrig );

        return narrowedProxy;
    }
    else {

        if ( object != null ) {
            final LazyInitializer li = ( (HibernateProxy) proxy ).getHibernateLazyInitializer();
            li.setImplementation( object );//將參數中的實體注入到proxy中,即手工初始化這個代理
        }
        return proxy;
    }
}

narrowProxy接口的註釋

/**
 * If the existing proxy is insufficiently "narrow" (derived), instantiate a new proxy
 * and overwrite the registration of the old one. This breaks == and occurs only for
 * "class" proxies rather than "interface" proxies. Also init the proxy to point to
 * the given target implementation if necessary.
 */

lockAndLoad

private Object lockAndLoad(
    final LoadEvent event,
    final EntityPersister persister,
    final EntityKey keyToLoad,
    final LoadEventListener.LoadType options,
    final SessionImplementor source) {
    SoftLock lock = null;
    final Object ck;
    final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy();
    if ( persister.hasCache() ) {
        ck = cache.generateCacheKey(
            event.getEntityId(),
            persister,
            source.getFactory(),
            source.getTenantIdentifier()
        );
        lock = persister.getCacheAccessStrategy().lockItem( source, ck, null );
    }
    else {
        ck = null;
    }

    Object entity;
    try {
        entity = load( event, persister, keyToLoad, options );
    }
    finally {
        if ( persister.hasCache() ) {
            cache.unlockItem( source, ck, lock );
        }
    }

    return event.getSession().getPersistenceContext().proxyFor( persister, keyToLoad, entity );
}
public Object proxyFor(EntityPersister persister, EntityKey key, Object impl) throws HibernateException {
    if ( !persister.hasProxy() ) {
        return impl;
    }
    final Object proxy = proxiesByKey.get( key );
    return ( proxy != null ) ? narrowProxy( proxy, persister, key, impl ) : impl;
}

proxyFor接口的註釋是這樣寫的

/**
 * Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
 * third argument (the entity associated with the key) if no proxy exists. Init
 * the proxy to the target implementation, if necessary.
 */

結論

Session裏沒有實體的proxy緩存

  • LOAD:查Session有無實體,有就返回,沒有就建個proxy
  • GET:依次查詢Session、二級緩存、數據庫的實體

Session裏有實體的proxy緩存

  • LOAD
    • 如果proxy類型不夠“窄”,就看proxy狀態:
      proxy已初始化:取出裏面的實體,並移除proxy緩存
      proxy未初始化:新建窄proxy並返回
    • 如果proxy類型夠“窄”:直接返回proxy
  • GET:先依次查詢Session、二級緩存、數據庫的實體是否存在
    • 如果proxy類型夠“窄”:
      • GET實體存在:將實體注入到proxy中,返回這個proxy
      • GET實體不存在:返回這個proxy
    • 如果proxy類型不夠“窄”
      • GET實體存在:返回這個實體
      • GET實體不存在,看proxy狀態:
        proxy已初始化:取出裏面的實體,並移除proxy緩存
        proxy未初始化:新建窄proxy並返回
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章