繁体   English   中英

为什么jdbi绑定以函数作为参数失败

[英]Why does jdbi bind fail with function as parameter

我正在研究用Java编写的API,并使用通过jdbi调用的Oracle数据库。 我正在编写一个辅助函数,以允许我为列表中的每个项目绑定变量。 但是,该列表不相关-关键是我的函数需要一个Query,一个对象和一个回调函数(我计划使用Class:getFoo)从该对象获取值。

tl; dr如果返回的值为null,则此操作将失败-但是,如果我直接绑定getFoo,则即使为null也会起作用。 为什么?

以下作品[1]:

public class Foo {
    private final String id;

    public Foo(String id) {
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public static void main(String[] args) throws SQLException {
        // Setup DB connection
        PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
        pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
        pds.setURL("jdbc:oracle:thin:@MY_IP:MY_PORT:MY_INSTANCE_NAME");
        pds.setUser(MY_USER);
        pds.setPassword(MY_PASSWORD);
        Jdbi jdbi = Jdbi.create(pds);

        // Create object
        Foo foo = new Foo("bar");
        try {
            // Call DB
            String val = jdbi.withHandle((handle) -> {
                Query query = handle.createQuery("select :foo foo from dual");
                query.bind("foo", foo.getId());                 
                return query.mapTo(String.class).findOnly();
            });
            System.out.println("Value: " + val);            
        } catch( Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }

    public static <T> SqlStatement<?> bindDataWith(SqlStatement<?> query, Foo value, String paramName, Function<Foo, ?> paramFn) {
        return query.bind(paramName, paramFn.apply(value));
    }

}

如果我更换也可以

query.bind("foo", foo.getId());

Foo.bindDataWith(query, foo, "foo", Foo::getId);

但是,如果我更换

Foo foo = new Foo("bar");

Foo foo = new Foo(null);

(换句话说,如果getter返回null字符串),则query.bind("foo", foo.getId()); 变体仍然有效,但是Foo.bindDataWith(query, foo, "foo", Foo::getId); 失败!

我不明白为什么。 在这两种情况下,都应该在foo上调用getId() ,它是一个字符串(尽管为null)。 谁能解释为什么这行不通?

无效的完整版本为:

public class Foo {
    private final String id;

    public Foo(String id) {
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public static void main(String[] args) throws SQLException {
        // Setup DB connection
        PoolDataSource  pds = PoolDataSourceFactory.getPoolDataSource();
        pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
        pds.setURL("jdbc:oracle:thin:@MY_IP:MY_PORT:MY_INSTANCE_NAME");
        pds.setUser(MY_USER);
        pds.setPassword(MY_PASSWORD);
        Jdbi jdbi = Jdbi.create(pds);

        // Create object
        Foo foo = new Foo(null);
        try {
            // Call DB
            String val = jdbi.withHandle((handle) -> {
                Query query = handle.createQuery("select :foo foo from dual");
                Foo.bindDataWith(query, foo, "foo", Foo::getId);
                return query.mapTo(String.class).findOnly();
            });
            System.out.println("Value: " + val);            
        } catch( Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }

    public static <T> SqlStatement<?> bindDataWith(SqlStatement<?> query, Foo value, String paramName, Function<Foo, ?> paramFn) {
        return query.bind(paramName, paramFn.apply(value));
    }

}

而错误的控制台输出是

Exception while binding named parameter 'foo' [statement:"select :foo foo from dual", rewritten:"select :foo foo from dual", parsed:"ParsedSql{sql='select ? foo from dual', parameters=ParsedParameters{positional=false, parameterNames=[foo]}}", arguments:{ positional:{}, named:{foo:NULL}, finder:[]}]
org.jdbi.v3.core.statement.UnableToCreateStatementException: Exception while binding named parameter 'foo' [statement:"select :foo foo from dual", rewritten:"select :foo foo from dual", parsed:"ParsedSql{sql='select ? foo from dual', parameters=ParsedParameters{positional=false, parameterNames=[foo]}}", arguments:{ positional:{}, named:{foo:NULL}, finder:[]}]
    at org.jdbi.v3.core.statement.ArgumentBinder.bindNamed(ArgumentBinder.java:57)
    at org.jdbi.v3.core.statement.ArgumentBinder.bind(ArgumentBinder.java:26)
    at org.jdbi.v3.core.statement.SqlStatement.internalExecute(SqlStatement.java:1378)
    at org.jdbi.v3.core.result.ResultProducers.lambda$getResultSet$2(ResultProducers.java:59)
    at org.jdbi.v3.core.result.ResultIterable.lambda$of$0(ResultIterable.java:53)
    at org.jdbi.v3.core.result.ResultIterable.findOnly(ResultIterable.java:97)
    at com.carus.api.bookings.actions.Foo.lambda$0(Foo.java:41)
    at org.jdbi.v3.core.Jdbi.withHandle(Jdbi.java:340)
    at com.carus.api.bookings.actions.Foo.main(Foo.java:37)
Caused by: java.sql.SQLException: Invalid column type: 1111
    at oracle.jdbc.driver.OracleStatement.getInternalType(OracleStatement.java:3978)
    at oracle.jdbc.driver.OraclePreparedStatement.setNullCritical(OraclePreparedStatement.java:4472)
    at oracle.jdbc.driver.OraclePreparedStatement.setNull(OraclePreparedStatement.java:4456)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.setNull(OraclePreparedStatementWrapper.java:1008)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at oracle.ucp.jdbc.proxy.StatementProxyFactory.invoke(StatementProxyFactory.java:367)
    at oracle.ucp.jdbc.proxy.PreparedStatementProxyFactory.invoke(PreparedStatementProxyFactory.java:194)
    at com.sun.proxy.$Proxy3.setNull(Unknown Source)
    at org.jdbi.v3.core.argument.NullArgument.apply(NullArgument.java:39)
    at org.jdbi.v3.core.statement.ArgumentBinder.bindNamed(ArgumentBinder.java:54)
    ... 8 more

我在Java 8中使用jdbi 3和ojdbc8(12.2.0.1)

[1]为了简单起见,我将所有内容都放在一个类中。 实际上,我的函数是一个泛型,它使用值列表和参数名称映射来获取函数。

在JDBC(Jdbi构建于JDBC之上)中,当您将null绑定为参数时,需要告知JDBC null数据类型。 在您的示例中,我们将java.sql.Types.VARCHAR数据类型用于String

Jdbi的SqlStatement.bind()对于许多不同的数据类型(包括String SqlStatement.bind()都已重载。 当您调用这些强类型的重载之一时,Jdbi知道如果您绑定null ,则指定要指定哪种JDBC数据类型,并为您处理。

您尝试使用bind("foo", foo.getId())方法直接起作用的原因是编译器识别出foo.getId()返回String ,并为您调用正确的重载。

但是,您的辅助函数需要一个Function<Foo,?> ,因此编译器会解释为paramFn.apply()返回Object ,而不是String

如果传入null bind(String, Object)变体无法猜测您拥有哪种数据类型。 数据库厂商经常会接受一些数据类型作为一种通配符空的-但它们在它们允许数据类型而有所不同。

  • Postgres接受Types.OTHER (Jdbi默认使用)来输入无类型的null。
  • H2接受Types.NULL (实际上不是类型常量)。
  • 一些实验表明,Oracle JDBC也接受Types.NULL

Jdbi提供了一个配置设置,因此您可以告诉它与无类型null一起使用的内容。 以下代码可以解决您的问题:

Jdbi jdbi = Jdbi.create(...);
jdbi.getConfig(Arguments.class)
    .setUntypedNullArgument(new NullArgument(Types.NULL));

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM