Hibernate 3 到 Hibernate 4 的迁移 - 自定义 ConnectionProvider 实现 configure() 方法不再存在

Hibernate 3 to Hibernate 4 migration - Custom ConnectionProvider implementation configure() method no longer exists

我正在尝试将遗留 Web 应用程序从 JBoss 4.2.3GA 迁移到 Wildfly 9。

部分迁移需要从 Hibernate 3.2 升级到 Hibernate 4.3.10

我对 Hibernate 知之甚少,所以如果我遗漏了一些明显的东西,请原谅我,但是我在网上找不到任何解释我 运行 遇到的问题的东西。

目前的 Web 应用程序具有 ConnectionProvider 接口的自定义实现。

重构前的MyConnectionProvider

package com.my_package.data;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Logger;

import javax.naming.NamingException;
import javax.sql.DataSource;

import org.hibernate.HibernateException;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.connection.ConnectionProviderFactory;
import org.hibernate.util.NamingHelper;


/**
 * An implementation of the {@link ConnectionProvider} interface. This class
 * requires the <code>hibernate.connection.datasource</code> property to be
 * set to the JNDI name of the DataSource to provide connections from, or a
 * DataSource to be injected before the {@link #configure(Properties)} method
 * is called. This supports use by the {@link Database} class and the Hibernate
 * implementation of the {@link javax.persistence.EntityManager}.
 * 
 */
public class MyConnectionProvider implements ConnectionProvider
{
   private static final Logger LOGGER = Logger.getLogger(MyConnectionProvider.class.getName());
   private static final String JNDI_NAME_KEY = "hibernate.connection.datasource";
   private static final String JNDI_NAME_KEY_STANDBY = "StandbyDatasource";

   private static final ThreadLocal<String>  USER_NAME = new ThreadLocal<String>();
   private static final ThreadLocal<String>  PASSWORD = new ThreadLocal<String>();

   private DataSource     dataSource;
   private DataSource[]   dataSources;
   private DataSource     lastMaster;
   private DataSource     standby;

   /**
    * Instantiate an instance of this class. This is called by the {@link 
    * ConnectionProviderFactory} class so must be public.
    */
   public MyConnectionProvider()
   {
      super();
   }

   /**
    * Configure the connection provider. If a DataSource has not been
    * explicitly injected by  {@link #setDataSource(DataSource)} then the JNDI
    * name for the DataSource must be provided.
    * 
    * @param props  The configuration properties. The property
    *               <code>hibernate.connection.datasource</code> should be set
    *               and point to the JNDI name of the DataSource.
    * 
    * @see org.hibernate.connection.ConnectionProvider#configure(java.util.Properties)
    */
   public void configure(Properties props) throws HibernateException
   {  
      String jndiName = props.getProperty(JNDI_NAME_KEY);
      String jndiStandbyName = props.getProperty(JNDI_NAME_KEY_STANDBY);

      /* 
       * either a JNDI name should be present or a DataSource should have been
       * injected
       */
      if (jndiName == null && this.dataSource == null)
      {
         throw new HibernateException("Datasource not set explicitly and JNDI name not specified");
      }

      /* if no DataSource was injected then use JNDI to look one up */
      if (this.dataSource == null)
      {                  
         try
         {  
            // Assume the first database is master - at least one needs to be configured             
            this.dataSource = (DataSource) NamingHelper.getInitialContext(props).lookup(
                  props.getProperty(JNDI_NAME_KEY));
            // Copy the datasource to another to keep the references throughout the
            // code the same if only using a single datasource or floating IP.
            this.lastMaster = this.dataSource;
         }
         catch (NamingException e)
         {
            throw new HibernateException("There was a problem retrieving the primary data source", e);
         }

         try
         {
            // Is there a standby datasource configured
            if (jndiStandbyName != null)
            {
               // Set up the standby data source            
               this.standby = null;            
               // Read the standby datasource which has been added to props. 
               this.standby =  (DataSource) NamingHelper.getInitialContext(props).lookup(
                     props.getProperty(JNDI_NAME_KEY_STANDBY));

               // Add both data sources
               dataSources = new DataSource[]{lastMaster,standby};
            }
         }
         catch (NamingException e)
         {
            // The standby datasource was not found - log out.
            System.err.println("Standby data source not configured");
         }

         if (this.dataSource == null)
         {
            throw new HibernateException("The primary data source wasn't found");
         }
      }
   }

