我正在开发一个适合客户端 - 服务器模型的Eclipse插件。它是一个商业项目,因此我们无法为我们支持的插件重新分发JDBC驱动程序。
所以我开发了一个首选项页面,允许用户找到jar并有一个简单的发现机制,它遍历jar文件中的类,加载每个文件以验证它是否实现了java.sql.Driver接口。这一切都很有效。
但问题是我正在使用Hibernate。 Hibernate使用Class.forName()来实例化JDBC驱动程序。
如果我尝试使用以下内容,我会ClassNotFoundException
。
public Object execute(final IRepositoryCallback callback)
{
final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
Activator.getDefault().getDatabaseDriverRegistry());
final ClassLoader oldLoader = Thread.currentThread()
.getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(loader);
try
{
final SessionFactory sessionFactory = this.configuration
.buildSessionFactory();
if (sessionFactory != null)
{
final Session session = sessionFactory
.openSession();
if (session != null)
{
// CHECKSTYLE:OFF
try
// CHECKSTYLE:ON
{
return callback.doExecute(session);
}
finally
{
session.close();
}
}
}
connection.close();
}
finally
{
}
}
// CHECKSTYLE:OFF
catch (Exception e)
// CHECKSTYLE:ON
{
RepositoryTemplate.LOG.error(e.getMessage(), e);
}
finally
{
Thread.currentThread().setContextClassLoader(oldLoader);
}
return null;
}
如果我尝试自己创建驱动程序,我会得到一个SecurityException。
public Object execute(final IRepositoryCallback callback)
{
final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
Activator.getDefault().getDatabaseDriverRegistry());
final ClassLoader oldLoader = Thread.currentThread()
.getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(loader);
final Class driverClass = loader.loadClass(this.connectionDriverClassName);
final Driver driver = (Driver)driverClass.newInstance();
DriverManager.registerDriver(driver);
try
{
final Connection connection = DriverManager.getConnection(
this.connectionUrl, this.connectionUsername,
this.connectionPassword);
final SessionFactory sessionFactory = this.configuration
.buildSessionFactory();
if (sessionFactory != null)
{
final Session session = sessionFactory
.openSession(connection);
if (session != null)
{
// CHECKSTYLE:OFF
try
// CHECKSTYLE:ON
{
return callback.doExecute(session);
}
finally
{
session.close();
}
}
}
connection.close();
}
finally
{
DriverManager.deregisterDriver(driver);
}
}
// CHECKSTYLE:OFF
catch (Exception e)
// CHECKSTYLE:ON
{
RepositoryTemplate.LOG.error(e.getMessage(), e);
}
finally
{
Thread.currentThread().setContextClassLoader(oldLoader);
}
return null;
}
编辑:我不确定它是最好的选择,但我采用了实现自己的ConnectionProvider
的方法,这允许我使用Class.forName()
实例化驱动程序,然后使用{{1}打开连接而不是Driver.connect()
。它非常基本,但在我的特定用例中我不需要连接池。
DriverManager.getConnection()
方法如下:
configure()
public void configure(final Properties props)
{
this.url = props.getProperty(Environment.URL);
this.connectionProperties = ConnectionProviderFactory
.getConnectionProperties(props);
final DatabaseDriverClassLoader classLoader = new DatabaseDriverClassLoader(
Activator.getDefault().getDatabaseDriverRegistry());
final String driverClassName = props.getProperty(Environment.DRIVER);
try
{
final Class driverClass = Class.forName(driverClassName, true,
classLoader);
this.driver = (Driver)driverClass.newInstance();
}
catch (ClassNotFoundException e)
{
throw new HibernateException(e);
}
catch (IllegalAccessException e)
{
throw new HibernateException(e);
}
catch (InstantiationException e)
{
throw new HibernateException(e);
}
}
方法如下:
getConnection()
答案 0 :(得分:4)
Class.forName()
是一个很大的痛苦。这实际上不是任何人的错,只是两个都使用类加载器,它们不像另一个客户期望的那样工作(即OSGi类加载器不能像hibernate期望的那样工作)。
我认为你可以选择其中一种方式,但我现在能想到的是:
DynamicImport-Package
从捆绑的驱动程序添加导出的包。这样,客户端代码仍然可以看到它将使用的类,但它不需要知道它直到运行时。但是,您可能需要尝试使用包装模式来涵盖所有情况(这就是为什么它不太干净)。osgi.parentClassloader=app
添加到您的config.ini中。这可能不适合您的部署,特别是如果您无法控制config.ini
文件。URLClassLoader
。这仅在您拥有一个充满驱动程序jar的目录时才有效,或者用户可以直接或间接指定驱动程序jar的位置。