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

时间:2015-08-26 13:16:59

标签: java hibernate jboss wildfly

我正在尝试将遗留的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);
   }
}

我已经将jar更新为Hibernate 4.3.10并重构了代码以使用新的ConnectionProvider路径并停止使用不再存在的NamingHelper Hibernate util类(我不确定这种重构是否正确/有效 - 任何指针都将不胜感激。)

重构后的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接口类在Hibernate 3和Hibernate 4之间发生了显着的变化 - 显然不再需要.configure(props)方法所以我认为无论用什么来调用.configure(props)方法都没有更长的时间。

我目前在configure方法中有很多逻辑,所以我假设我的自定义ConnectionProvider类无法正常工作的事实是由于它不再像以前那样被使用了。

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

1 个答案:

答案 0 :(得分:1)

可能要迟到但我遇到了同样的问题,但在查看TomcatJDBCConnectionProvider实现后,我意识到除了{{1}之外,您还需要实现Configurable接口接口