LoadEventListener class migration from hibernate3 to hibernate5

I’m in the process of migrating an app from JBoss 5.1.0, using hibernate3 to WildFly13, using hibernate5.
One of the last tasks I’m having to do is to rewrite a LoadEventListener so that it uses hibernate5 classes.

There are a few places in the code where I could not find an equivalent in hibernate5:

persister.getCache().lock(ck, null);

persister.getCache().release(ck, lock);

where persister is an EntityPersister,

Object[] values = entry.assemble(result, id, subclassPersister, session.getInterceptor(), session);

entry is missing the assemble method, where entry is a org.hibernate.cache.spi.entry.CacheEntry

TypeFactory.deepCopy(values, types, subclassPersister.getPropertyUpdateability(), values, session);

TypeFactory does not have the deepCopy method,

PostLoadEventListener[] listeners = session.getListeners().getPostLoadEventListeners();

and session is lacking the getListeners() method.

The entire class is pasted below, maybe someone can help me adapt the code to hibernate5 specs.

Thank you.


package com.hibernate.persistence;

import java.io.Serializable;
import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.NonUniqueObjectException;
import org.hibernate.PersistentObjectException;
import org.hibernate.TypeMismatchException;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.cache.spi.access.EntityDataAccess;
import org.hibernate.cache.spi.entry.CacheEntry;
import org.hibernate.engine.internal.TwoPhaseLoad;
import org.hibernate.engine.internal.Versioning;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.engine.spi.Status;
import org.hibernate.event.internal.AbstractLockUpgradeEventListener;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.event.spi.PostLoadEvent;
import org.hibernate.event.spi.PostLoadEventListener;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;


