Custom load listener is added but not being used


#1

Hi everyone,

I’m using Hibernate 4.3.10 for in my migration project and I have been running into a strange problem. I use custom load listeners. The strange thing is that during runtime of my test, I can see that the listeners are being added to the eventlistenerregistry initially (replacing the default listener), but once I run my test the default listeners are being used, causing my tests to fail.

My custom EventListenerRegistryImpl:

public class AutofetchEventListenerRegistryImpl extends EventListenerRegistryImpl {

	private ExtentManager em;
	public AutofetchEventListenerRegistryImpl(ExtentManager em) {
		super();
		this.em = em;
	}
	@Override
	public <T> void setListeners(EventType<T> type, T... listeners) {
		setExtentManager(listeners, em);
		super.setListeners(type, listeners);
	}
	
	private <T> void setExtentManager(T[] listeners, ExtentManager em) {
		for (Object listener : listeners) {
			if (listener instanceof AutofetchInitializeCollectionListener) {
				((AutofetchInitializeCollectionListener) listener).setExtentManager(em);
			}
			if (listener instanceof AutofetchLoadListener) {
				((AutofetchLoadListener) listener).setExtentManager(em);
			}
		}
	}
}

My custom integrator:

	@Override
	public void integrate(Configuration configuration, SessionFactoryImplementor sessionFactory,
			SessionFactoryServiceRegistry serviceRegistry) {
//		EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(AutofetchEventListenerRegistryImpl.class);
		//eventListenerRegistry.getEventListenerGroup(EventType.LOAD).appendListener(new AutofetchLoadListener(extentManager));
        EventListenerRegistry eventListenerRegistry = ((SessionFactoryImpl) sessionFactory).getServiceRegistry()
                .getService(AutofetchEventListenerRegistryImpl.class);
            eventListenerRegistry.setListeners(EventType.LOAD, new AutofetchLoadListener(extentManager));
            eventListenerRegistry.setListeners(EventType.INIT_COLLECTION,
                new AutofetchInitializeCollectionListener(extentManager));
            eventListenerRegistry.addDuplicationStrategy(new DuplicationStrategy());
	}

My custom listener:

public class AutofetchLoadListener extends DefaultLoadEventListener {

    private static final Log log = LogFactory
            .getLog(AutofetchLoadListener.class);

    private ExtentManager extentManager;

    /**
     * Default constructor.
     * setEm(ExtentManager) must be called before this listener is used
     * This constructor exists so that this listener can be instantiated from
     * the configuration file.
     */
    public AutofetchLoadListener() {
        // empty constructor
    }
    
    public AutofetchLoadListener(ExtentManager em) {
        super();
        if (em == null) {
            throw new NullPointerException("Extent manager may not be null.");
        }
        this.extentManager = em;
    }

    @Override
    protected Object loadFromDatasource(LoadEvent event,
            EntityPersister entityPersister, EntityKey entityKey,
            LoadType loadType) throws HibernateException {

        String classname = entityPersister.getEntityName();
        if (log.isDebugEnabled()) {
            log.debug("Entity id: " + event.getEntityId());
        }
        List<Path> prefetchPaths = extentManager.getPrefetchPaths(classname);
        Object result = null;
        if (!prefetchPaths.isEmpty()) {
            result = getResult(prefetchPaths, classname, event.getEntityId(),
                    event.getLockMode(), event.getSession());
            if (result instanceof HibernateProxy) {
                HibernateProxy proxy = (HibernateProxy) result;
                if (proxy.getHibernateLazyInitializer().isUninitialized()) {
                    throw new IllegalStateException("proxy uninitialized");
                }
                result = proxy.getHibernateLazyInitializer()
                        .getImplementation();
            }
        } else {
            result = super.loadFromDatasource(event, entityPersister,
                    entityKey, loadType);
        }
        extentManager.markAsRoot(result, classname);
        return result;
    }
    
    public static Object getResult(List<Path> prefetchPaths, String classname,
            Serializable id, LockMode lm, Session sess) {
        StringBuilder queryStr = new StringBuilder();
        queryStr.append("from " + classname + " entity");
        Map<Path, String> pathAliases = new HashMap<Path, String>();
        int aliasCnt = 0;
        pathAliases.put(new Path(), "entity");
        // Assumes prefetchPaths is ordered such larger paths
        // appear after smaller ones.
        // Also assumes all prefixes of a path are present except the
        // empty prefix.
        for (Path p : prefetchPaths) {
            String oldAlias = pathAliases.get(p.removeLastTraversal());
            String newAlias = "af" + (aliasCnt++);
            String lastField = p.traversals().get(p.size() - 1);
            pathAliases.put(p, newAlias);
            queryStr.append(" left outer join fetch ");
            queryStr.append(oldAlias + "." + lastField + " " + newAlias);
        }
        queryStr.append(" where entity.id = :id");
        
        if (log.isDebugEnabled()) {
            log.debug("Query: " + queryStr);
        }
        
        Query q = sess.createQuery(queryStr.toString());
        q.setLockMode("entity", lm);
        q.setFlushMode(FlushMode.MANUAL);
        q.setParameter("id", id);
        
        long startTimeMillis = System.currentTimeMillis();
        Object o = q.uniqueResult();
        if (log.isDebugEnabled()) {
            log.debug("Query execution time: " + 
                    (System.currentTimeMillis() - startTimeMillis));
        }
        return o;
    }

    public void setExtentManager(ExtentManager em) {
        this.extentManager = em;
    }
}

How I set up my tests:

