使用错误的JDBC驱动程序?

6
我有一个方法,可以将记录插入到Postgres数据库中,并返回为该记录生成的标识字段。问题是,如果我在POM文件中包含Redshift驱动程序,那么该驱动程序将被使用,而不是Postgres驱动程序 - 而Redshift驱动程序不允许返回标识值。
代码如下:
try {
  Class.forName( "org.postgresql.Driver" ).newInstance();
  Connection connection = DriverManager.getConnection( "jdbc:postgresql://localhost:5433/postgres", "postgres", "password" );
  Statement stmt = connection.createStatement();
  stmt.execute( "insert into public.job ( job_name ) values ( 'test' )" , Statement.RETURN_GENERATED_KEYS );
  ResultSet keyset = stmt.getGeneratedKeys();
  if ( keyset.next() ) System.out.println( keyset.getLong( 1 ) );
}
catch ( Exception e ) {
  e.printStackTrace();
}

当使用此POM时,它可以正常工作:

<dependencies>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>9.4-1201-jdbc41</version>
    </dependency>
</dependencies>

当使用这个POM时,它无法正常工作:

<dependencies>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>redshift.jdbc</artifactId>
        <version>1.1.2.0002</version>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>9.4-1201-jdbc41</version>
    </dependency>
</dependencies>

什么原因使Java选择Redshift驱动而不是Postgres驱动?(Redshift驱动的类路径为com.amazon.jdbc41.Driver,因此我认为这不是类路径冲突)谢谢。

如果你不想使用Redshift驱动程序,为什么要将它包含进来? - user207421
3个回答

3
你的问题是,Java支持JDBC 4.0中的ServiceLoader机制。在JDBC 4中,DriverManager会在jar文件中的META-INF/services/java.sql.Driver设置中查找并注册驱动程序。当你调用getConnection()方法时,DriverManager将为给定的jdbc URL选择第一个合适的驱动程序。现在,redshift和postgres驱动程序在jdbc url方面有所不同,但(引用自redshift文档http://docs.aws.amazon.com/redshift/latest/mgmt/configure-jdbc-connection.html#obtain-jdbc-url):
“使用以前的格式jdbc:postgresql://endpoint:port/database指定的JDBC URL仍将起作用。”
现在发生的情况是,来自redshift的JDBC驱动程序通过服务条目加载,并将自己放置为redshift jdbc URL和postgres URL的驱动程序。
我无法确定DriverManager是否允许通过加载另一个驱动程序来覆盖现有的jdbc驱动程序链接,但解决您的问题的方法可能是显式控制,要么先加载postgres驱动程序(如果URL仅注册一次),要么在redshift驱动程序之后显式加载它(如果JDBC URL映射可以被覆盖)。
我无法确定是否存在属性来禁止redshift驱动程序为postgres URL注册。

我已经打开了驱动器的JAR文件,至少对于当前版本来说,没有包名称冲突。JDBC URL的冲突(我猜)是一个“便利”功能,只需使用旧的URL,放入新的JAR文件并使用新的驱动程序。否则,我并不打算在这里进行判断,因为楼主只是问为什么会发生这种情况。 - thst
谢谢你指引我正确的方向。目前的解决方案是调用DriverManager.deregisterDriver(<the RedshiftDriver>)。看起来有点笨拙,但在找到更好的方法之前,它可以让我继续前进。谢谢! - Todd

3
我尝试过的唯一一种能够让驱动程序以正确顺序加载且保持一致性的方法是添加以下内容:
static {
    // Put the redshift driver at the end so that it doesn't
    // conflict with postgres queries
    java.util.Enumeration<Driver> drivers =  DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
        Driver d = drivers.nextElement();
        if (d.getClass().getName().equals("com.amazon.redshift.jdbc41.Driver")) {
            try {
                DriverManager.deregisterDriver(d);
                DriverManager.registerDriver(d);
            } catch (SQLException e) {
                throw new RuntimeException("Could not deregister redshift driver");
            }
            break;
        }
    }
}

这相当恶心,但能胜任工作。

0

如下所述,PGDatasource也使用DriverManager,但是可以直接创建org.postgresql.jdbc4.Jdbc4Connection

Properties info = new Properties();
info.put("password", "postgres");
Jdbc4Connection c = new Jdbc4Connection(new HostSpec[]{new HostSpec("localhost", 5432)}, "postgres", "postgres", info, "");

我们尝试过这个方法,但是PGPoolingDataSource在后台使用DriverManager,所以结果是一样的。 - Nathan Villaescusa
请查看 https://github.com/pgjdbc/pgjdbc/blob/master/org/postgresql/ds/common/BaseDataSource.java#L84 - Nathan Villaescusa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接