JDBC4 support

Version 6

    Problem

    Part of supporting JDK 1.6 in Hibernate is supporting the move from JDBC3 to JDBC4, while still allowing support for JDBC3 for those still using JDK 1.5 (and even 1.4 still for a bit).  This is an example of exactly why we decided to modularize Hibernate, and we will be *attempting* to handle this through 2 new separate modules, one targeting JDBC3 and the other targeting JDBC4.

     

    The first and simplest concern is the ability to compile Hibernate with JDK 1.6.  The issue here is that Hibernate implements a number of JDBC contracts which have changed.  This includes

    1. *internal* implementations of JDBC contracts
      1. org.hibernate.jdbc.ResultSetWrapper
    2. *exposed* implementations of JDBC contracts
      1. org.hibernate.lob.BlobImpl
      2. org.hibernate.lob.ClobImpl
      3. org.hibernate.lob.SerializableBlob
      4. org.hibernate.lob.SerializableClob

     

    Other concerns include which new features would we like to leverage.  For example, I can see isolating exception interpretation into these modules to take advantage of the new JDBC exception hierarchy in creating the Hibernate JDBCException hierarchy rather than relying on sql codes and states.  Wrappers?  That would be beneficial, but not quite certain this is important enough to implement pre-JDBC3 for users; doing so would mean knowing how the wrapped objects can be unwrapped.

     

    Considerations

    1. How to implement the isolation.  In other words, what does the code that provides the isolation look like.  The approaches considered include:
      1. utilizing proxies
      2. directly implementing the JDBC contracts in each module
    2. How to determine which implementation to use.  The approaches considered include:
          1. explicit registration (configuration setting)
          2. auto registration
          3. static binding (see slf4j for an example)
        1. I mentioned using modules above; that also implies separate jars (hibernate-jdbc3 and hibernate-jdbc4).  However, in retrospect it is feasible and more user-friendly for the Hibernate code itself to decide whether to load/use JDBC3 or JDBC4 support; but having them in separate jars would put the onus on the user to make sure the correct jars are defined in the classpath.  This is largely an issue with Maven as the build tool (see http://in.relation.to/12371.lace for discussion of this issue).

         

        Proposal

        We will be adding 2 new modules hibernate-jdbc3 and hibernate-jdbc4 which will contain the implementations of the isolations specific to JDBC3 and JDBC4, respectively.

         

        I think in this case it makes the most sense to not use proxies, except for maybe ResultSetWrapper.  I just do not see the benefit in terms of the LOB stuff.

         

        In terms of determining which implementation to use I like the auto-registration approach the best, though I am not overly familiar with the static binding approach and would like to take a little closer look at it as an option here.  Anyway, what I mean by auto-registration is a simplified "service loader" approach; it is simplified because in our case we have a very discreet finite set of service impls: exactly 2.

        public interface JdbcSupport {
            public LobCreator getLobCreator();
            ...
        }
        
        public final class JdbcSupportRegistry {
            private static JdbcSupport IMPL;
        
            public static interface Registrar {
                public void register(JdbcSupport jdbcSupport);
            }
        
            private static final Class[] CTOR_SIG = new Class[] { Registrar.class };
        
            static {
                Registrar registrar = new Registrar() {
                    public void register(JdbcSupport jdbcSupport) {
                        JdbcSupportRegistry.IMPL = jdbcSupport;
                    }
                };
        
                // first try to load JDBC3 support
                //    TODO : does order matter?
                try {
                    // we use reflection to avoid static binding
                    Class jdbc3Class = loadClass( "org.hibernate.jdbc.intg.Jdbc3Support" );
                    jdbc3Class.getConstructor( CTOR_SIG ).newInstance( registrar );
                }
                catch( Throwable t ) {
                    // ignore, fall through to JDBC4 support
                }
        
                if ( IMPL == null ) {
                    // try loading JDBC4 support
                    ...
                }
        
                if ( IMPL == null ) {
                    throw new some-exception...
                }
            }
        
        }
        
        public class Jdbc3Support implements JdbcSupport {
            public Jdbc3Support(JdbcSupportRegistry.Registrar registrar) {
                // do any initialization work...
                // then register ourselves
                registrar.register( this );
            }
        }
        

         

        Another variation of auto-registration would be to drop the Registrar concept and to just do something like:

        public class JdbcSupportRegistry {
            private static final JdbcSupport IMPL = loadAppropriateSupportImpl();
        
            private static JdbcSupport loadAppropriateSupportImpl() {
                // first try to load JDBC3 support
                //    TODO : does order matter?
                try {
                    return loadClass( "org.hibernate.jdbc.intg.Jdbc3Support" ).newInstance();
                }
                catch( Throwable ignore ) {
                    // maybe log for diagnostic purposes
                }
        
                try {
                    return loadClass( "org.hibernate.jdbc.intg.Jdbc4Support" ).newInstance();
                }
                catch( Throwable ignore ) {
                    ...
                }
        
                // if we get here, throw an exceptoon indicative of the fact that loading JDBC support failed...
            }
        }
        

        Registrar could be used to give the support modules access to config/environment information, not just for registration.

         

         


        As a follow up,I have attached 3 different projects implementing this 3 different ways:

        1. module-poc.tar.gz is a maven project implementing this functionality using seperate modules for JDBC 3 and JDBC 4 support, but still trying to fold those classes back into the jar file for core.  Maven requires the "commonality" be split out into a separate module to avoid dependency circularities.
        2. proxy-poc.tar.gz is again a maven project, but this time utilizing JDK proxies to implement the differing JDBC 3 and JDBC 4 support
        3. gradle-poc.tar.gz is a gradle project whcih uses modules, but in what feels to me a much more natural way.