为啥 Informix JDBC 驱动程序处理不相关的连接字符串?
Posted
技术标签:
【中文标题】为啥 Informix JDBC 驱动程序处理不相关的连接字符串?【英文标题】:Why does the Informix JDBC driver handle unrelated connection strings?为什么 Informix JDBC 驱动程序处理不相关的连接字符串? 【发布时间】:2016-01-15 19:21:19 【问题描述】:当 Informix JDBC 驱动程序出现在我的类路径中时,它似乎会在适当的驱动程序有机会之前拦截并拒绝所有连接字符串。
例如,像jdbc:ghmghmghm
这样的完全无意义的连接字符串将导致以下堆栈跟踪:
java.sql.SQLException: Invalid sub-protocol Invalid sub-protocol: 'ghmghmghm'
at com.informix.util.IfxErrMsg.getLocSQLException(IfxErrMsg.java:493)
at com.informix.jdbc.IfxDriver.checkURL(IfxDriver.java:560)
at com.informix.jdbc.IfxDriver.connect(IfxDriver.java:208)
at java.sql.DriverManager.getConnection(DriverManager.java:664)
at java.sql.DriverManager.getConnection(DriverManager.java:208)
我的理解是,行为良好的 JDBC 驱动程序将自己限制为以魔术前缀开头的连接字符串。是 Informix 驱动程序坏了,还是我有不合理的期望?
更新
如果我只删除 Informix 驱动程序,但保留所有其他驱动程序,则异常会转为更加理智
java.sql.SQLException: No suitable driver found for jdbc:ghmghmghm
at java.sql.DriverManager.getConnection(DriverManager.java:596)
at java.sql.DriverManager.getConnection(DriverManager.java:187)
此外,如果 Informix 驱动程序被删除,则存在特定的有效连接字符串 jdbc:sybase:Tds:leeta:5001/leeta_ase1
,但如果存在 Informix,则会失败(Informix 无效的子协议堆栈跟踪)。
我的结论是 Informix 没有正确拒绝完全不匹配的连接字符串,并且 Informix 正在首先破解 Sybase 连接字符串(但不是我尝试过的所有其他连接字符串类型......)
我的 Linux JDK 是
java version "1.7.0_91"
OpenJDK Runtime Environment (IcedTea 2.6.3) (7u91-2.6.3-0ubuntu0.14.04.1)
OpenJDK 64-Bit Server VM (build 24.91-b01, mixed mode)
但我看到错误的连接字符串也会导致 Windows 上的官方 Java 8 上出现 Infx 跟踪。还没有见过 Sybase 在 Windows 上被拦截,但也许这是一个类路径排序问题。
更新 2
我无法重现我的说法,即 Informix 拦截并拒绝了完全有效的连接字符串。我一定是在尝试微妙的错误字符串,查看 Informix 堆栈,删除 Informix 驱动程序作为响应,然后认为来自正确驱动程序的堆栈是一个胜利(因为它允许快速修复连接字符串)。
我看到了一些改进方法:
只要知道驱动程序类就停止使用DriverManager
(直接实例化驱动程序并在其上调用getConnection()
)
写一个替换 DriverManager.getConnection()
至少通过 Throwable.addSuppressed()
报告所有拒绝堆栈
具有 Java 类路径的猴子试图使 Informix(和其他不良行为者)出现在驱动程序列表的后面(根据 @jonathan-leffler)
使用DriverManager.(de)registerDriver()
将不良行为者(静态列表或在运行时动态测试)移动到驱动程序列表的末尾
感谢所有反馈!
【问题讨论】:
您的类路径上还有哪些其他 JDBC 驱动程序?你试过打印jdbc.drivers
系统属性的内容吗?
您能否确保 Informix 驱动程序在您的列表中排在最后?你能确保它是最后的吗?这是否是一个足够的解决方案?
你也用真实的连接字符串测试过这个?因为 jdbc:ghmghmghm
失败的潜在原因可能是解析问题,因为 JDBC 指定 url 应为 jdbc:<subprotocol>:<driver specific part>
,而 jdbc:ghmghmghm
未满足该要求 ;)
很难理解你为什么在这里问,而不是问供应商。
我可以肯定,至少 Informix 驱动程序的某些版本在这方面存在缺陷。我遇到了 Informix 驱动程序拦截有效 Netezza 连接 URL 的问题。
【参考方案1】:
这听起来像是 Informix 驱动程序中的一个错误(但充其量只是一个小错误)。行为良好的 JDBC 驱动程序需要遵循java.sql.Driver.connect(String url, Properties properties)
中定义的期望(强调我的):
尝试与给定 URL 建立数据库连接。司机 如果它意识到它是错误的驱动程序,则应该返回“null” 连接到给定的 URL。这很常见,就像 JDBC 驱动程序一样 manager 被要求连接到给定的 URL,它将 URL 传递给每个 依次加载驱动程序。
如果驱动程序是正确的驱动程序,应该抛出一个 SQLException 连接到给定的 URL,但无法连接到数据库。
java.sql.DriverManager.getConnection
会一一查询所有注册驱动的connect
方法。如果驱动程序返回null
,它将继续下一个驱动程序。如果驱动程序返回一个连接,该连接将返回给调用者。如果所有驱动程序都返回null
,则抛出SQLException
并显示“没有为[url] 找到合适的驱动程序” 消息。
如果驱动程序抛出SQLException
,则保留最后抛出的异常,驱动程序管理器将继续处理下一个驱动程序。如果所有其他驱动程序拒绝与null
的连接尝试,则将抛出最后一个异常,而不是 “没有合适的驱动程序...” 异常。据我所知,在较旧的 Java 版本中,它实际上会停止尝试其他驱动程序。但是DriverManager
的代码(至少从 Oracle/Sun Java 5 开始)可以防止这种情况,并防止行为不端的驱动程序独占 JDBC,并允许尝试同一数据库(和协议)的多个驱动程序。
因此,除非您的 Java 版本具有不同的 DriverManager
实现(在第一个异常处停止),否则它应该继续使用其他注册的驱动程序,并且如果其中任何一个接受 URL,那么应该没有问题。
【讨论】:
【参考方案2】:该问题已在最新的 Informix JDBC 驱动程序版本 (JDBC.4.10.JC8DE) 中得到修复。
【讨论】:
遗憾的是,新驱动程序也破坏了 LOB 支持,但我想你不能拥有一切。【参考方案3】:根据DriverManager
文档:
作为其初始化的一部分,DriverManager 类将尝试加载“jdbc.drivers”系统属性中引用的驱动程序类。这允许用户自定义其应用程序使用的 JDBC 驱动程序。例如,在您的 ~/.hotjava/properties 文件中,您可以指定:
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.taste.ourDriver
假设您有 3 个 JDBC 驱动程序,您的 jdbc.drivers
属性如下所示:
jdbc.drivers=com.DriverA:com.DriverB:com.DriverC
如果您调用 DriverManager.getConnection("jdbc:driverA:blahblah");
,DriverManager 不知道要使用 jdbc.drivers
属性中的哪个驱动程序,因此它必须遍历所有驱动程序。
DriverManager.getConnection() 可能会做类似的事情:
public Connection getConnection(String url)
Set<Driver> drivers = // drivers in 'jdbc.drivers' prop
SQLException failure = null;
for(Driver driver : drivers)
try
Connection conn = driver.connect(url);
if(conn != null)
return conn;
catch (SQLException sqle)
// potentially not trying to connect to the right driver
if(failure == null)
failure = sqle;
// If we get here, no drivers could connect
if(failure != null)
throw failure;
else // no connection obtained, but no drivers complained
throw new SQLException("No driver found for URL " + url);
更新: (代码示例已更新)
OpenJDK 似乎可以容忍驱动程序为它无法识别的协议返回 null 的情况,并且它也可以容忍驱动程序为它无法识别的协议抛出异常的情况。
我认为 OpenJDK 永远不应该抛出直接来自驱动程序的异常,并且如果没有找到该 URL 的驱动程序,则总是抛出一个新的 SQLException。
【讨论】:
所以如果我理解你的话,任何在适当的驱动程序中失败的连接字符串都可能导致来自不相关驱动程序的堆栈,因为它是最后一个尝试的?跨度> 可能。我确信一个健壮的 JDK 会以一种聪明的方式处理这个问题。例如,看起来 OpenJDK 6 抛出了它看到的第一个失败(假设所有连接尝试都失败) 有趣的发现,我也更新了答案。我还建议查看 OpenJDK 源代码:grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… 你的回答不正确,如果驱动程序不支持协议,它应该返回null
拒绝尝试,如果驱动程序确实支持协议(但它有语法错误,无效属性等,那么它应该抛出一个SQLException
。如果DriverManager
接收到SQLException
,它将转发该异常,如果它接收到null
,它将继续下一个驱动程序。
在更仔细地查看驱动程序管理器代码之后,在抛出 SQLException
之后,它看起来确实会继续与其他驱动程序一起使用,据我所知,它过去没有这样做。 以上是关于为啥 Informix JDBC 驱动程序处理不相关的连接字符串?的主要内容,如果未能解决你的问题,请参考以下文章
如何通过 JDBC 显示 Informix sysprocedures.paramtypes 列?
使用 Dataframes 从 Informix 到 Spark 的 JDBC