@Before
	public void setUp() {
		try {
			em = new ExtentManager();
			org.hibernate.boot.registry.BootstrapServiceRegistry bootstrapRegistry = new org.hibernate.boot.registry.BootstrapServiceRegistryBuilder()
					.with(new AutofetchEventListenerIntegrator(em)).build();
			ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder(bootstrapRegistry)
					.addService(EventListenerRegistry.class, new AutofetchEventListenerRegistryImpl(em)).build();
			AutofetchConfiguration cfg = new AutofetchConfiguration();
			sf = cfg.buildSessionFactory(serviceRegistry, em);
			SchemaExport se = new SchemaExport(cfg);
			se.create(false, true);
		} catch (Throwable ex) {
			System.err.println("Initial SessionFactory creation failed." + ex);
			throw new ExceptionInInitializerError(ex);
		}
	}

When I get the custom service, I get the following message in the console:

        EventListenerRegistry eventListenerRegistry = ((SessionFactoryImpl) sessionFactory).getServiceRegistry()
                .getService(AutofetchEventListenerRegistryImpl.class);

WARN  HHH000450: Encountered request for Service by non-primary service role [org.autofetch.hibernate.AutofetchEventListenerRegistryImpl -> org.hibernate.event.service.spi.EventListenerRegistry]; please update usage

The problem is that while running the tests, the AutofetchLoadListener is not being used, instead it is the DefaultListener. Does anyone have an idea what could be wrong here?

Thanks.


#2

As explained in the User Guide, you can provide your own listeners via the EventListenerRegistry.

Why did you extend the EventListenerRegistry class?


#3

I wanted to set the Extentmanager automatically when setting up the listeners. There are probably other ways of doing this, but this is what I opted for.

The thing is that I can see during runtime that my integrator is setting the autofetch listener, but in the end when running my tests (loading the entity) it still uses the default listener, and I am confused why this is happening.


#4

Maybe you didn’t supply the Integrator to the tests as well. Is the configuration logic separated between tests and runtime?


#5

I debugged during the execution of the tests, and the right integrator was running (setting the only listener to the custom one). This was done is the @Before statement of the tests. When the actual tests run, the default listener is being used. Could it have something to do with the duplication strategy of the listeners? It seems like my listeners are being overridden somehow by the default ones at runtime.

I set up a breakpoint in EventListenerRegistryImpl, and the prepareListeners function is being called both before and after my own listener is added. An error?


#6

If you provide the AutofetchEventListenerRegistryImpl, why did you add an Integrator as well?

You can just set the listeners at the time the Service is created, not afterward.

The Service should be immutable so all you need is this:

sessionFactory
    .getServiceRegistry()
    .getService( EventListenerRegistry.class )
    .setListeners(EventType.LOAD, new AutofetchLoadListener(extentManager))
    .setListeners(EventType.INIT_COLLECTION, new AutofetchInitializeCollectionListener(extentManager));

#7

A result of desperately trying out different things when I didn’t get the desired results. :wink: You’re right, it’s not needed.

In 4.3 the SessionFactory does not hold a ServiceRegistry so I can’t do that. In which class do you mean by the way?

I tried something similar, like this, and it wouldn’t load the right listener. The problem still persists.

public void setUp() {
		try {
			em = new ExtentManager();
			BootstrapServiceRegistryBuilder bootstrapServiceRegistryBuilder = new BootstrapServiceRegistryBuilder();
			bootstrapServiceRegistryBuilder.build();
			BootstrapServiceRegistry bootstrapServiceRegistry = bootstrapServiceRegistryBuilder.build();
			StandardServiceRegistryBuilder serviceRegistryBuilder = new StandardServiceRegistryBuilder(
					bootstrapServiceRegistry);
			AutofetchEventListenerRegistryImpl autofetchRegistry = new AutofetchEventListenerRegistryImpl(em);
			autofetchRegistry.setListeners(EventType.LOAD, new AutofetchLoadListener(em));
			autofetchRegistry.setListeners(EventType.INIT_COLLECTION, new AutofetchInitializeCollectionListener(em));
			StandardServiceRegistry serviceRegistry = serviceRegistryBuilder
					.addService(EventListenerRegistry.class, autofetchRegistry).build();

			AutofetchConfiguration cfg = new AutofetchConfiguration();
			sf = cfg.buildSessionFactory(serviceRegistry, em);

Update:
I realized that the following does not return the AutofetchEventListenerRegistryImpl:

		eventListenerRegistry = ((SessionFactoryImpl) sessionFactory).getServiceRegistry()
				.getService(EventListenerRegistry.class);

Even though I clearly add the service earlier like this before building the SessionFactory:

AutofetchEventListenerRegistryImpl autofetchRegistry = new AutofetchEventListenerRegistryImpl(em);
			autofetchRegistry.setListeners(EventType.LOAD, new AutofetchLoadListener(em));
			autofetchRegistry.setListeners(EventType.INIT_COLLECTION, new AutofetchInitializeCollectionListener(em));
			StandardServiceRegistry serviceRegistry = serviceRegistryBuilder
					.addService(EventListenerRegistry.class, autofetchRegistry).build();

I am very confused why this is happening…


#8

If you supply it prior to building the SessionFactory, it should work. Otherwise, it will probably use the default one.


#9

I realized that it had to do with the testclass in the end, after further investigation of the stacktrace. It used a different configuration and Service registry from the superclass of the testing class which of course meant that the load listener wouldn’t load… Time-consuming mistake, but lesson learned.


#10

Good thing you are on the right track now.