我正在尝试将遗留的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()方法中处理的逻辑,还是指出任何可以解释这个的文档?
答案 0 :(得分:1)
可能要迟到但我遇到了同样的问题,但在查看TomcatJDBCConnectionProvider
实现后,我意识到除了{{1}之外,您还需要实现Configurable
接口接口