views:

247

answers:

1

So pre spring, we used version of HibernateUtil that cached the SessionFactory instance if a successful raw JDBC connection was made, and threw SQLException otherwise. This allowed us to recover from initial setup of the SessionFactory being "bad" due to authentication or server connection issues.

We moved to Spring and wired things in a more or less classic way with the LocalSessionFactoryBean, the C3P0 datasource, and various dao classes which have the SessionFactory injected.

Now, if the SQL server appears to not be up when the web app runs, the web app never recovers. All access to the dao methods blow up because a null sessionfactory gets injected. (once the sessionfactory is made properly, the connection pool mostly handles the up/down status of the sql server fine, so recovery is possible)

Now, the dao methods are wired by default to be singletons, and we could change them to prototype. I don't think that will fix the matter though - I believe the LocalSessionFactoryBean is now "stuck" and caches the null reference (I haven't tested this yet, though, I'll shamefully admit). This has to be an issue that concerns people.

Tried proxy as suggested below -- this failed

First of all I had to ignore the suggestion (which frankly seemed wrong from a decompile) to call LocalSessionFactory.buildSessionFactory - it isn't visible.

Instead I tried a modified version as follows:

override newSessionFactory. At end return proxy of SessionFactory pointing to an invocation handler listed below

This failed too.

org.hibernate.HibernateException: No local DataSource found for configuration - 'dataSource' property must be set on LocalSessionFactoryBean

Now, if newSessionfactory() is changed to simply return config.buildSessionFactory() (instead of a proxy) it works, but of course no longer exhibits the desired proxy behavior.

public static class HibernateInvocationHandler implements InvocationHandler {
    final private Configuration config;
    private SessionFactory realSessionFactory;
    public HibernateInvocationHandler(Configuration config) {
        this.config=config;
        }

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if (false) proxy.hashCode();
        System.out.println("Proxy for SessionFactory called");
            synchronized(this) {
                if (this.realSessionFactory == null){
                    SessionFactory sf =null;
                    try {
                        System.out.println("Gonna BUILD one or die trying");

                        sf=this.config.buildSessionFactory();
                    } catch (RuntimeException e) {
                        System.out.println(ErrorHandle.exceptionToString(e));
                        log.error("SessionFactoryProxy",e);
                        closeSessionFactory(sf);
                        System.out.println("FAILED to build");
                        sf=null;
                    }
                    if (sf==null) throw new RetainConfigDataAccessException("SessionFactory not available");
                    this.realSessionFactory=sf;                     
                }
                return method.invoke(this.realSessionFactory, args);    
        }

    }

The proxy creation in newSessionFactory looks like this

    SessionFactory sfProxy= (SessionFactory) Proxy.newProxyInstance(
            SessionFactory.class.getClassLoader(),
            new Class[] { SessionFactory.class },
            new HibernateInvocationHandler(config));

and one can return this proxy (which fails) or config.buildSessionFactory() which works but doesn't solve the initial issue.

An alternate approach has been suggested by bozho, using getObject(). Note the fatal flaw in d), because buildSessionFactory is not visible.

a) if this.sessionfactory is nonnull, no need for a proxy, just return b) if it is , build a proxy which... c) should contain a private reference of sessionfactory, and each time it is called check if it is null. If so, you build a new factory and if successful assign to the private reference and return it from now on. d) Now, state how you would build that factory from getObject(). Your answer should involve calling buildSessionFactory....but you CAN'T. One could create the factory by oneself, but you would end up risking breaking spring that way (look at buildSessionFactory code)

A: 

You shouldn't worry about this. Starting the app is something you will rarely do in production, and in development - well, you need the DB server anyway.

You should worry if the application doesn't recover if the db server stops while the app is running.

What you can do is extend LocalSessionFactoryBean and override the getObject() method, and make it return a proxy (via java.lang.reflect.Proxy or CGLIB / javassist), in case the sessionFactory is null. That way a SessionFactory will be injected. The proxy should hold a reference to a bare SessionFactory, which would initially be null. Whenever the proxy is asked to connect, if the sessionFacotry is still null, you call the buildSessionFactory() (of the LocalSessionFactoryBean) and delegate to it. Otherwise throw an exception. (Then of course map your new factory bean instead of the current)

Thus your app will be available even if the db isn't available on startup. But I myself wouldn't bother with this.

Bozho
Hmm. I'll investigate the approach. I appreciate the suggestion though I consider your opinion wrong headed here. The target audience is end-user oriented so recovery is important. I'd agree if this were an in-house app or one that was professionally hosted this would not be an issue
MJB
Your proxy suggestion seemed promising but I couldn't get it to work
MJB
it's not easy to start with proxies. You may want to ask another question about it.
Bozho
I am confident the proxy is working, it is the LocalSessionFactory bean that is fighting it. What speciic question do you suggest?
MJB
@MJB it is not customary to downvote someone, who has spent some time investigating _your_ problem, and giving a solution that you fail to understand.
Bozho