/**
   * Attempts to load the entity from the second-level cache.
   *
   * @return The entity from the second-level cache, or null.
   * @throws HibernateException
   */
  protected Object loadFromSecondLevelCache(
      final LoadEvent event,
      final EntityPersister persister,
      final LoadEventListener.LoadType options)
      throws HibernateException {

    final SessionImplementor source = event.getSession();

    final boolean useCache =
        persister.hasCache()
            && source.getCacheMode().isGetEnabled()
            && event.getLockMode().lessThan(LockMode.READ);

    if (useCache) {

      final SessionFactoryImplementor factory = source.getFactory();

      final CacheKey ck =
          new CacheKey(
              event.getEntityId(),
              persister.getIdentifierType(),
              persister.getRootEntityName(),
              source.getEntityMode(),
              source.getFactory());
      Object ce = persister.getCache().get(ck, source.getTimestamp());

      if (factory.getStatistics().isStatisticsEnabled()) {
        if (ce == null) {
          factory
              .getStatisticsImplementor()
              .secondLevelCacheMiss(persister.getCache().getRegionName());
        } else {
          factory
              .getStatisticsImplementor()
              .secondLevelCacheHit(persister.getCache().getRegionName());
        }
      }

      if (ce != null) {

        CacheEntry entry = (CacheEntry) persister.getCacheEntryStructure().destructure(ce, factory);

        // Entity was found in second-level cache...
        return assembleCacheEntry(entry, event.getEntityId(), persister, event);
      }
    }

    return null;
  }
  @Override
  public void execute() throws HibernateException {
    nullifyTransientReferencesIfNotAlready();

    final EntityPersister persister = getPersister();
    final SessionImplementor session = getSession();
    final Object instance = getInstance();
    final Serializable id = getId();

    final boolean veto = preInsert();

    // Don't need to lock the cache here, since if someone
    // else inserted the same pk first, the insert would fail

    if (!veto) {

      persister.insert(id, getState(), instance, session);

      final EntityEntry entry = session.getPersistenceContext().getEntry(instance);
      if (entry == null) {
        throw new AssertionFailure("possible non-threadsafe access to session");
      }

      entry.postInsert(getState());

      if (persister.hasInsertGeneratedProperties()) {
        persister.processInsertGeneratedProperties(id, instance, getState(), session);
        if (persister.isVersionPropertyGenerated()) {
          version = Versioning.getVersion(getState(), persister);
        }
        entry.postUpdate(instance, getState(), version);
      }

      getSession().getPersistenceContext().registerInsertedKey(getPersister(), getId());
    }

    final SessionFactoryImplementor factory = getSession().getFactory();

    if (isCachePutEnabled(persister, session)) {
      final CacheEntry ce = persister.buildCacheEntry(instance, getState(), version, session);
      cacheEntry = persister.getCacheEntryStructure().structure(ce);
      final CacheKey ck =
          session.generateCacheKey(
              id, persister.getIdentifierType(), persister.getRootEntityName());
      final boolean put = persister.getCacheAccessStrategy().insert(ck, cacheEntry, version);

      if (put && factory.getStatistics().isStatisticsEnabled()) {
        factory
            .getStatisticsImplementor()
            .secondLevelCachePut(getPersister().getCacheAccessStrategy().getRegion().getName());
      }
    }

    handleNaturalIdPostSaveNotifications(id);

    postInsert();

    if (factory.getStatistics().isStatisticsEnabled() && !veto) {
      factory.getStatisticsImplementor().insertEntity(getPersister().getEntityName());
    }

    markExecuted();
  }
  @Override
  public void execute() throws HibernateException {
    final Serializable id = getId();
    final EntityPersister persister = getPersister();
    final SessionImplementor session = getSession();
    final Object instance = getInstance();

    final boolean veto = preUpdate();

    final SessionFactoryImplementor factory = session.getFactory();
    Object previousVersion = this.previousVersion;
    if (persister.isVersionPropertyGenerated()) {
      // we need to grab the version value from the entity, otherwise
      // we have issues with generated-version entities that may have
      // multiple actions queued during the same flush
      previousVersion = persister.getVersion(instance);
    }

    final Object ck;
    if (persister.hasCache()) {
      final EntityRegionAccessStrategy cache = persister.getCacheAccessStrategy();
      ck = cache.generateCacheKey(id, persister, factory, session.getTenantIdentifier());
      lock = cache.lockItem(session, ck, previousVersion);
    } else {
      ck = null;
    }

    if (!veto) {
      persister.update(
          id,
          state,
          dirtyFields,
          hasDirtyCollection,
          previousState,
          previousVersion,
          instance,
          rowId,
          session);
    }

    final EntityEntry entry = session.getPersistenceContext().getEntry(instance);
    if (entry == null) {
      throw new AssertionFailure("possible nonthreadsafe access to session");
    }

    if (entry.getStatus() == Status.MANAGED || persister.isVersionPropertyGenerated()) {
      // get the updated snapshot of the entity state by cloning current state;
      // it is safe to copy in place, since by this time no-one else (should have)
      // has a reference  to the array
      TypeHelper.deepCopy(
          state, persister.getPropertyTypes(), persister.getPropertyCheckability(), state, session);
      if (persister.hasUpdateGeneratedProperties()) {
        // this entity defines proeprty generation, so process those generated
        // values...
        persister.processUpdateGeneratedProperties(id, instance, state, session);
        if (persister.isVersionPropertyGenerated()) {
          nextVersion = Versioning.getVersion(state, persister);
        }
      }
      // have the entity entry doAfterTransactionCompletion post-update processing, passing it the
      // update state and the new version (if one).
      entry.postUpdate(instance, state, nextVersion);
    }

    if (persister.hasCache()) {
      if (persister.isCacheInvalidationRequired() || entry.getStatus() != Status.MANAGED) {
        persister.getCacheAccessStrategy().remove(session, ck);
      } else {
        // TODO: inefficient if that cache is just going to ignore the updated state!
        final CacheEntry ce = persister.buildCacheEntry(instance, state, nextVersion, getSession());
        cacheEntry = persister.getCacheEntryStructure().structure(ce);

        final boolean put = cacheUpdate(persister, previousVersion, ck);
        if (put && factory.getStatistics().isStatisticsEnabled()) {
          factory
              .getStatisticsImplementor()
              .secondLevelCachePut(getPersister().getCacheAccessStrategy().getRegion().getName());
        }
      }
    }

    session
        .getPersistenceContext()
        .getNaturalIdHelper()
        .manageSharedNaturalIdCrossReference(
            persister, id, state, previousNaturalIdValues, CachedNaturalIdValueSource.UPDATE);

    postUpdate();

    if (factory.getStatistics().isStatisticsEnabled() && !veto) {
      factory.getStatisticsImplementor().updateEntity(getPersister().getEntityName());
    }
  }
  /**
   * Perform the second step of 2-phase load. Fully initialize the entity instance.
   *
   * <p>After processing a JDBC result set, we "resolve" all the associations between the entities
   * which were instantiated and had their state "hydrated" into an array
   */
  public static void initializeEntity(
      final Object entity,
      final boolean readOnly,
      final SessionImplementor session,
      final PreLoadEvent preLoadEvent,
      final PostLoadEvent postLoadEvent)
      throws HibernateException {

    // TODO: Should this be an InitializeEntityEventListener??? (watch out for performance!)

    final PersistenceContext persistenceContext = session.getPersistenceContext();
    EntityEntry entityEntry = persistenceContext.getEntry(entity);
    if (entityEntry == null) {
      throw new AssertionFailure("possible non-threadsafe access to the session");
    }
    EntityPersister persister = entityEntry.getPersister();
    Serializable id = entityEntry.getId();
    Object[] hydratedState = entityEntry.getLoadedState();

    if (log.isDebugEnabled())
      log.debug(
          "resolving associations for "
              + MessageHelper.infoString(persister, id, session.getFactory()));

    Type[] types = persister.getPropertyTypes();
    for (int i = 0; i < hydratedState.length; i++) {
      final Object value = hydratedState[i];
      if (value != LazyPropertyInitializer.UNFETCHED_PROPERTY
          && value != BackrefPropertyAccessor.UNKNOWN) {
        hydratedState[i] = types[i].resolve(value, session, entity);
      }
    }

    // Must occur after resolving identifiers!
    if (session.isEventSource()) {
      preLoadEvent.setEntity(entity).setState(hydratedState).setId(id).setPersister(persister);
      PreLoadEventListener[] listeners = session.getListeners().getPreLoadEventListeners();
      for (int i = 0; i < listeners.length; i++) {
        listeners[i].onPreLoad(preLoadEvent);
      }
    }

    persister.setPropertyValues(entity, hydratedState, session.getEntityMode());

    final SessionFactoryImplementor factory = session.getFactory();
    if (persister.hasCache() && session.getCacheMode().isPutEnabled()) {

      if (log.isDebugEnabled())
        log.debug(
            "adding entity to second-level cache: "
                + MessageHelper.infoString(persister, id, session.getFactory()));

      Object version = Versioning.getVersion(hydratedState, persister);
      CacheEntry entry =
          new CacheEntry(
              hydratedState,
              persister,
              entityEntry.isLoadedWithLazyPropertiesUnfetched(),
              version,
              session,
              entity);
      CacheKey cacheKey =
          new CacheKey(
              id,
              persister.getIdentifierType(),
              persister.getRootEntityName(),
              session.getEntityMode(),
              session.getFactory());
      boolean put =
          persister
              .getCache()
              .put(
                  cacheKey,
                  persister.getCacheEntryStructure().structure(entry),
                  session.getTimestamp(),
                  version,
                  persister.isVersioned() ? persister.getVersionType().getComparator() : null,
                  useMinimalPuts(
                      session,
                      entityEntry)); // we could use persister.hasLazyProperties() instead of true

      if (put && factory.getStatistics().isStatisticsEnabled()) {
        factory
            .getStatisticsImplementor()
            .secondLevelCachePut(persister.getCache().getRegionName());
      }
    }

    if (readOnly || !persister.isMutable()) {
      // no need to take a snapshot - this is a
      // performance optimization, but not really
      // important, except for entities with huge
      // mutable property values
      persistenceContext.setEntryStatus(entityEntry, Status.READ_ONLY);
    } else {
      // take a snapshot
      TypeFactory.deepCopy(
          hydratedState,
          persister.getPropertyTypes(),
          persister.getPropertyUpdateability(),
          hydratedState, // after setting values to object, entityMode
          session);
      persistenceContext.setEntryStatus(entityEntry, Status.MANAGED);
    }

    persister.afterInitialize(entity, entityEntry.isLoadedWithLazyPropertiesUnfetched(), session);

    if (session.isEventSource()) {
      postLoadEvent.setEntity(entity).setId(id).setPersister(persister);
      PostLoadEventListener[] listeners = session.getListeners().getPostLoadEventListeners();
      for (int i = 0; i < listeners.length; i++) {
        listeners[i].onPostLoad(postLoadEvent);
      }
    }

    if (log.isDebugEnabled())
      log.debug(
          "done materializing entity "
              + MessageHelper.infoString(persister, id, session.getFactory()));

    if (factory.getStatistics().isStatisticsEnabled()) {
      factory.getStatisticsImplementor().loadEntity(persister.getEntityName());
    }
  }