   /**
    * Utility method used to determine if an SQL exception is caused by invalid user
    * credentials being provided by the client.
    * 
    * @param e The SQLException raised when attempting to open a connection.
    * @return  boolean flag, true if the exception is related to invalid login, or 
    *          false if the exception is raised for some other reason.
    */
   public static boolean isLoginException(SQLException e)
   {
      /* Switch on the error code */
      switch(e.getErrorCode())
      {
      case 1004  : /* FALLTHROUGH: ORA-01004 null password given; logon denied */
      case 1017  : /* FALLTHROUGH: ORA-01017 invalid username/password; logon denied */
      case 1040  : /* FALLTHROUGH: ORA-01040 invalid character in password; logon denied (multibyte character issue) */
      case 17443 : /* ORA-17443 null username or password not supported by thin driver */
         break;
      default :
         break;
      }
      return false;
   }

   /**
    * Get a configured connection.
    * 
    * @return              the connection to the database.
    * @throws SQLException if there was a problem retrieving the connection.
    * 
    * @see org.hibernate.connection.ConnectionProvider#getConnection()
    */
   public Connection getConnection() throws SQLException
   {
      Connection conn;
      CallableStatement stmt = null; 
      ResultSet rs = null;

      String userName = USER_NAME.get();
      String password = PASSWORD.get();

      try
      {
         /* if a user name or password are specified then retrieve a connection using these criteria */
         if (userName != null || password != null)
         {
            // Set the connection. Don't return it yet as it may be a standby
            conn = lastMaster.getConnection(userName, password);
         }
         else /* use the data source parameters to retrieve the connection */
         {
            // Set the connection. Don't return it yet as it may be a standby
            conn = lastMaster.getConnection();
         }

         // See if the connection is to the master and is ok
         // This will throw a SQL exception if the application is not running or the
         // connection is being made to the standby
         stmt = conn.prepareCall("{call oracle_package.stored_procedure_check_sys_state}");
         stmt.executeUpdate();

         // If we get here then no exceptions have been thrown so its ok return connection
         return conn;
      }
      catch(SQLException e)
      {         
         /* 
          * Determine that the exception isn't simply a user credentials issue, if it
          * is then we rethrow the exception, otherwise we see  if there is an alternate
          * connection to try.
          */
         if (!isLoginException(e) && dataSources != null && dataSources.length > 1)
         {
            /* Determine the alternate data source */
            DataSource alternate = dataSources[0] == lastMaster ? dataSources[1] : dataSources[0];

            /* Attempt to open a conenction to the alternate datasource */                        
            conn = (Connection)alternate.getConnection(userName, password);

            /* 
             * If we opened a connection then update the lastMaster instance variable,
             * If we didn't then an SQLException will have been thrown and we won't have
             * reached this next instruction.
             */
            lastMaster = alternate;
            System.err.println("Problem with datasource - switching to next datasource");
            return conn;                      
         }
         else
         {
            /* The caught exception cannot be handled here so propogate it. */
            throw e;
         }
      }
   }

   /**
    * Close a connection.
    * 
    * @param conn the connection to close.
    * 
    * @see org.hibernate.connection.ConnectionProvider#closeConnection(java.sql.Connection)
    */
   public void closeConnection(Connection conn) throws SQLException
   {
      conn.close();
   }

   /**
    * Close the connection provider.
    * 
    * @see org.hibernate.connection.ConnectionProvider#close()
    */
   public void close()
   {
      this.dataSource = null;
      this.lastMaster = null;
      this.standby = null;
   }

