Почему jdbi связывается с функцией в качестве параметра

Я работаю над API, написанным на Java, и использую базу данных Oracle, которую я вызываю через jdbi. Я пишу вспомогательную функцию, чтобы позволить мне связывать переменные для каждого элемента в списке. Тем не менее, список не имеет значения - дело в том, что моя функция берет Query, объект и функцию обратного вызова (я планирую использовать Class: getFoo), чтобы получить значение из объекта.

tl; dr Если возвращаемое значение равно нулю, это не получается, но если я связываю getFoo напрямую, это работает, даже когда значение равно нулю Зачем?

Следующие работы [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);

(другими словами, если получатель возвращает пустую строку), тогда query.bind("foo", foo.getId()); вариант все еще работает, но Foo.bindDataWith(query, foo, "foo", Foo::getId); терпит неудачу!

Я не понимаю почему. В обоих случаях это должен быть вызов getId() на foo, которая является строкой (хотя и нулевой). Кто-нибудь может объяснить, почему это не работает?

Полная версия, которая не работает:

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

Я использую jdbi 3 и ojdbc8 (12.2.0.1) с Java 8.

[1] Я помещаю все в один класс для простоты примера. На самом деле, моя функция - это обобщение, которое принимает список значений и карту имени параметра, чтобы получить функцию.

1 ответ

Решение

В JDBC (на котором построен Jdbi), когда вы связываете null в качестве параметра нужно указать JDBC, какой тип данных null является. В вашем примере мы будем использовать java.sql.Types.VARCHAR тип данных для String,

Jdbi-х SqlStatement.bind() перегружен для разных типов данных, в том числе String, Когда вы вызываете одну из этих строго типизированных перегрузок, Jdbi знает, какой тип данных JDBC указывать, если вы связываете nullи позаботится об этом за вас.

Ваша попытка, которая использует bind("foo", foo.getId()) метод работал напрямую, потому что компилятор распознал foo.getId() возвращается Stringи вызывает правильную перегрузку для вас.

Тем не менее, ваша вспомогательная функция занимает Function<Foo,?>так что компилятор интерпретирует это paramFn.apply() возвращается Objectне String,

bind(String, Object) Вариант не может угадать, какой тип данных у вас есть, если вы передаете в ноль. Поставщики баз данных часто принимают некоторый тип данных как своего рода подстановочный знак для пустых значений, однако они различаются в зависимости от того, какой тип данных они допускают.

  • Postgres принимает Types.OTHER (который Jdbi использует по умолчанию) для нетипизированных нулей.
  • H2 принимает Types.NULL (который на самом деле не является константой типа).
  • Некоторые эксперименты показали, что Oracle JDBC принимает Types.NULL также

Jdbi предоставляет параметр конфигурации, чтобы вы могли указать, что использовать с нетипизированными нулями. Следующий код должен решить вашу проблему:

Jdbi jdbi = Jdbi.create(...);
jdbi.getConfig(Arguments.class)
    .setUntypedNullArgument(new NullArgument(Types.NULL));
Другие вопросы по тегам