I was frustrated with the way that Hibernate handles lazy initialization, because there are certain cases, especially in a Swing application, where it is very difficult to efficiently preload all the data, and where unacceptable architecture modifications are required to ensure that all objects which might be lazy loaded are in an open, connected session.

To fix this, I've created what I think is a very small, non-disruptive patch to allow lazy loading outside of an open, connected session.

Steps 1 through 3 add pluggable collection loading to Hibernate, but do not actually change the way that Hibernate currently functions. Steps 4 and 5 are optional steps that takes advantage of this pluggable loading behavior to implement lazy loading with closed or disconnected sessions.

Step 1:

Modify the AbstractPersistenceCollection class. This is the only change to existing Hibernate code. Overwrite the initialize(boolean) method with the following code:

	protected final void initialize(boolean writing) {
		if (!initialized) {
			ProxyInitializer.sharedInstance().initializeCollection( this, initializing, session, getCollectionSnapshot() );
		}
	}
Step 2:

Create two new subclasses of LazyInitializationException:

public class SessionDisconnectedException extends LazyInitializationException {
	public SessionDisconnectedException(String msg) {
		super(msg);
	}
}

public class SessionMissingException extends LazyInitializationException {
	public SessionMissingException(String msg) {
		super(msg);
	}
}
Step 3:

Create a new class, org.hibernate.ProxyInitializer:

public class ProxyInitializer {
	private static ProxyInitializer sharedInstance;
	
	public static ProxyInitializer sharedInstance() {
		if( sharedInstance == null ) {
			sharedInstance = new ProxyInitializer();
		}
		return sharedInstance;
	}
	
	public static void setSharedInstance( ProxyInitializer initializer ) {
		sharedInstance = initializer;
	}
	
	public void initializeCollection(PersistentCollection collection, boolean initializing, SessionImplementor session, CollectionSnapshot snapshot) throws SessionMissingException, SessionDisconnectedException {
		if (initializing) {
			throw new LazyInitializationException("illegal access to loading collection");
		}
		if( session == null ) throwSessionMissingException("no session", snapshot);
		if( ! session.isOpen() ) throwSessionMissingException("session was closed", snapshot);
		if( ! session.isConnected() ) throwSessionDisconnectedException("session is disconnected", snapshot);
		
		session.initializeCollection( collection, false );
	}
	
	private void throwSessionMissingException(String msg, CollectionSnapshot snapshot) {
		String name = snapshot==null ? "" : " of role: " + snapshot.getRole();
		throw new SessionMissingException("failed to lazily initialize a collection" + name + " - " + msg);
	}
	
	private void throwSessionDisconnectedException(String msg, CollectionSnapshot snapshot) {
		String name = snapshot==null ? "" : " of role: " + snapshot.getRole();
		throw new SessionDisconnectedException("failed to lazily initialize a collection" + name + " - " + msg);
	}
}
Step 4:

If you do nothing at this point, then Hibernate will continue to work exactly as it previously did. However, you may choose to subclass ProxyInitializer and override the initializeCollection() method. You can call the super method, catch the exception, and then take some other action if you wish. For example, here is how I've implemented my lazy loading. Feel free to use this verbatim, or come up with your own implementation:

private class MyLazyLoader extends ProxyInitializer {
	public void initializeCollection(PersistentCollection collection, boolean initializing, SessionImplementor session, CollectionSnapshot snapshot) {
		try {
			super.initializeCollection(collection, initializing, session, snapshot);
		} catch( SessionDisconnectedException e) {
			session.reconnect();
			try {
				fetchInOpenSession(session, collection, initializing, snapshot);
			} finally {
				session.disconnect();
			}
		} catch(SessionMissingException e) {
			SessionImplementor lazySession = (SessionImplementor)mySessionFactory.openSession();
			try {
				fetchInOpenSession(lazySession, collection, initializing, snapshot);
			} finally {
				lazySession.close();
			}
		}
	}

	private void fetchInOpenSession(SessionImplementor lazySession, PersistentCollection collection, boolean initializing, CollectionSnapshot snapshot) {
		Transaction tx = null;
		try {
			tx = lazySession.beginTransaction();
			lazySession.lock( collection.getOwner(), LockMode.NONE );
			super.initializeCollection( collection, initializing, lazySession, snapshot );
			tx.commit();
		} catch(HibernateException e1) {
			if( tx != null ) tx.rollback();
			throw e1;
		}
	}
}
Step 5:

If you create your own subclass as in step 4, you need to set it as the shared static instance in ProxyInitializer, like this:

ProxyInitializer.setSharedInstance( new MyLazyLoader() );

That should be it... please let me know how well this works for you. I've tested this and it seems to work fine, but I have not used it in a complex application yet. I'd love to know what you think of this.

--Jesse Barnum, President, 360Works
(770) 234-9293