   /**
    * Query whether this instance supports aggressive release of database
    * connections.
    * 
    * @return  true as this implementation does support aggressive release of
    *          connections.
    * 
    * @see ConnectionProvider#supportsAggressiveRelease()
    */
   public boolean supportsAggressiveRelease()
   {
      return true;
   }

   /**
    * Inject a DataSource into this connection provider. Must be provided
    * before {@link MyConnectionProvider#configure(Properties)} is invoked
    * if the DataSource is not being provided via JNDI.
    * 
    * @param dataSource The dataSource to inject.
    */
   public void setDataSource(DataSource dataSource)
   {
      if (dataSource != null)
      {
         LOGGER.info("Using injected DataSource");
      }
      this.dataSource = dataSource;
   }

   /**
    * Set the user name to be used for connections retrieved by the current
    * thread, when {@link #getConnection()} is called.
    * 
    * @param userName   the user name to use.
    */
   public static final void setUserName(String userName)
   {
      MyConnectionProvider.USER_NAME.set(userName);
   }

   /**
    * Set the password to be used for connections retrieved by the current
    * thread, when {@link #getConnection()} is called.
    * 
    * @param password   the password to use.
    */
   public static final void setPassword(String password)
   {
      MyConnectionProvider.PASSWORD.set(password);
   }
}

