Почему 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));