如何将 Oracle 的 JSON_VALUE 函数与 PreparedStatement 一起使用
How to use Oracle's JSON_VALUE function with a PreparedStatement
我正在尝试 运行 使用 Oracle 的 json_value()
函数和 PreparedStatement
进行 SQL 查询。
假设以下 table 设置:
drop table foo cascade constraints purge;
create table foo
(
id integer primary key,
payload clob,
constraint ensure_json check (payload IS JSON STRICT)
);
insert into foo values (1, '{"data": {"k1": 1, "k2": "foo"}}');
以下 SQL 查询工作正常:
select *
from foo
where json_value(payload, '$.data.k1') = '1'
和 returns 预期的行。
但是,当我尝试使用 PreparedStatement
来 运行 此查询时,如以下代码:
String sql =
"select *\n" +
"from foo\n" +
"where json_value(payload, ?) = ?";
PreparedStatement pstmt = conection.prepareStatement(sql);
pstmt.setString(1, "$.data.k1");
pstmt.setString(2, "1");
ResultSet rs = pstmt.executeQuery();
(为了简单起见,我从示例中删除了所有错误检查)
这导致:
java.sql.SQLException: ORA-40454: path expression not a literal
罪魁祸首是传递json路径值(参数索引1),第二个参数没问题。
当我(仅)用字符串常量替换第一个参数时 json_value(payload, '$.data.k1') = ?
准备好的语句工作正常。
在绝望的尝试中,我还尝试在参数中包含单引号:pstmt.setString(1, "'$.data.k1'")
但毫不奇怪,Oracle 也不接受它(相同的错误消息)。
我也试过使用 json_value(payload, concat('$.', ?) )
并且只传递 "data.k1"
作为参数 - 结果相同。
所以,问题是:
- 如何使用
PreparedStatement
参数将 JSON 路径表达式传递给 Oracle 的 json_value
函数?
有什么想法吗?这是驱动程序中的错误还是 Oracle 中的错误? (我在 My Oracle Support 上找不到任何内容)
或者这只是 "not implemented" 的一个例子?
环境:
我正在使用 Oracle 18.0
我尝试了 18.3 和 19.3 版本的 ojdbc10.jar
驱动程序以及 OpenJDK 11。
这不是驱动程序 - 你得到同样的东西 with dynamic SQL:
declare
result foo%rowtype;
begin
execute immediate 'select *
from foo
where json_value(payload, :1) = :2'
into result using '$.data.k1', '1';
dbms_output.put_line(result.payload);
end;
/
ORA-40454: path expression not a literal
ORA-06512: at line 4
这并不是一个真正的错误,它是 documented(强调已添加):
JSON_basic_path_expression
Use this clause to specify a SQL/JSON path expression. The function uses the path expression to evaluate expr and find a scalar JSON value that matches, or satisfies, the path expression. The path expression must be a text literal. See Oracle Database JSON Developer's Guide for the full semantics of JSON_basic_path_expression.
所以你必须 embed the path literal,而不是绑定它,不幸的是:
declare
result foo%rowtype;
begin
execute immediate 'select *
from foo
where json_value(payload, ''' || '$.data.k1' || ''') = :1'
into result using '1';
dbms_output.put_line(result.payload);
end;
/
1 rows affected
dbms_output:
{"data": {"k1": 1, "k2": "foo"}}
或您的 JDBC 示例(将路径保留为单独的字符串,因为您可能确实希望它成为一个变量):
String sql =
"select *\n" +
"from foo\n" +
"where json_value(payload, '" + "$.data.k1" + "') = ?";
PreparedStatement pstmt = conection.prepareStatement(sql);
pstmt.setString(1, "1");
ResultSet rs = pstmt.executeQuery();
这显然不是您想要做的*,但似乎没有其他选择。除了将您的查询转换为一个函数并将路径变量传递给它之外,但是该函数必须使用动态 SQL,所以效果大致相同 - 也许更容易处理 SQL 注入不过担心这种方式。
* 我知道您知道如何以嵌入式方式执行此操作,并且知道您想使用绑定变量,因为这是正确的做法;我已经详细说明了 你 需要其他访客 *8-)
我正在尝试 运行 使用 Oracle 的 json_value()
函数和 PreparedStatement
进行 SQL 查询。
假设以下 table 设置:
drop table foo cascade constraints purge;
create table foo
(
id integer primary key,
payload clob,
constraint ensure_json check (payload IS JSON STRICT)
);
insert into foo values (1, '{"data": {"k1": 1, "k2": "foo"}}');
以下 SQL 查询工作正常:
select *
from foo
where json_value(payload, '$.data.k1') = '1'
和 returns 预期的行。
但是,当我尝试使用 PreparedStatement
来 运行 此查询时,如以下代码:
String sql =
"select *\n" +
"from foo\n" +
"where json_value(payload, ?) = ?";
PreparedStatement pstmt = conection.prepareStatement(sql);
pstmt.setString(1, "$.data.k1");
pstmt.setString(2, "1");
ResultSet rs = pstmt.executeQuery();
(为了简单起见,我从示例中删除了所有错误检查)
这导致:
java.sql.SQLException: ORA-40454: path expression not a literal
罪魁祸首是传递json路径值(参数索引1),第二个参数没问题。
当我(仅)用字符串常量替换第一个参数时 json_value(payload, '$.data.k1') = ?
准备好的语句工作正常。
在绝望的尝试中,我还尝试在参数中包含单引号:pstmt.setString(1, "'$.data.k1'")
但毫不奇怪,Oracle 也不接受它(相同的错误消息)。
我也试过使用 json_value(payload, concat('$.', ?) )
并且只传递 "data.k1"
作为参数 - 结果相同。
所以,问题是:
- 如何使用
PreparedStatement
参数将 JSON 路径表达式传递给 Oracle 的json_value
函数?
有什么想法吗?这是驱动程序中的错误还是 Oracle 中的错误? (我在 My Oracle Support 上找不到任何内容)
或者这只是 "not implemented" 的一个例子?
环境:
我正在使用 Oracle 18.0
我尝试了 18.3 和 19.3 版本的 ojdbc10.jar
驱动程序以及 OpenJDK 11。
这不是驱动程序 - 你得到同样的东西 with dynamic SQL:
declare
result foo%rowtype;
begin
execute immediate 'select *
from foo
where json_value(payload, :1) = :2'
into result using '$.data.k1', '1';
dbms_output.put_line(result.payload);
end;
/
ORA-40454: path expression not a literal
ORA-06512: at line 4
这并不是一个真正的错误,它是 documented(强调已添加):
JSON_basic_path_expression
Use this clause to specify a SQL/JSON path expression. The function uses the path expression to evaluate expr and find a scalar JSON value that matches, or satisfies, the path expression. The path expression must be a text literal. See Oracle Database JSON Developer's Guide for the full semantics of JSON_basic_path_expression.
所以你必须 embed the path literal,而不是绑定它,不幸的是:
declare
result foo%rowtype;
begin
execute immediate 'select *
from foo
where json_value(payload, ''' || '$.data.k1' || ''') = :1'
into result using '1';
dbms_output.put_line(result.payload);
end;
/
1 rows affected
dbms_output:
{"data": {"k1": 1, "k2": "foo"}}
或您的 JDBC 示例(将路径保留为单独的字符串,因为您可能确实希望它成为一个变量):
String sql =
"select *\n" +
"from foo\n" +
"where json_value(payload, '" + "$.data.k1" + "') = ?";
PreparedStatement pstmt = conection.prepareStatement(sql);
pstmt.setString(1, "1");
ResultSet rs = pstmt.executeQuery();
这显然不是您想要做的*,但似乎没有其他选择。除了将您的查询转换为一个函数并将路径变量传递给它之外,但是该函数必须使用动态 SQL,所以效果大致相同 - 也许更容易处理 SQL 注入不过担心这种方式。
* 我知道您知道如何以嵌入式方式执行此操作,并且知道您想使用绑定变量,因为这是正确的做法;我已经详细说明了 你 需要其他访客 *8-)