我已将 jars 更新到 Hibernate 4.3.10 并重构代码以使用新的 ConnectionProvider 路径并停止使用不再存在的 NamingHelper Hibernate util class(我不确定此重构是否是 correct/works - 任何指针将不胜感激。

重构后的MyConnectionProvider

package com.my_package.data;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import java.util.logging.Logger;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.hibernate.HibernateException;
/*Refactored*/    import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;

/**
 * An implementation of the {@link ConnectionProvider} interface. This class
 * requires the <code>hibernate.connection.datasource</code> property to be
 * set to the JNDI name of the DataSource to provide connections from, or a
 * DataSource to be injected before the {@link #configure(Properties)} method
 * is called. This supports use by the {@link Database} class and the Hibernate
 * implementation of the {@link javax.persistence.EntityManager}.
 * 
 */
public class MyConnectionProvider implements ConnectionProvider
{
/*Refactored*/   private static final long serialVersionUID = -7542368426769408563L;

   private static final Logger LOGGER = Logger.getLogger(MyConnectionProvider.class.getName());
   private static final String JNDI_NAME_KEY = "hibernate.connection.datasource";
   private static final String JNDI_NAME_KEY_STANDBY = "StandbyDatasource";

   private static final ThreadLocal<String>  USER_NAME = new ThreadLocal<String>();
   private static final ThreadLocal<String>  PASSWORD = new ThreadLocal<String>();

   private DataSource     dataSource;
   private DataSource[]   dataSources;
   private DataSource     lastMaster;
   private DataSource     standby;

   /**
    * Instantiate an instance of this class. This is called by the {@link 
    * ConnectionProviderFactory} class so must be public.
    */
   public MyConnectionProvider()
   {
      super();
   }

   /**
    * Configure the connection provider. If a DataSource has not been
    * explicitly injected by  {@link #setDataSource(DataSource)} then the JNDI
    * name for the DataSource must be provided.
    * 
    * @param props  The configuration properties. The property
    *               <code>hibernate.connection.datasource</code> should be set
    *               and point to the JNDI name of the DataSource.
    * 
    * @see org.hibernate.connection.ConnectionProvider#configure(java.util.Properties)
    */
   public void configure(Properties props) throws HibernateException
   {  
      String jndiName = props.getProperty(JNDI_NAME_KEY);
      String jndiStandbyName = props.getProperty(JNDI_NAME_KEY_STANDBY);

      /* 
       * either a JNDI name should be present or a DataSource should have been
       * injected
       */
      if (jndiName == null && this.dataSource == null)
      {
         throw new HibernateException("Datasource not set explicitly and JNDI name not specified");
      }

      /* if no DataSource was injected then use JNDI to look one up */
      if (this.dataSource == null)
      {                  
         try
         {  
            // Assume the first database is master - at least one needs to be configured             
/*Refactored*/            Context initialContext = new InitialContext(props);

/*Refactored*/            this.dataSource = (DataSource) initialContext.lookup(props.getProperty(JNDI_NAME_KEY));
            // Copy the datasource to another to keep the references throughout the
            // code the same if only using a single datasource or floating IP.
            this.lastMaster = this.dataSource;
         }
         catch (NamingException e)
         {
            throw new HibernateException("There was a problem retrieving the primary data source", e);
         }

         try
         {
            // Is there a standby datasource configured
            if (jndiStandbyName != null)
            {
               // Set up the standby data source            
               this.standby = null;            
               // Read the standby datasource which has been added to props. 
/*Refactored*/               Context initialContext = new InitialContext(props);

/*Refactored*/               this.standby =  (DataSource) initialContext.lookup(props.getProperty(JNDI_NAME_KEY_STANDBY));

               // Add both data sources
               dataSources = new DataSource[]{lastMaster,standby};
            }
         }
         catch (NamingException e)
         {
            // The standby datasource was not found - log out.
            System.err.println("Standby data source not configured");
         }

         if (this.dataSource == null)
         {
            throw new HibernateException("The primary data source wasn't found");
         }
      }
   }

   /**
    * Utility method used to determine if an SQL exception is caused by invalid user
    * credentials being provided by the client.
    * 
    * @param e The SQLException raised when attempting to open a connection.
    * @return  boolean flag, true if the exception is related to invalid login, or 
    *          false if the exception is raised for some other reason.
    */
   public static boolean isLoginException(SQLException e)
   {
      /* Switch on the error code */
      switch(e.getErrorCode())
      {
      case 1004  : /* FALLTHROUGH: ORA-01004 null password given; logon denied */
      case 1017  : /* FALLTHROUGH: ORA-01017 invalid username/password; logon denied */
      case 1040  : /* FALLTHROUGH: ORA-01040 invalid character in password; logon denied (multibyte character issue) */
      case 17443 : /* ORA-17443 null username or password not supported by thin driver */
         break;
      default :
         break;
      }
      return false;
   }

   /**
    * Get a configured connection.
    * 
    * @return              the connection to the database.
    * @throws SQLException if there was a problem retrieving the connection.
    * 
    * @see org.hibernate.connection.ConnectionProvider#getConnection()
    */
   public Connection getConnection() throws SQLException
   {
      Connection conn;
      CallableStatement stmt = null; 
      ResultSet rs = null;

      String userName = USER_NAME.get();
      String password = PASSWORD.get();

      try
      {
         /* if a user name or password are specified then retrieve a connection using these criteria */
         if (userName != null || password != null)
         {
            // Set the connection. Don't return it yet as it may be a standby
            conn = lastMaster.getConnection(userName, password);
         }
         else /* use the data source parameters to retrieve the connection */
         {
            // Set the connection. Don't return it yet as it may be a standby
            conn = lastMaster.getConnection();
         }

         // See if the connection is to the master and is ok
         // This will throw a SQL exception if the application is not running or the
         // connection is being made to the standby
         stmt = conn.prepareCall("{call oracle_package.stored_procedure_check_sys_state}");
         stmt.executeUpdate();

         // If we get here then no exceptions have been thrown so its ok return connection
         return conn;
      }
      catch(SQLException e)
      {         
         /* 
          * Determine that the exception isn't simply a user credentials issue, if it
          * is then we rethrow the exception, otherwise we see  if there is an alternate
          * connection to try.
          */
         if (!isLoginException(e) && dataSources != null && dataSources.length > 1)
         {
            /* Determine the alternate data source */
            DataSource alternate = dataSources[0] == lastMaster ? dataSources[1] : dataSources[0];

            /* Attempt to open a conenction to the alternate datasource */                        
            conn = (Connection)alternate.getConnection(userName, password);

            /* 
             * If we opened a connection then update the lastMaster instance variable,
             * If we didn't then an SQLException will have been thrown and we won't have
             * reached this next instruction.
             */
            lastMaster = alternate;
            System.err.println("Problem with datasource - switching to next datasource");
            return conn;                      
         }
         else
         {
            /* The caught exception cannot be handled here so propogate it. */
            throw e;
         }
      }
   }

   /**
    * Close a connection.
    * 
    * @param conn the connection to close.
    * 
    * @see org.hibernate.connection.ConnectionProvider#closeConnection(java.sql.Connection)
    */
   public void closeConnection(Connection conn) throws SQLException
   {
      conn.close();
   }

   /**
    * Close the connection provider.
    * 
    * @see org.hibernate.connection.ConnectionProvider#close()
    */
   public void close()
   {
      this.dataSource = null;
      this.lastMaster = null;
      this.standby = null;
   }

   /**
    * Query whether this instance supports aggressive release of database
    * connections.
    * 
    * @return  true as this implementation does support aggressive release of
    *          connections.
    * 
    * @see ConnectionProvider#supportsAggressiveRelease()
    */
   public boolean supportsAggressiveRelease()
   {
      return true;
   }

   /**
    * Inject a DataSource into this connection provider. Must be provided
    * before {@link MyConnectionProvider#configure(Properties)} is invoked
    * if the DataSource is not being provided via JNDI.
    * 
    * @param dataSource The dataSource to inject.
    */
   public void setDataSource(DataSource dataSource)
   {
      if (dataSource != null)
      {
         LOGGER.info("Using injected DataSource");
      }
      this.dataSource = dataSource;
   }

   /**
    * Set the user name to be used for connections retrieved by the current
    * thread, when {@link #getConnection()} is called.
    * 
    * @param userName   the user name to use.
    */
   public static final void setUserName(String userName)
   {
      MyConnectionProvider.USER_NAME.set(userName);
   }

   /**
    * Set the password to be used for connections retrieved by the current
    * thread, when {@link #getConnection()} is called.
    * 
    * @param password   the password to use.
    */
   public static final void setPassword(String password)
   {
      MyConnectionProvider.PASSWORD.set(password);
   }


/*Refactored - stub methods added for isWrappableAs and unwrap*/
   @Override
    public boolean isUnwrappableAs(Class arg0) 
    {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> arg0) 
    {
        // TODO Auto-generated method stub
        return null;
    }
}

我可以让 ConnectionProvider 进行编译,但是我在 getConnection() 方法中的运行时抛出了一个空指针异常,该行显示为

conn = lastMaster.getConnection(userName, password);

初始化lastMaster变量的逻辑在configure(props)方法中。但是这个方法没有被调用 - 我认为这就是问题所在。

我注意到 ConnectionProvider 接口 class 在 Hibernate 3 和 Hibernate 4 之间发生了显着变化 - 值得注意的是不再需要 .configure(props) 方法,所以我认为任何用于调用 . configure(props) 方法不再这样做。

我目前在 configure 方法中有很多逻辑,所以我假设我的自定义 ConnectionProvider class 不工作是因为它不再像以前那样使用了。

有人可以强调将自定义 ConnectionProvider 从 Hibernate 3 迁移到 Hibernate 4.3.10 所需的步骤,特别是过去在 configure() 方法中处理的逻辑,或者指出任何可以解释这一点的文档吗?

可能会迟到,但我遇到了同样的问题,但在查看了 <a href="https://forums.hibernate.org/viewtopic.php?p=2452561" rel="nofollow">TomcatJDBCConnectionProvider</a> implementation, I then realized that the you need to implement the <a href="https://docs.jboss.org/hibernate/orm/4.1/javadocs/org/hibernate/service/spi/Configurable.html" rel="nofollow">Configurable</a> interface in addition to the <a href="https://docs.jboss.org/hibernate/orm/4.1/javadocs/org/hibernate/service/jdbc/connections/spi/ConnectionProvider.html" rel="nofollow">ConnectionProvider</a> 界面后