public class LoadEventListener
	extends
	AbstractLockUpgradeEventListener
	implements
	org.hibernate.event.spi.LoadEventListener {

	private static final long serialVersionUID = 1L;

	public static final Object REMOVED_ENTITY_MARKER = new Object();
	public static final Object INCONSISTENT_RTN_CLASS_MARKER = new Object();
	public static final LockMode DEFAULT_LOCK_MODE = LockMode.NONE;

	private static final Logger LOGGER = Logger.getLogger(LoadEventListener.class);


	@Override
	public void onLoad(final LoadEvent event, final LoadEventListener.LoadType loadType)
		throws HibernateException {

		final SessionImplementor source = event.getSession();
		EntityPersister persister;

		if (event.getInstanceToLoad() != null) {
			persister = source.getEntityPersister(null, event.getInstanceToLoad());
			event.setEntityClassName(event.getInstanceToLoad().getClass().getName());

		} else {
			persister = source.getFactory().getEntityPersister(event.getEntityClassName());
		}

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

		Class idClass = persister.getIdentifierType().getReturnedClass();
		if (idClass != null && !idClass.isInstance(event.getEntityId())) {
			throw new TypeMismatchException("Provided id of the wrong type. Expected: " + idClass + ", got " + event.getEntityId().getClass());
		}

		EntityKey keyToLoad = new EntityKey(event.getEntityId(), persister);

		try {
			if (loadType.isNakedEntityReturned()) {
				event.setResult(this.load(event, persister, keyToLoad, loadType));
			} else {
				if (event.getLockMode() == LockMode.NONE) {
					event.setResult(this.proxyOrLoad(event, persister, keyToLoad, loadType));
				} else {
					event.setResult(this.lockAndLoad(event, persister, keyToLoad, loadType, source));
				}
			}
		} catch (HibernateException e) {
			LoadEventListener.LOGGER.info("Error performing load command", e);
			throw e;
		}
	}


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

		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());
		}

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

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

		if (!options.isAllowNulls() || isOptionalInstance) {
			if (entity == null) {
				event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound(event.getEntityClassName(), event.getEntityId());
			}
		}

		if (isOptionalInstance && entity != event.getInstanceToLoad()) {
			throw new NonUniqueObjectException(event.getEntityId(), event.getEntityClassName());
		}

		return entity;

	}


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

		if (LoadEventListener.LOGGER.isDebugEnabled()) {
			LoadEventListener.LOGGER
				.debug("loading entity: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));

		}

		if (!persister.hasProxy()) {
			return this.load(event, persister, keyToLoad, options);
		} else {
			final PersistenceContext persistenceContext = event.getSession().getPersistenceContext();
			Object proxy = persistenceContext.getProxy(keyToLoad);

			if (proxy != null) {
				return this.returnNarrowedProxy(event, persister, keyToLoad, options, persistenceContext, proxy);
			} else {
				if (options.isAllowProxyCreation()) {
					return this.createProxyIfNecessary(event, persister, keyToLoad, options, persistenceContext);
				} else {
					return this.load(event, persister, keyToLoad, options);
				}
			}
		}
	}


	private Object returnNarrowedProxy(final LoadEvent event, final EntityPersister persister, final EntityKey keyToLoad,
		final LoadEventListener.LoadType options, final PersistenceContext persistenceContext, final Object proxy) {

		LoadEventListener.LOGGER.debug("entity proxy found in session cache");
		LazyInitializer li = ((HibernateProxy)proxy).getHibernateLazyInitializer();

		if (li.isUnwrap()) {
			return li.getImplementation();
		}

		Object impl = null;

		if (!options.isAllowProxyCreation()) {
			impl = this.load(event, persister, keyToLoad, options);
			if (impl == null) {
				event.getSession().getFactory().getEntityNotFoundDelegate().handleEntityNotFound(persister.getEntityName(),
					keyToLoad.getIdentifier());
			}
		}

		return persistenceContext.narrowProxy(proxy, persister, keyToLoad, impl);
	}


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

		Object existing = persistenceContext.getEntity(keyToLoad);

		if (existing != null) {
			LoadEventListener.LOGGER.debug("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;

		} else {

			LoadEventListener.LOGGER.debug("creating new proxy for entity");
			Object proxy = persister.createProxy(event.getEntityId(), event.getSession());
			persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey(keyToLoad);
			persistenceContext.addProxy(keyToLoad, proxy);
			
			return proxy;
		}
	}


	protected Object lockAndLoad(final LoadEvent event, final EntityPersister persister, final EntityKey keyToLoad,
		final LoadEventListener.LoadType options, final SessionImplementor source)
		throws HibernateException {

		CacheConcurrencyStrategy lock = null;

		final EntityDataAccess cache = persister.getCacheAccessStrategy();
		final Object ck = null;

		if (persister.hasCache()) {
			ck = cache.generateCacheKey(event.getEntityId(), persister, source.getFactory(), source.getTenantIdentifier());
			lock = persister.getCache().lock(ck, null);
		} else {
			ck = null;
		}

		Object entity;

		try {
			entity = this.load(event, persister, keyToLoad, options);
		} finally {
			if (persister.hasCache()) {
				persister.getCache().release(ck, lock);
			}
		}

		Object proxy = event.getSession().getPersistenceContext().proxyFor(persister, keyToLoad, entity);

		return proxy;

	}


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

		if (LoadEventListener.LOGGER.isDebugEnabled()) {

			LoadEventListener.LOGGER
				.debug("attempting to resolve: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));

		}

		Object entity = this.loadFromSessionCache(event, keyToLoad, options);

		if (entity == LoadEventListener.REMOVED_ENTITY_MARKER) {
			LoadEventListener.LOGGER.debug("load request found matching entity in context, but it is scheduled for removal; returning null");

			return null;
		}

		if (entity == LoadEventListener.INCONSISTENT_RTN_CLASS_MARKER) {

			LoadEventListener.LOGGER
				.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 (LoadEventListener.LOGGER.isDebugEnabled()) {
				LoadEventListener.LOGGER.debug(
					"resolved object in session cache: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
			}

			return entity;
		}

		entity = this.loadFromSecondLevelCache(event, persister, options);

		if (entity != null) {
			if (LoadEventListener.LOGGER.isDebugEnabled()) {

				LoadEventListener.LOGGER.debug("resolved object in second-level cache: "
					+ MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
			}

			return entity;
		}

		if (LoadEventListener.LOGGER.isDebugEnabled()) {

			LoadEventListener.LOGGER.debug(
				"object not resolved in any cache: " + MessageHelper.infoString(persister, event.getEntityId(), event.getSession().getFactory()));
		}

		return this.loadFromDatasource(event, persister, keyToLoad, options);
	}


	protected Object loadFromDatasource(final LoadEvent event, final EntityPersister persister, final EntityKey keyToLoad,
		final LoadEventListener.LoadType options)
		throws HibernateException {

		final SessionImplementor source = event.getSession();

		Object entity = persister.load(event.getEntityId(), event.getInstanceToLoad(), event.getLockMode(), source);

		if (event.isAssociationFetch() && source.getFactory().getStatistics().isStatisticsEnabled()) {
			source.getFactory().getStatisticsImplementor().fetchEntity(event.getEntityClassName());
		}

		return entity;

	}


	protected Object loadFromSessionCache(final LoadEvent event, final EntityKey keyToLoad, final LoadEventListener.LoadType options)
		throws HibernateException {

		SessionImplementor session = event.getSession();

		Object old = session.getEntityUsingInterceptor(keyToLoad);

		if (old != null) {

			EntityEntry oldEntry = session.getPersistenceContext().getEntry(old);

			if (options.isCheckDeleted()) {
				Status status = oldEntry.getStatus();
				if (status == Status.DELETED || status == Status.GONE) {
					return LoadEventListener.REMOVED_ENTITY_MARKER;
				}

			}

			if (options.isAllowNulls()) {

				EntityPersister persister = event.getSession().getFactory().getEntityPersister(event.getEntityClassName());
				if (!persister.isInstance(old)) {
					return LoadEventListener.INCONSISTENT_RTN_CLASS_MARKER;
				}
			}

			this.upgradeLock(old, oldEntry, event.getLockOptions(), event.getSession());
		}

		return old;
	}


	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 EntityDataAccess cache = persister.getCacheAccessStrategy();
			final Object ck = cache.generateCacheKey(event.getEntityId(), persister, source.getFactory(), source.getTenantIdentifier());

			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);
				return this.assembleCacheEntry(entry, event.getEntityId(), persister, event);
			}
		}
		return null;
	}


	private Object assembleCacheEntry(final CacheEntry entry, final Serializable id, final EntityPersister persister, final LoadEvent event)
		throws HibernateException {

		final Object optionalObject = event.getInstanceToLoad();

		final EventSource session = event.getSession();

		final SessionFactoryImplementor factory = session.getFactory();

		if (LoadEventListener.LOGGER.isDebugEnabled()) {

			LoadEventListener.LOGGER.debug("assembling entity from second-level cache: " + MessageHelper.infoString(persister, id, factory));

		}

		EntityPersister subclassPersister = factory.getEntityPersister(entry.getSubclass());

		Object result = optionalObject == null ? session.instantiate(subclassPersister, id) : optionalObject;

		TwoPhaseLoad.addUninitializedCachedEntity(new EntityKey(id, subclassPersister), result, subclassPersister, LockMode.NONE, entry.getVersion(),
			session);

		Type[] types = subclassPersister.getPropertyTypes();

		Object[] values = entry.assemble(result, id, subclassPersister, session.getInterceptor(), session);

		TypeFactory.deepCopy(values, types, subclassPersister.getPropertyUpdateability(), values, session);

		Object version = Versioning.getVersion(values, subclassPersister);

		if (LoadEventListener.LOGGER.isDebugEnabled()) {
			LoadEventListener.LOGGER.debug("Cached Version: " + version);
		}

		final PersistenceContext persistenceContext = session.getPersistenceContext();

		persistenceContext.addEntry(result, Status.MANAGED, values, null, id, version, LockMode.NONE, true, subclassPersister, false);

		subclassPersister.afterInitialize(result, session);

		persistenceContext.initializeNonLazyCollections();

		PostLoadEvent postLoadEvent = new PostLoadEvent(session).setEntity(result).setId(id).setPersister(persister);

		PostLoadEventListener[] listeners = session.getListeners().getPostLoadEventListeners();

		for (PostLoadEventListener listener : listeners) {

			listener.onPostLoad(postLoadEvent);

		}

		return result;
	}
}

Before trying to migrate this, why do you even supply your own LoadEventListener when you could just configure pre/after EventListeners that are executed before or after the default ones.

This is a much better strategy than substituting Hibernate internal mechanism. Otherwise, you are going to have this issue with every migration.

The app is old, 12 years or so, I wasn’t part of the original development team so I’m just trying to migrate the functionality “as-is”.

If there’s a better way of doing the above, maybe with some default implementations, I’m open to suggestions.

Thanks

There’s a better way, hence my suggestion.

Could you please give me some examples on how to do such configuration ?
Maybe some quickstarts ?

Thanks!

Sure. Check out this User Guide chapter for more details.