为什么 jdbi 以函数为参数绑定失败
Why does jdbi bind fail with function as parameter
我正在研究 API,用 Java 编写并使用我通过 jdbi 调用的 Oracle 数据库。我正在编写一个辅助函数,以允许我为列表中的每个项目绑定变量。但是,该列表不相关 - 重点是我的函数采用查询、对象和回调函数(我计划使用 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 returns 是空字符串)那么 query.bind("foo", foo.getId());
变体仍然有效,但 Foo.bindDataWith(query, foo, "foo", Foo::getId);
失败!
我不明白为什么。在这两种情况下,它都应该在 foo
上调用 getId()
,这是一个字符串(尽管为空)。 谁能解释为什么这不起作用?
不能使用的完整版本是:
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(ResultProducers.java:59)
at org.jdbi.v3.core.result.ResultIterable.lambda$of[=16=](ResultIterable.java:53)
at org.jdbi.v3.core.result.ResultIterable.findOnly(ResultIterable.java:97)
at com.carus.api.bookings.actions.Foo.lambda[=16=](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] 为了简单起见,我在这里将所有内容都放在一个 class 中。实际上,我的函数是一个泛型函数,它采用值列表和参数名称映射来获取函数。
在JDBC(Jdbi建立在其之上)中,当你绑定null
作为参数时,JDBC需要被告知[=11=是什么数据类型] 是。在您的示例中,我们将为 String
.
使用 java.sql.Types.VARCHAR
数据类型
Jdbi 的 SqlStatement.bind()
为许多不同的数据类型重载,包括 String
。当您调用这些强类型重载之一时,如果您绑定 null
,Jdbi 知道要指定哪种 JDBC 数据类型,并会为您处理它。
您使用 bind("foo", foo.getId())
方法的尝试直接奏效了,因为编译器识别了 foo.getId()
returns String
,并为您调用了正确的重载。
但是,您的辅助函数采用 Function<Foo,?>
,因此编译器将解释为 paramFn.apply()
returns Object
,而不是 String
.
bind(String, Object)
变体如果传入 null 则无法猜测您的数据类型。数据库供应商通常会接受某些数据类型作为空值的一种通配符——但是他们在允许的数据类型方面有所不同。
- 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));
我正在研究 API,用 Java 编写并使用我通过 jdbi 调用的 Oracle 数据库。我正在编写一个辅助函数,以允许我为列表中的每个项目绑定变量。但是,该列表不相关 - 重点是我的函数采用查询、对象和回调函数(我计划使用 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 returns 是空字符串)那么 query.bind("foo", foo.getId());
变体仍然有效,但 Foo.bindDataWith(query, foo, "foo", Foo::getId);
失败!
我不明白为什么。在这两种情况下,它都应该在 foo
上调用 getId()
,这是一个字符串(尽管为空)。 谁能解释为什么这不起作用?
不能使用的完整版本是:
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(ResultProducers.java:59)
at org.jdbi.v3.core.result.ResultIterable.lambda$of[=16=](ResultIterable.java:53)
at org.jdbi.v3.core.result.ResultIterable.findOnly(ResultIterable.java:97)
at com.carus.api.bookings.actions.Foo.lambda[=16=](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] 为了简单起见,我在这里将所有内容都放在一个 class 中。实际上,我的函数是一个泛型函数,它采用值列表和参数名称映射来获取函数。
在JDBC(Jdbi建立在其之上)中,当你绑定null
作为参数时,JDBC需要被告知[=11=是什么数据类型] 是。在您的示例中,我们将为 String
.
java.sql.Types.VARCHAR
数据类型
Jdbi 的 SqlStatement.bind()
为许多不同的数据类型重载,包括 String
。当您调用这些强类型重载之一时,如果您绑定 null
,Jdbi 知道要指定哪种 JDBC 数据类型,并会为您处理它。
您使用 bind("foo", foo.getId())
方法的尝试直接奏效了,因为编译器识别了 foo.getId()
returns String
,并为您调用了正确的重载。
但是,您的辅助函数采用 Function<Foo,?>
,因此编译器将解释为 paramFn.apply()
returns Object
,而不是 String
.
bind(String, Object)
变体如果传入 null 则无法猜测您的数据类型。数据库供应商通常会接受某些数据类型作为空值的一种通配符——但是他们在允许的数据类型方面有所不同。
- 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));